From b6260c3fb7b8987ad2b5eb6abd51c3e24f4bdd24 Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Sat, 11 Jun 2016 13:33:30 -0700 Subject: [PATCH] Before trying new id related stuff --- .../java/com/philliphsu/clock2/Alarm.java | 51 ++++++++++++++++--- .../clock2/UpcomingAlarmReceiver.java | 2 +- .../clock2/alarms/AlarmViewHolder.java | 2 +- .../clock2/editalarm/EditAlarmActivity.java | 2 +- .../clock2/ringtone/RingtoneActivity.java | 2 +- .../philliphsu/clock2/util/AlarmUtils.java | 25 ++++----- 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/philliphsu/clock2/Alarm.java b/app/src/main/java/com/philliphsu/clock2/Alarm.java index 24998fa..f711286 100644 --- a/app/src/main/java/com/philliphsu/clock2/Alarm.java +++ b/app/src/main/java/com/philliphsu/clock2/Alarm.java @@ -33,17 +33,36 @@ public abstract class Alarm implements JsonSerializable { private static final String KEY_LABEL = "label"; private static final String KEY_RINGTONE = "ringtone"; private static final String KEY_VIBRATES = "vibrates"; + private static final String KEY_RECURRENCE_IDS = "recurrence_ids"; // ========= MUTABLE ============== private long snoozingUntilMillis; private boolean enabled; + + // ------------------------------------------ TODO -------------------------------------------- + // The problem with using a counter to assign the unique ids is that you need to restore the counter + // between application sessions to the last value used. This is something that can be easily forgotten, + // or worse, you can botch the code for the restoration, because it does feel hacky. Especially since + // you are now implementing recurring alarms with their own ids, restoring the counter to the correct + // value can be elusive to get right. Even if you do get it right, you had to painstakingly make sure + // your thought process was correct and know which value/object you would have to read the last value from. + // + // An alternative solution is to use UUID hashcodes as the unique ids. For our purposes, we shouldn't need + // to worry about the truncation of 128-bits to 32-bits because, practically, there aren't going to be that + // many Alarms out in memory to worry about id collisions. A significant pro to this solution is that you + // don't need to reset a counter to its previous value from an earlier session when you deserialize Alarms. + // Once you recreate an instance, the hashcode would be set and it's a done deal. + private final long[] recurrenceIds = new long[NUM_DAYS]; + private final boolean[] recurringDays = new boolean[NUM_DAYS]; // ================================ - //public abstract long id(); + //public abstract long id(); // TODO: Find the time to change to int? public abstract int hour(); public abstract int minutes(); @SuppressWarnings("mutable") // TODO: Consider using an immutable collection instead + // TODO: Consider maintaining this yourself. There's no practical value to having an array passed + // in during building. public abstract boolean[] recurringDays(); // array itself is immutable, but elements are not public abstract String label(); public abstract String ringtone(); @@ -58,6 +77,13 @@ public abstract class Alarm implements JsonSerializable { for (int i = 0; i < recurringDays.length; i++) { recurringDays[i] = a.getBoolean(i); } + + a = (JSONArray) jsonObject.get(KEY_RECURRENCE_IDS); + long[] recurrenceIds = new long[a.length()]; + for (int i = 0; i < recurrenceIds.length; i++) { + recurrenceIds[i] = a.getLong(i); + } + Alarm alarm = new AutoValue_Alarm.Builder() .id(jsonObject.getLong(KEY_ID)) .hour(jsonObject.getInt(KEY_HOUR)) @@ -66,6 +92,7 @@ public abstract class Alarm implements JsonSerializable { .label(jsonObject.getString(KEY_LABEL)) .ringtone(jsonObject.getString(KEY_RINGTONE)) .vibrates(jsonObject.getBoolean(KEY_VIBRATES)) + .recurrenceIds(recurrenceIds) .rebuild(); alarm.setEnabled(jsonObject.getBoolean(KEY_ENABLED)); alarm.snoozingUntilMillis = jsonObject.getLong(KEY_SNOOZING_UNTIL_MILLIS); @@ -86,7 +113,8 @@ public abstract class Alarm implements JsonSerializable { .recurringDays(new boolean[NUM_DAYS]) .label("") .ringtone("") - .vibrates(false); + .vibrates(false) + .recurrenceIds(new long[NUM_DAYS]); } public void snooze(int minutes) { @@ -227,7 +255,8 @@ public abstract class Alarm implements JsonSerializable { .put(KEY_RECURRING_DAYS, new JSONArray(recurringDays())) .put(KEY_LABEL, label()) .put(KEY_RINGTONE, ringtone()) - .put(KEY_VIBRATES, vibrates()); + .put(KEY_VIBRATES, vibrates()) + .put(KEY_RECURRENCE_IDS, new JSONArray(recurrenceIds)); } catch (JSONException e) { throw new RuntimeException(e); } @@ -261,6 +290,7 @@ public abstract class Alarm implements JsonSerializable { public abstract Builder label(String label); public abstract Builder ringtone(String ringtone); public abstract Builder vibrates(boolean vibrates); + public abstract Builder recurrenceIds(long[] recurrenceIds); // To enforce preconditions, split the build method into two. autoBuild() is hidden from // callers and is generated. You implement the public build(), which calls the generated // autoBuild() and performs your desired validations. @@ -268,22 +298,27 @@ public abstract class Alarm implements JsonSerializable { public Alarm build() { this.id(++idCount); // TOneverDO: change to post-increment without also adding offset of 1 to idCount in rebuild() + // TODO: Set each recurrenceId in a loop Alarm alarm = autoBuild(); - checkTime(alarm.hour(), alarm.minutes()); - checkRecurringDaysArrayLength(alarm.recurringDays()); + doChecks(alarm); return alarm; } /** Should only be called when recreating an instance from JSON */ private Alarm rebuild() { Alarm alarm = autoBuild(); - idCount = alarm.id(); // prevent future instances from id collision - checkTime(alarm.hour(), alarm.minutes()); - checkRecurringDaysArrayLength(alarm.recurringDays()); + //idCount = alarm.id(); // prevent future instances from id collision + idCount = alarm.recurrenceIds[6]; // the last id set + doChecks(alarm); return alarm; } } + private static void doChecks(Alarm alarm) { + checkTime(alarm.hour(), alarm.minutes()); + checkRecurringDaysArrayLength(alarm.recurringDays()); + } + private static void checkDay(int day) { if (day < SUNDAY || day > SATURDAY) { throw new IllegalArgumentException("Invalid day of week: " + day); diff --git a/app/src/main/java/com/philliphsu/clock2/UpcomingAlarmReceiver.java b/app/src/main/java/com/philliphsu/clock2/UpcomingAlarmReceiver.java index 0096b50..d1bc9e5 100644 --- a/app/src/main/java/com/philliphsu/clock2/UpcomingAlarmReceiver.java +++ b/app/src/main/java/com/philliphsu/clock2/UpcomingAlarmReceiver.java @@ -8,8 +8,8 @@ import android.content.Context; import android.content.Intent; import android.support.v4.app.NotificationCompat; -import com.philliphsu.clock2.util.AlarmUtils; import com.philliphsu.clock2.model.AlarmsRepository; +import com.philliphsu.clock2.util.AlarmUtils; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static com.philliphsu.clock2.util.DateFormatUtils.formatTime; 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 75a5b68..ae1c915 100644 --- a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java +++ b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmViewHolder.java @@ -119,7 +119,7 @@ public class AlarmViewHolder extends BaseViewHolder implements AlarmCount alarm.setEnabled(checked); if (alarm.isEnabled()) { // TODO: On Moto X, upcoming notification doesn't post immediately - AlarmUtils.scheduleAlarm(getContext(), alarm); + AlarmUtils.scheduleAlarm(getContext(), alarm, true); bindCountdown(true, alarm.ringsIn()); bindDismissButton(alarm); } else { 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 a5fe682..7142208 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java @@ -428,7 +428,7 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi @Override public void scheduleAlarm(Alarm alarm) { - AlarmUtils.scheduleAlarm(this, alarm); + AlarmUtils.scheduleAlarm(this, alarm, true); } @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 7f76140..b6e7a8c 100644 --- a/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/ringtone/RingtoneActivity.java @@ -64,7 +64,7 @@ public class RingtoneActivity extends AppCompatActivity implements RingtoneServi // workaround is to schedule one-time exact alarms, and reschedule each time after handling // an alarm delivery. if (mAlarm.hasRecurrence()) { - AlarmUtils.scheduleAlarm(this, mAlarm); + AlarmUtils.scheduleAlarm(this, mAlarm, false /*show toast?*/); } Intent intent = new Intent(this, RingtoneService.class); 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 bf22203..451c7e7 100644 --- a/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java +++ b/app/src/main/java/com/philliphsu/clock2/util/AlarmUtils.java @@ -36,7 +36,7 @@ public final class AlarmUtils { private AlarmUtils() {} - public static void scheduleAlarm(Context context, Alarm alarm) { + public static void scheduleAlarm(Context context, Alarm alarm, boolean showToast) { Log.d(TAG, "Scheduling alarm " + alarm); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); // If there is already an alarm for this Intent scheduled (with the equality of two @@ -55,17 +55,18 @@ public final class AlarmUtils { 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?*/)); + if (showToast) { + 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(); } - // 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, boolean showToast) { @@ -108,7 +109,7 @@ public final class AlarmUtils { public static void snoozeAlarm(Context c, Alarm a) { a.snooze(AlarmUtils.snoozeDuration(c)); - AlarmUtils.scheduleAlarm(c, a); + AlarmUtils.scheduleAlarm(c, a, true); save(c); }