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.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<Alarm> 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<Alarm> 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<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) {
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<Alarm> implements AlarmCount
private Alarm getAlarm() {
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.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

View File

@ -91,7 +91,9 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter {
@Override
public void delete() {
if (mAlarm != null) {
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

View File

@ -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();
}

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."
// 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

View File

@ -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();
}
}