AlarmViewHolder hides dismiss button if recurring and upcoming alarm dismissed via notification

This commit is contained in:
Phillip Hsu 2016-07-08 19:31:03 -07:00
parent 9380f8f579
commit 20b5ff2d8a
7 changed files with 61 additions and 21 deletions

View File

@ -11,6 +11,7 @@ import org.json.JSONObject;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;
import static com.philliphsu.clock2.DaysOfWeek.NUM_DAYS; import static com.philliphsu.clock2.DaysOfWeek.NUM_DAYS;
import static com.philliphsu.clock2.DaysOfWeek.SATURDAY; import static com.philliphsu.clock2.DaysOfWeek.SATURDAY;
@ -28,6 +29,7 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
private long snoozingUntilMillis; private long snoozingUntilMillis;
private boolean enabled; private boolean enabled;
private final boolean[] recurringDays = new boolean[NUM_DAYS]; private final boolean[] recurringDays = new boolean[NUM_DAYS];
private boolean ignoreUpcomingRingTime;
// ==================================================== // ====================================================
public abstract int hour(); public abstract int hour();
@ -116,6 +118,14 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
return count; return count;
} }
public void ignoreUpcomingRingTime(boolean ignore) {
ignoreUpcomingRingTime = ignore;
}
public boolean isIgnoringUpcomingRingTime() {
return ignoreUpcomingRingTime;
}
public long ringsAt() { public long ringsAt() {
// Always with respect to the current date and time // Always with respect to the current date and time
Calendar calendar = new GregorianCalendar(); Calendar calendar = new GregorianCalendar();
@ -124,22 +134,26 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0); calendar.set(Calendar.MILLISECOND, 0);
long baseRingTime = calendar.getTimeInMillis();
if (!hasRecurrence()) { if (!hasRecurrence()) {
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { if (baseRingTime <= System.currentTimeMillis()) {
// The specified time has passed for today // The specified time has passed for today
calendar.add(Calendar.HOUR_OF_DAY, 24); baseRingTime += TimeUnit.DAYS.toMillis(1);
} }
return baseRingTime;
} else { } else {
// Compute the ring time just for the next closest recurring day. // Compute the ring time just for the next closest recurring day.
// Remember that day constants defined in the Calendar class are not zero-based like ours, so we have to // Remember that day constants defined in the Calendar class are
// compensate with an offset of magnitude one, with the appropriate sign based on the situation. // not zero-based like ours, so we have to compensate with an offset
// of magnitude one, with the appropriate sign based on the situation.
int weekdayToday = calendar.get(Calendar.DAY_OF_WEEK); int weekdayToday = calendar.get(Calendar.DAY_OF_WEEK);
int numDaysFromToday = -1; int numDaysFromToday = -1;
for (int i = weekdayToday; i <= Calendar.SATURDAY; i++) { for (int i = weekdayToday; i <= Calendar.SATURDAY; i++) {
if (isRecurring(i - 1 /*match up with our day constant*/)) { if (isRecurring(i - 1 /*match up with our day constant*/)) {
if (i == weekdayToday) { if (i == weekdayToday) {
if (calendar.getTimeInMillis() > System.currentTimeMillis()) { if (baseRingTime > System.currentTimeMillis()) {
// The normal ring time has not passed yet // The normal ring time has not passed yet
numDaysFromToday = 0; numDaysFromToday = 0;
break; break;
@ -164,26 +178,29 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
// Still not computed yet. The only recurring day is weekdayToday, // Still not computed yet. The only recurring day is weekdayToday,
// and its normal ring time has already passed. // and its normal ring time has already passed.
if (numDaysFromToday < 0 && isRecurring(weekdayToday - 1) if (numDaysFromToday < 0 && isRecurring(weekdayToday - 1)
&& calendar.getTimeInMillis() <= System.currentTimeMillis()) { && baseRingTime <= System.currentTimeMillis()) {
numDaysFromToday = 7; numDaysFromToday = 7;
} }
if (numDaysFromToday < 0) if (numDaysFromToday < 0)
throw new IllegalStateException("How did we get here?"); throw new IllegalStateException("How did we get here?");
calendar.add(Calendar.HOUR_OF_DAY, 24 * numDaysFromToday); return baseRingTime + TimeUnit.DAYS.toMillis(numDaysFromToday);
} }
return calendar.getTimeInMillis();
} }
public long ringsIn() { public long ringsIn() {
return ringsAt() - System.currentTimeMillis(); return ringsAt() - System.currentTimeMillis();
} }
/** @return true if this Alarm will ring in the next {@code hours} hours */ /**
* Returns whether this Alarm is upcoming in the next {@code hours} hours.
* To return true, this Alarm must not have its {@link #ignoreUpcomingRingTime}
* member field set to true.
* @see #ignoreUpcomingRingTime(boolean)
*/
public boolean ringsWithinHours(int hours) { public boolean ringsWithinHours(int hours) {
return ringsIn() <= hours * 3600000; return !ignoreUpcomingRingTime && ringsIn() <= TimeUnit.HOURS.toMillis(hours);
} }
public int intId() { public int intId() {
@ -233,6 +250,7 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
dest.writeLong(snoozingUntilMillis); dest.writeLong(snoozingUntilMillis);
dest.writeInt(enabled ? 1 : 0); dest.writeInt(enabled ? 1 : 0);
dest.writeBooleanArray(recurringDays); dest.writeBooleanArray(recurringDays);
dest.writeInt(ignoreUpcomingRingTime ? 1 : 0);
} }
private static Alarm create(Parcel in) { private static Alarm create(Parcel in) {
@ -247,6 +265,7 @@ public abstract class Alarm implements JsonSerializable, Parcelable {
alarm.snoozingUntilMillis = in.readLong(); alarm.snoozingUntilMillis = in.readLong();
alarm.enabled = in.readInt() != 0; alarm.enabled = in.readInt() != 0;
in.readBooleanArray(alarm.recurringDays); in.readBooleanArray(alarm.recurringDays);
alarm.ignoreUpcomingRingTime = in.readInt() != 0;
return alarm; return alarm;
} }

View File

@ -46,10 +46,13 @@ public class PendingAlarmScheduler extends BroadcastReceiver {
if (!alarm.isEnabled()) { if (!alarm.isEnabled()) {
throw new IllegalStateException("Alarm must be enabled!"); throw new IllegalStateException("Alarm must be enabled!");
} }
alarm.ignoreUpcomingRingTime(false); // allow #ringsWithinHours() to behave normally
// Because showToast = false, we don't do any UI work. // Because showToast = false, we don't do any UI work.
// TODO: Since we're in a worker thread, verify that the // TODO: Since we're in a worker thread, verify that the
// UI related code within will not cause us to crash. // UI related code within will not cause us to crash.
AlarmUtils.scheduleAlarm(context, alarm, false); AlarmUtils.scheduleAlarm(context, alarm, false);
// Update the db
AlarmUtils.save(context, alarm);
} }
}).start(); }).start();
} }

View File

@ -72,7 +72,7 @@ public class UpcomingAlarmReceiver extends BroadcastReceiver {
} }
} }
Intent in = new Intent(context, getClass()) Intent in = new Intent(context, UpcomingAlarmReceiver.class)
.putExtra(EXTRA_ALARM_ID, id) // TOneverDO: cast to int .putExtra(EXTRA_ALARM_ID, id) // TOneverDO: cast to int
.setAction(ACTION_DISMISS_NOW); .setAction(ACTION_DISMISS_NOW);
PendingIntent pi = PendingIntent.getBroadcast(context, (int) id, in, FLAG_ONE_SHOT); PendingIntent pi = PendingIntent.getBroadcast(context, (int) id, in, FLAG_ONE_SHOT);

View File

@ -68,6 +68,8 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
@OnClick(R.id.dismiss) @OnClick(R.id.dismiss)
void dismiss() { void dismiss() {
// TODO: This is NOT correct for all alarms! This may be correct for single
// use alarms, but not so for recurring alarms!
mSwitch.setPressed(true); // needed so the OnCheckedChange event calls through mSwitch.setPressed(true); // needed so the OnCheckedChange event calls through
bindSwitch(false); // fires OnCheckedChange to do the binding for you bindSwitch(false); // fires OnCheckedChange to do the binding for you
// TOneverDO: AlarmUtils.cancelAlarm() otherwise it will be called twice // TOneverDO: AlarmUtils.cancelAlarm() otherwise it will be called twice
@ -120,10 +122,15 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
if (alarm.isEnabled()) { if (alarm.isEnabled()) {
// TODO: On Moto X, upcoming notification doesn't post immediately // TODO: On Moto X, upcoming notification doesn't post immediately
AlarmUtils.scheduleAlarm(getContext(), alarm, true); AlarmUtils.scheduleAlarm(getContext(), alarm, true);
// TODO: We don't have to manually bind these if we update the alarm via the db,
// because that would trigger the loader to reload the dataset and hence the
// corresponding VH is rebound.
bindCountdown(true, alarm.ringsIn()); bindCountdown(true, alarm.ringsIn());
bindDismissButton(alarm); bindDismissButton(alarm);
} else { } else {
AlarmUtils.cancelAlarm(getContext(), alarm, true); // saves repo AlarmUtils.cancelAlarm(getContext(), alarm, true); // saves repo
// TODO: Remove these, cancelAlarm will prompt update call to the db, so all VHs will
// be rebound.
bindCountdown(false, -1); bindCountdown(false, -1);
bindDismissButton(false, ""); bindDismissButton(false, "");
} }
@ -164,7 +171,6 @@ public class AlarmViewHolder extends BaseViewHolder<Alarm> implements AlarmCount
String buttonText = alarm.isSnoozed() String buttonText = alarm.isSnoozed()
? getContext().getString(R.string.title_snoozing_until, formatTime(getContext(), alarm.snoozingUntil())) ? getContext().getString(R.string.title_snoozing_until, formatTime(getContext(), alarm.snoozingUntil()))
: getContext().getString(R.string.dismiss_now); : getContext().getString(R.string.dismiss_now);
// when this alarm crosses the upcoming threshold, so we can show this button.
bindDismissButton(visible, buttonText); bindDismissButton(visible, buttonText);
} }

View File

@ -52,6 +52,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
private static final String COLUMN_THURSDAY = "thursday"; private static final String COLUMN_THURSDAY = "thursday";
private static final String COLUMN_FRIDAY = "friday"; private static final String COLUMN_FRIDAY = "friday";
private static final String COLUMN_SATURDAY = "saturday"; private static final String COLUMN_SATURDAY = "saturday";
private static final String COLUMN_IGNORE_UPCOMING_RING_TIME = "ignore_upcoming_ring_time";
// https://www.sqlite.org/lang_select.html#orderby // https://www.sqlite.org/lang_select.html#orderby
// Rows are first sorted based on the results of evaluating the left-most expression in the // Rows are first sorted based on the results of evaluating the left-most expression in the
@ -98,7 +99,8 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
+ COLUMN_WEDNESDAY + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_WEDNESDAY + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_THURSDAY + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_THURSDAY + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_FRIDAY + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_FRIDAY + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_SATURDAY + " INTEGER NOT NULL DEFAULT 0);"); + COLUMN_SATURDAY + " INTEGER NOT NULL DEFAULT 0, "
+ COLUMN_IGNORE_UPCOMING_RING_TIME + " INTEGER NOT NULL);");
} }
@Override @Override
@ -168,7 +170,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
} }
public AlarmCursor queryEnabledAlarms() { public AlarmCursor queryEnabledAlarms() {
return queryAlarms(COLUMN_ENABLED + " = " + 1); return queryAlarms(COLUMN_ENABLED + " = 1");
} }
private AlarmCursor queryAlarms(String where) { private AlarmCursor queryAlarms(String where) {
@ -194,6 +196,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
values.put(COLUMN_THURSDAY, alarm.isRecurring(THURSDAY)); values.put(COLUMN_THURSDAY, alarm.isRecurring(THURSDAY));
values.put(COLUMN_FRIDAY, alarm.isRecurring(FRIDAY)); values.put(COLUMN_FRIDAY, alarm.isRecurring(FRIDAY));
values.put(COLUMN_SATURDAY, alarm.isRecurring(SATURDAY)); values.put(COLUMN_SATURDAY, alarm.isRecurring(SATURDAY));
values.put(COLUMN_IGNORE_UPCOMING_RING_TIME, alarm.isIgnoringUpcomingRingTime());
return values; return values;
} }
@ -241,6 +244,7 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper {
alarm.setRecurring(THURSDAY, isTrue(COLUMN_THURSDAY)); alarm.setRecurring(THURSDAY, isTrue(COLUMN_THURSDAY));
alarm.setRecurring(FRIDAY, isTrue(COLUMN_FRIDAY)); alarm.setRecurring(FRIDAY, isTrue(COLUMN_FRIDAY));
alarm.setRecurring(SATURDAY, isTrue(COLUMN_SATURDAY)); alarm.setRecurring(SATURDAY, isTrue(COLUMN_SATURDAY));
alarm.ignoreUpcomingRingTime(isTrue(COLUMN_IGNORE_UPCOMING_RING_TIME));
return alarm; return alarm;
} }

View File

@ -10,10 +10,10 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.AsyncItemChangeHandler;
import com.philliphsu.clock2.PendingAlarmScheduler; import com.philliphsu.clock2.PendingAlarmScheduler;
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.DatabaseManager;
import com.philliphsu.clock2.ringtone.RingtoneActivity; import com.philliphsu.clock2.ringtone.RingtoneActivity;
import com.philliphsu.clock2.ringtone.RingtoneService; import com.philliphsu.clock2.ringtone.RingtoneService;
@ -119,8 +119,9 @@ public final class AlarmUtils {
} else { } else {
if (a.isEnabled()) { if (a.isEnabled()) {
if (a.ringsWithinHours(hoursBeforeUpcoming(c))) { if (a.ringsWithinHours(hoursBeforeUpcoming(c))) {
// Still upcoming today, so wait until the normal ring time passes before // Still upcoming today, so wait until the normal ring time
// rescheduling the alarm. // passes before rescheduling the alarm.
a.ignoreUpcomingRingTime(true); // Useful only for VH binding
Intent intent = new Intent(c, PendingAlarmScheduler.class) Intent intent = new Intent(c, PendingAlarmScheduler.class)
.putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, a.id()); .putExtra(PendingAlarmScheduler.EXTRA_ALARM_ID, a.id());
pi = PendingIntent.getBroadcast(c, a.intId(), intent, PendingIntent.FLAG_ONE_SHOT); pi = PendingIntent.getBroadcast(c, a.intId(), intent, PendingIntent.FLAG_ONE_SHOT);
@ -209,7 +210,13 @@ public final class AlarmUtils {
return pi; return pi;
} }
private static void save(Context c, Alarm alarm) { public static void save(final Context c, final Alarm alarm) {
new AsyncItemChangeHandler(c, null, null).asyncUpdateAlarm(alarm); // TODO: Will using the Runnable like this cause a memory leak?
new Thread(new Runnable() {
@Override
public void run() {
DatabaseManager.getInstance(c).updateAlarm(alarm.id(), alarm);
}
}).start();
} }
} }

View File

@ -9,4 +9,5 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager" app:layoutManager="LinearLayoutManager"
tools:context="com.philliphsu.clock2.alarms.AlarmsFragment" tools:context="com.philliphsu.clock2.alarms.AlarmsFragment"
tools:listitem="@layout/item_alarm"/> tools:listitem="@layout/item_alarm"
android:scrollbars="vertical"/>