From abdb939ada88c73ff50107b9d7bcc602a3d34dde Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Tue, 7 Jun 2016 17:35:50 -0700 Subject: [PATCH] More Alarm utility methods for cancelling and scheduling --- .../clock2/alarms/AlarmViewHolder.java | 110 +++++++++++------- .../clock2/editalarm/EditAlarmActivity.java | 4 - .../clock2/editalarm/EditAlarmPresenter.java | 8 +- .../clock2/ringtone/RingtoneActivity.java | 16 +-- .../clock2/ringtone/RingtoneService.java | 15 ++- .../philliphsu/clock2/util/AlarmUtils.java | 35 ++++++ 6 files changed, 126 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java index 102876e..ec6dc35 100644 --- a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java +++ b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java @@ -16,6 +16,7 @@ import com.philliphsu.clock2.BaseViewHolder; import com.philliphsu.clock2.DaysOfWeek; import com.philliphsu.clock2.OnListItemInteractionListener; import com.philliphsu.clock2.R; +import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.util.AlarmUtils; import java.util.Date; @@ -52,41 +53,9 @@ public class AlarmViewHolder extends BaseViewHolder implements AlarmCount bindTime(new Date(alarm.ringsAt())); bindSwitch(alarm.isEnabled()); bindCountdown(alarm.isEnabled(), alarm.ringsIn()); - - int hoursBeforeUpcoming = AlarmUtils.hoursBeforeUpcoming(getContext()); - boolean visible = alarm.isEnabled() && (alarm.ringsWithinHours(hoursBeforeUpcoming) || alarm.isSnoozed()); - String buttonText = alarm.isSnoozed() - ? getContext().getString(R.string.title_snoozing_until, formatTime(getContext(), alarm.snoozingUntil())) - : getContext().getString(R.string.dismiss_now); - // TODO: Register dynamic broadcast receiver in this class to listen for - // when this alarm crosses the upcoming threshold, so we can show this button. - bindDismissButton(visible, buttonText); - - // Should also be visible even if alarm has no label so mCountdown is properly positioned next - // to mLabel. That is, mCountdown's layout position is dependent on mLabel being present. - boolean labelVisible = alarm.label().length() > 0 || mCountdown.getVisibility() == VISIBLE; - bindLabel(labelVisible, alarm.label()); - - int num = alarm.numRecurringDays(); - String text; - if (num == NUM_DAYS) { - text = getContext().getString(R.string.every_day); - } else if (num == 0) { - text = ""; - } else { - StringBuilder sb = new StringBuilder(); - for (int i = 0 /* Ordinal days*/; i < NUM_DAYS; i++) { - // What day is at this position in the week? - int weekDay = DaysOfWeek.getInstance(getContext()).weekDayAt(i); - if (alarm.isRecurring(weekDay)) { - sb.append(DaysOfWeek.getLabel(weekDay)).append(", "); - } - } - // Cut off the last comma and space - sb.delete(sb.length() - 2, sb.length()); - text = sb.toString(); - } - bindDays(num > 0, text); + bindDismissButton(alarm); + bindLabel(alarm); + bindDays(alarm); } @Override @@ -95,14 +64,26 @@ public class AlarmViewHolder extends BaseViewHolder implements AlarmCount } @OnClick(R.id.dismiss) - void onClick() { - Alarm alarm = getAlarm(); - AlarmUtils.cancelAlarm(getContext(), alarm); - if (alarm.isSnoozed()) { - alarm.stopSnoozing(); // TOneverDO: before cancelAlarm() - } + void dismiss() { + AlarmUtils.cancelAlarm(getContext(), getAlarm()); bindDismissButton(false, ""); // Will be set to correct text the next time we bind. - // TODO: Check if alarm has no recurrence, then turn it off. + // If cancelAlarm() modified the alarm's fields, then it will save changes for you. + } + + @OnClick(R.id.on_off_switch) + void toggle() { + Alarm alarm = getAlarm(); + alarm.setEnabled(mSwitch.isChecked()); + if (alarm.isEnabled()) { + AlarmUtils.scheduleAlarm(getContext(), alarm); + bindCountdown(true, alarm.ringsIn()); + bindDismissButton(alarm); + } else { + AlarmUtils.cancelAlarm(getContext(), alarm); // might save repo + bindCountdown(false, -1); + bindDismissButton(false, ""); + } + save(); // TODO: Problem! If cancelAlarm() saves the repo, this is a redundant call! } private void bindTime(Date date) { @@ -132,16 +113,57 @@ public class AlarmViewHolder extends BaseViewHolder implements AlarmCount } } + private void bindDismissButton(Alarm alarm) { + int hoursBeforeUpcoming = AlarmUtils.hoursBeforeUpcoming(getContext()); + boolean visible = alarm.isEnabled() && (alarm.ringsWithinHours(hoursBeforeUpcoming) || alarm.isSnoozed()); + String buttonText = alarm.isSnoozed() + ? getContext().getString(R.string.title_snoozing_until, formatTime(getContext(), alarm.snoozingUntil())) + : getContext().getString(R.string.dismiss_now); + // TODO: Register dynamic broadcast receiver in this class to listen for + // when this alarm crosses the upcoming threshold, so we can show this button. + bindDismissButton(visible, buttonText); + } + private void bindDismissButton(boolean visible, String buttonText) { setVisibility(mDismissButton, visible); mDismissButton.setText(buttonText); } + private void bindLabel(Alarm alarm) { + // Should also be visible even if alarm has no label so mCountdown is properly positioned next + // to mLabel. That is, mCountdown's layout position is dependent on mLabel being present. + boolean labelVisible = alarm.label().length() > 0 || mCountdown.getVisibility() == VISIBLE; + bindLabel(labelVisible, alarm.label()); + } + private void bindLabel(boolean visible, String label) { setVisibility(mLabel, visible); mLabel.setText(label); } + private void bindDays(Alarm alarm) { + int num = alarm.numRecurringDays(); + String text; + if (num == NUM_DAYS) { + text = getContext().getString(R.string.every_day); + } else if (num == 0) { + text = ""; + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 0 /* Ordinal days*/; i < NUM_DAYS; i++) { + // What day is at this position in the week? + int weekDay = DaysOfWeek.getInstance(getContext()).weekDayAt(i); + if (alarm.isRecurring(weekDay)) { + sb.append(DaysOfWeek.getLabel(weekDay)).append(", "); + } + } + // Cut off the last comma and space + sb.delete(sb.length() - 2, sb.length()); + text = sb.toString(); + } + bindDays(num > 0, text); + } + private void bindDays(boolean visible, String text) { setVisibility(mDays, visible); mDays.setText(text); @@ -154,4 +176,8 @@ public class AlarmViewHolder extends BaseViewHolder implements AlarmCount private Alarm getAlarm() { return getItem(); } + + private void save() { + AlarmsRepository.getInstance(getContext()).saveItems(); + } } diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java index 1335306..b2c2589 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java @@ -29,7 +29,6 @@ import com.philliphsu.clock2.R; import com.philliphsu.clock2.SharedPreferencesHelper; import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.util.AlarmUtils; -import com.philliphsu.clock2.util.DurationUtils; import java.util.Date; @@ -425,9 +424,6 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi @Override public void scheduleAlarm(Alarm alarm) { AlarmUtils.scheduleAlarm(this, alarm); - String message = getString(R.string.alarm_set_for, - DurationUtils.toString(this, alarm.ringsIn(), false /*abbreviate?*/)); - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } @Override diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmPresenter.java b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmPresenter.java index 9f12d3b..2b71d20 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmPresenter.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmPresenter.java @@ -91,7 +91,9 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter { @Override public void delete() { if (mAlarm != null) { - mAlarmUtilsHelper.cancelAlarm(mAlarm); // (1) + if (mAlarm.isEnabled()) { + mAlarmUtilsHelper.cancelAlarm(mAlarm); // (1) + } mRepository.deleteItem(mAlarm); // TOneverDO: before (1) } mView.showEditorClosed(); @@ -105,8 +107,12 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter { @Override public void stopSnoozing() { dismissNow(); // MUST be first, see AlarmUtils.notifyUpcomingAlarmIntent() + + // AlarmUtils.cancelAlarm() does this for you if snoozed + /* mAlarm.stopSnoozing(); // TOneverDO: before dismissNow() mRepository.saveItems(); + */ } @Override diff --git a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java index 3f23fd7..6a950b3 100644 --- a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java @@ -11,14 +11,12 @@ import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.Button; -import android.widget.Toast; import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.R; import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.util.AlarmUtils; -import static com.philliphsu.clock2.util.DateFormatUtils.formatTime; import static com.philliphsu.clock2.util.Preconditions.checkNotNull; /** @@ -132,18 +130,16 @@ public class RingtoneActivity extends AppCompatActivity implements RingtoneServi } private void snooze() { - int snoozeMins = AlarmUtils.snoozeDuration(this); - mAlarm.snooze(snoozeMins); - AlarmUtils.scheduleAlarm(this, mAlarm); - AlarmsRepository.getInstance(this).saveItems(); - // Display toast - String message = getString(R.string.title_snoozing_until, formatTime(this, mAlarm.snoozingUntil())); - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - dismiss(); + AlarmUtils.snoozeAlarm(this, mAlarm); + // Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example, + // we don't want the alarm, if it has no recurrence, to be turned off immediately. + unbindService(); // don't wait for finish() to call onDestroy() + finish(); } private void dismiss() { // TODO: Do we need to cancel the PendingIntent and the alarm in AlarmManager? + AlarmUtils.cancelAlarm(this, mAlarm); unbindService(); // don't wait for finish() to call onDestroy() finish(); } diff --git a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java index 750dba4..c295932 100644 --- a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java +++ b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneService.java @@ -77,14 +77,19 @@ public class RingtoneService extends Service { // TODO: abstract this, make subc // stopService(Intent) [or stopSelf()] is called, regardless of whether any clients are connected to it." // I have found the regardless part does not apply here. You MUST also unbind any clients from this service // at the same time you stop this service! + long id = intent.getLongExtra(EXTRA_ITEM_ID, -1); + if (id < 0) + throw new IllegalStateException("No item id set"); + Alarm alarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id)); if (ACTION_SNOOZE.equals(intent.getAction())) { - long id = intent.getLongExtra(EXTRA_ITEM_ID, -1); - if (id < 0) throw new IllegalStateException("No item id set"); - Alarm alarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id)); - alarm.snooze(AlarmUtils.snoozeDuration(this)); - AlarmUtils.scheduleAlarm(this, alarm); + AlarmUtils.snoozeAlarm(this, alarm); + } else if (ACTION_DISMISS.equals(intent.getAction())) { + AlarmUtils.cancelAlarm(this, alarm); + } else { + throw new UnsupportedOperationException(); } + stopSelf(startId); if (mRingtoneCallback != null) { mRingtoneCallback.onServiceFinish(); // tell client to unbind from this service diff --git a/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java b/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java index aabf56e..917e6b5 100644 --- a/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java +++ b/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java @@ -7,16 +7,19 @@ import android.content.Intent; import android.preference.PreferenceManager; import android.support.annotation.StringRes; import android.util.Log; +import android.widget.Toast; import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.R; import com.philliphsu.clock2.UpcomingAlarmReceiver; +import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.ringtone.RingtoneActivity; import com.philliphsu.clock2.ringtone.RingtoneService; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_NO_CREATE; import static android.app.PendingIntent.getActivity; +import static com.philliphsu.clock2.util.DateFormatUtils.formatTime; import static com.philliphsu.clock2.util.Preconditions.checkNotNull; /** @@ -50,6 +53,18 @@ public final class AlarmUtils { am.set(AlarmManager.RTC_WAKEUP, ringAt - hoursBeforeUpcoming(context) * 3600000, notifyUpcomingAlarmIntent(context, alarm, false)); am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent(context, alarm, false)); + + // Display toast + String message; + if (alarm.isSnoozed()) { + message = context.getString(R.string.title_snoozing_until, + formatTime(context, alarm.snoozingUntil())); + } else { + message = context.getString(R.string.alarm_set_for, + DurationUtils.toString(context, alarm.ringsIn(), false /*abbreviate?*/)); + } + // TODO: Will toasts show for any Context? e.g. IntentService can't do anything on UI thread. + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } public static void cancelAlarm(Context c, Alarm a) { @@ -66,6 +81,16 @@ public final class AlarmUtils { removeUpcomingAlarmNotification(c, a); + if (a.isSnoozed()) { + a.stopSnoozing(); + save(c); + } + + if (!a.hasRecurrence()) { + a.setEnabled(false); + save(c); + } + // If service is not running, nothing happens // TODO: Since RingtoneService is a bound service, will this destroy the service after returning? // Note that if a stopped service still has ServiceConnection objects bound to it with the @@ -73,6 +98,12 @@ public final class AlarmUtils { c.stopService(new Intent(c, RingtoneService.class)); } + public static void snoozeAlarm(Context c, Alarm a) { + a.snooze(AlarmUtils.snoozeDuration(c)); + AlarmUtils.scheduleAlarm(c, a); + save(c); + } + public static void removeUpcomingAlarmNotification(Context c, Alarm a) { Intent intent = new Intent(c, UpcomingAlarmReceiver.class) .setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION) @@ -130,4 +161,8 @@ public final class AlarmUtils { } return pi; } + + private static void save(Context c) { + AlarmsRepository.getInstance(c).saveItems(); + } }