More Alarm utility methods for cancelling and scheduling

This commit is contained in:
Phillip Hsu 2016-06-07 17:35:50 -07:00
parent 5eb52ee510
commit abdb939ada
6 changed files with 126 additions and 62 deletions

View File

@ -16,6 +16,7 @@ import com.philliphsu.clock2.BaseViewHolder;
import com.philliphsu.clock2.DaysOfWeek; import com.philliphsu.clock2.DaysOfWeek;
import com.philliphsu.clock2.OnListItemInteractionListener; import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.model.AlarmsRepository;
import com.philliphsu.clock2.util.AlarmUtils; import com.philliphsu.clock2.util.AlarmUtils;
import java.util.Date; import java.util.Date;
@ -52,41 +53,9 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
bindTime(new Date(alarm.ringsAt())); bindTime(new Date(alarm.ringsAt()));
bindSwitch(alarm.isEnabled()); bindSwitch(alarm.isEnabled());
bindCountdown(alarm.isEnabled(), alarm.ringsIn()); bindCountdown(alarm.isEnabled(), alarm.ringsIn());
bindDismissButton(alarm);
int hoursBeforeUpcoming = AlarmUtils.hoursBeforeUpcoming(getContext()); bindLabel(alarm);
boolean visible = alarm.isEnabled() && (alarm.ringsWithinHours(hoursBeforeUpcoming) || alarm.isSnoozed()); bindDays(alarm);
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);
} }
@Override @Override
@ -95,14 +64,26 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
} }
@OnClick(R.id.dismiss) @OnClick(R.id.dismiss)
void onClick() { void dismiss() {
Alarm alarm = getAlarm(); AlarmUtils.cancelAlarm(getContext(), getAlarm());
AlarmUtils.cancelAlarm(getContext(), alarm);
if (alarm.isSnoozed()) {
alarm.stopSnoozing(); // TOneverDO: before cancelAlarm()
}
bindDismissButton(false, ""); // Will be set to correct text the next time we bind. 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) { private void bindTime(Date date) {
@ -132,16 +113,57 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> 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) { private void bindDismissButton(boolean visible, String buttonText) {
setVisibility(mDismissButton, visible); setVisibility(mDismissButton, visible);
mDismissButton.setText(buttonText); 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) { private void bindLabel(boolean visible, String label) {
setVisibility(mLabel, visible); setVisibility(mLabel, visible);
mLabel.setText(label); 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) { private void bindDays(boolean visible, String text) {
setVisibility(mDays, visible); setVisibility(mDays, visible);
mDays.setText(text); mDays.setText(text);
@ -154,4 +176,8 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
private Alarm getAlarm() { private Alarm getAlarm() {
return getItem(); return getItem();
} }
private void save() {
AlarmsRepository.getInstance(getContext()).saveItems();
}
} }

View File

@ -29,7 +29,6 @@ import com.philliphsu.clock2.R;
import com.philliphsu.clock2.SharedPreferencesHelper; import com.philliphsu.clock2.SharedPreferencesHelper;
import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.model.AlarmsRepository;
import com.philliphsu.clock2.util.AlarmUtils; import com.philliphsu.clock2.util.AlarmUtils;
import com.philliphsu.clock2.util.DurationUtils;
import java.util.Date; import java.util.Date;
@ -425,9 +424,6 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi
@Override @Override
public void scheduleAlarm(Alarm alarm) { public void scheduleAlarm(Alarm alarm) {
AlarmUtils.scheduleAlarm(this, 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 @Override

View File

@ -91,7 +91,9 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter {
@Override @Override
public void delete() { public void delete() {
if (mAlarm != null) { if (mAlarm != null) {
mAlarmUtilsHelper.cancelAlarm(mAlarm); // (1) if (mAlarm.isEnabled()) {
mAlarmUtilsHelper.cancelAlarm(mAlarm); // (1)
}
mRepository.deleteItem(mAlarm); // TOneverDO: before (1) mRepository.deleteItem(mAlarm); // TOneverDO: before (1)
} }
mView.showEditorClosed(); mView.showEditorClosed();
@ -105,8 +107,12 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter {
@Override @Override
public void stopSnoozing() { public void stopSnoozing() {
dismissNow(); // MUST be first, see AlarmUtils.notifyUpcomingAlarmIntent() dismissNow(); // MUST be first, see AlarmUtils.notifyUpcomingAlarmIntent()
// AlarmUtils.cancelAlarm() does this for you if snoozed
/*
mAlarm.stopSnoozing(); // TOneverDO: before dismissNow() mAlarm.stopSnoozing(); // TOneverDO: before dismissNow()
mRepository.saveItems(); mRepository.saveItems();
*/
} }
@Override @Override

View File

@ -11,14 +11,12 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast;
import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.model.AlarmsRepository; import com.philliphsu.clock2.model.AlarmsRepository;
import com.philliphsu.clock2.util.AlarmUtils; import com.philliphsu.clock2.util.AlarmUtils;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull; import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
/** /**
@ -132,18 +130,16 @@ public class RingtoneActivity extends AppCompatActivity implements RingtoneServi
} }
private void snooze() { private void snooze() {
int snoozeMins = AlarmUtils.snoozeDuration(this); AlarmUtils.snoozeAlarm(this, mAlarm);
mAlarm.snooze(snoozeMins); // Can't call dismiss() because we don't want to also call cancelAlarm()! Why? For example,
AlarmUtils.scheduleAlarm(this, mAlarm); // we don't want the alarm, if it has no recurrence, to be turned off immediately.
AlarmsRepository.getInstance(this).saveItems(); unbindService(); // don't wait for finish() to call onDestroy()
// Display toast finish();
String message = getString(R.string.title_snoozing_until, formatTime(this, mAlarm.snoozingUntil()));
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
dismiss();
} }
private void dismiss() { private void dismiss() {
// TODO: Do we need to cancel the PendingIntent and the alarm in AlarmManager? // 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() unbindService(); // don't wait for finish() to call onDestroy()
finish(); finish();
} }

View File

@ -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." // 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 // 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! // 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())) { if (ACTION_SNOOZE.equals(intent.getAction())) {
long id = intent.getLongExtra(EXTRA_ITEM_ID, -1); AlarmUtils.snoozeAlarm(this, alarm);
if (id < 0) throw new IllegalStateException("No item id set"); } else if (ACTION_DISMISS.equals(intent.getAction())) {
Alarm alarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id)); AlarmUtils.cancelAlarm(this, alarm);
alarm.snooze(AlarmUtils.snoozeDuration(this)); } else {
AlarmUtils.scheduleAlarm(this, alarm); throw new UnsupportedOperationException();
} }
stopSelf(startId); stopSelf(startId);
if (mRingtoneCallback != null) { if (mRingtoneCallback != null) {
mRingtoneCallback.onServiceFinish(); // tell client to unbind from this service mRingtoneCallback.onServiceFinish(); // tell client to unbind from this service

View File

@ -7,16 +7,19 @@ import android.content.Intent;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.UpcomingAlarmReceiver; import com.philliphsu.clock2.UpcomingAlarmReceiver;
import com.philliphsu.clock2.model.AlarmsRepository;
import com.philliphsu.clock2.ringtone.RingtoneActivity; import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.ringtone.RingtoneService; import com.philliphsu.clock2.ringtone.RingtoneService;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_NO_CREATE; import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.getActivity; import static android.app.PendingIntent.getActivity;
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
import static com.philliphsu.clock2.util.Preconditions.checkNotNull; 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, am.set(AlarmManager.RTC_WAKEUP, ringAt - hoursBeforeUpcoming(context) * 3600000,
notifyUpcomingAlarmIntent(context, alarm, false)); notifyUpcomingAlarmIntent(context, alarm, false));
am.setExact(AlarmManager.RTC_WAKEUP, ringAt, alarmIntent(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) { public static void cancelAlarm(Context c, Alarm a) {
@ -66,6 +81,16 @@ public final class AlarmUtils {
removeUpcomingAlarmNotification(c, a); 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 // If service is not running, nothing happens
// TODO: Since RingtoneService is a bound service, will this destroy the service after returning? // 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 // 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)); 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) { public static void removeUpcomingAlarmNotification(Context c, Alarm a) {
Intent intent = new Intent(c, UpcomingAlarmReceiver.class) Intent intent = new Intent(c, UpcomingAlarmReceiver.class)
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION) .setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION)
@ -130,4 +161,8 @@ public final class AlarmUtils {
} }
return pi; return pi;
} }
private static void save(Context c) {
AlarmsRepository.getInstance(c).saveItems();
}
} }