source: trunk/hiptop/pester/net/sabi/pester/Alarm.java@ 297

Last change on this file since 297 was 297, checked in by Nicholas Riley, 18 years ago

Store deleted alarms creation IDs (timestamps) in on-device datastore, delete/omit on sync from service.

File size: 9.1 KB
RevLine 
[237]1package net.sabi.pester;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.DataInputStream;
6import java.io.DataOutputStream;
[242]7import danger.app.Application;
[277]8import danger.app.IPCMessage;
[244]9import danger.audio.RingToneObject;
[237]10import danger.internal.Date;
[255]11import danger.system.Hardware;
[259]12import danger.text.Collator;
[243]13import danger.util.LocaleUtils;
[242]14import danger.util.StdActiveList;
[237]15import danger.util.StdActiveObject;
[240]16import danger.util.DEBUG;
[243]17import danger.util.format.DateFormat;
18import danger.util.format.StringFormat;
[259]19import java.util.Comparator;
[237]20
[259]21public class Alarm extends StdActiveObject implements Comparator {
[237]22 private static final int VERSION_1 = 1;
23
24 // persisted
25 private String mMessage;
[239]26 private int mType;
[250]27 private int mPeriod;
[237]28 private Date mDate;
[244]29 private RingToneObject mAlert;
[297]30 private int mCreationID;
[237]31
32 // transient
[241]33 private int mState;
[296]34 private int mAbsoluteFireTime; // valid if periodic or snoozed,
35 // and modified since reboot
[242]36 private danger.app.Alarm mAlarm;
[276]37 private int mUID;
[237]38
39 public Alarm() {
[241]40 mState = STATE_INVALID;
[242]41 mAlarm = new danger.app.Alarm(0, Application.getCurrentApp(), this);
[297]42 mCreationID = Hardware.getSystemTime();
[276]43 mUID = 0;
[295]44 mAbsoluteFireTime = -1;
[237]45 }
46
47 public String getMessage() {
48 return mMessage;
49 }
[240]50 public int getPeriod() {
[250]51 return mPeriod;
[237]52 }
53 public boolean getUsesPeriod() {
[239]54 return mType != TYPE_DATE;
[237]55 }
[240]56 public boolean getRepeating() {
57 return mType == TYPE_PERIODIC_REPEATING;
58 }
[237]59 public Date getDate() {
60 return mDate;
61 }
[244]62 public RingToneObject getAlert() {
63 return mAlert;
64 }
[276]65 public int getUID() {
66 return mUID;
67 }
[297]68 public int getCreationID() {
69 return mCreationID;
70 }
[237]71
72 public void setMessage(String message) {
73 mMessage = message;
74 }
[240]75 public void setPeriod(int period, boolean repeating) {
[239]76 mType = repeating ? TYPE_PERIODIC_REPEATING : TYPE_PERIODIC;
[250]77 mPeriod = period;
[237]78 }
79 public void setDate(Date date) {
[239]80 mType = TYPE_DATE;
[237]81 mDate = date;
82 }
[244]83 public void setAlert(RingToneObject alert) {
84 mAlert = alert.isValid() ? alert : null;
85 }
[276]86 public void setUID(int uid) {
87 mUID = uid;
88 }
[287]89 public void snoozeForMinutes(int minutes) {
90 mDate = new Date();
91 mDate.addMinutes(minutes);
[295]92 mAbsoluteFireTime = Hardware.getAbsoluteTime() + (minutes * 60);
[287]93 resume();
94 }
[237]95
96 public byte[] toByteArray() {
97 try {
98 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
99 DataOutputStream dataStream = new DataOutputStream(byteStream);
100
101 dataStream.writeByte(VERSION_1);
102 dataStream.writeUTF(mMessage);
[250]103 dataStream.writeInt(mPeriod);
[237]104 dataStream.writeInt(mDate.getUnixTimeGMT());
[239]105 dataStream.writeInt(mType);
[253]106 dataStream.writeInt(mAlert == null ? 0 : mAlert.getID());
[297]107 dataStream.writeInt(mCreationID);
[237]108 dataStream.flush();
109 return byteStream.toByteArray();
110 } catch (Exception e) {
111 // XXX do something
[297]112 DEBUG.p("failed to write alarm: " + e);
[237]113 }
114 return null;
115 }
116
117 public void fromByteArray(byte[] data) {
118 try {
119 ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
120 DataInputStream dataStream = new DataInputStream(byteStream);
121
122 byte version = dataStream.readByte();
123 if (version != VERSION_1) {
124 // XXX barf
125 }
126 mMessage = dataStream.readUTF();
[250]127 mPeriod = dataStream.readInt();
[253]128 mDate = new Date(dataStream.readInt());
[239]129 mType = dataStream.readInt();
[253]130 int alertID = dataStream.readInt();
131 mAlert = (alertID == 0 ? null : new RingToneObject(alertID));
[297]132 mCreationID = dataStream.readInt();
[295]133 mAbsoluteFireTime = -1; // just in case
[237]134 } catch (Exception e) {
[297]135 // XXX do something, prevernt alarm from being activated
136 DEBUG.p("failed to read alarm: " + e);
[237]137 }
138 }
139
140 void beginEditing() {
[241]141 mState = STATE_EDITING;
[242]142 mAlarm.deactivate();
[237]143 }
144
[249]145 public static int secondsFromNow(Date date) {
146 return date.getDangerTimeGMT() - new Date().getDangerTimeGMT();
147 }
148
149 protected int remainingInterval() {
150 return secondsFromNow(mDate);
151 }
152
[243]153 void resume() {
154 mState = STATE_SCHEDULED;
[249]155 int interval = remainingInterval();
156 if (interval < 0) interval = 0;
157 mAlarm.resetWake(interval);
158 DEBUG.p("resetWake: " + description() + " - " + interval + "s left");
[243]159 update();
160 }
161
[255]162 void timeChanged() {
163 if (mState != STATE_SCHEDULED)
164 return;
[296]165 if (mAbsoluteFireTime != -1) {
[255]166 int realSecondsLeft = mAbsoluteFireTime - Hardware.getAbsoluteTime();
167 mDate = new Date();
168 mDate.addSeconds(realSecondsLeft < 0 ? 0 : realSecondsLeft);
169 // no need to call resume(): alarm uses absolute time
[277]170 update();
[255]171 } else {
172 resume();
173 }
174 }
175
[243]176 void schedule() {
177 if (getUsesPeriod()) {
178 mDate = new Date();
[250]179 mDate.addSeconds(mPeriod);
[255]180 mAbsoluteFireTime = Hardware.getAbsoluteTime() + mPeriod;
[243]181 }
182 resume();
183 }
184
[242]185 void dismiss() {
186 if (getRepeating())
187 schedule();
188 else
189 ((StdActiveList)getDelegate()).removeItem(this);
190 }
191
192 void cancel() {
193 mState = STATE_INVALID;
194 mAlarm.deactivate();
195 }
196
[277]197 // XXX not sure if this is featureful enough to be worthwhile
198 public IPCMessage getCalendarIPCMessage() {
199 if (mState != STATE_SCHEDULED)
200 return null;
201 IPCMessage message = new IPCMessage();
202 message.addItem("action", "new");
203 message.addItem("title", getMessage());
204 message.addItem("start", mDate.getUnixTimeGMT());
205 return message;
206 }
207
[243]208 public String description() {
209 StringBuffer sb = new StringBuffer();
[296]210 if (mUID != 0) sb.append('(').append(mUID).append(") ");
[243]211 switch (mState) {
[296]212 case STATE_INVALID: sb.append("inv "); break;
213 case STATE_EDITING: sb.append("edi "); break;
214 case STATE_SCHEDULED: sb.append("sch "); break;
[243]215 }
[240]216 switch (mType) {
[296]217 case TYPE_PERIODIC_REPEATING:
218 sb.append("r-");
[240]219 case TYPE_PERIODIC:
[296]220 sb.append("per(").append(mPeriod).append("s)");
221 if (mState != STATE_SCHEDULED)
222 break;
[240]223 case TYPE_DATE:
[296]224 sb.append("(").append(getDateTimeString()).append(")"); break;
[240]225 }
[296]226 sb.append(": ");
[243]227 sb.append('"').append(mMessage).append('"');
228 return sb.toString();
[237]229 }
[243]230
[249]231 public static String dateTimeString(Date date, boolean relative) {
[243]232 if (date == null)
233 return null;
234 String layout = LocaleUtils.getDateTimePattern();
[247]235 String dateFormat = LocaleUtils.getMediumDateFormat();
[250]236 String dateString;
237 String timeFormat;
[249]238 if (relative) {
[250]239 timeFormat = LocaleUtils.getShortTimeFormat();
[249]240 int daysFromToday = date.getDaysBetween(new Date());
241 if (daysFromToday == 0)
242 dateString = "Today"; // XXX localize
243 else if (daysFromToday == 1)
244 dateString = "Tomorrow"; // XXX localize
[295]245 else if (daysFromToday > 1 && daysFromToday < 7)
[249]246 dateString = date.getDayString();
[250]247 else
248 dateString = DateFormat.withFormat(dateFormat, date);
249 } else {
250 dateString = DateFormat.withFormat(dateFormat, date);
251 timeFormat = LocaleUtils.getMediumTimeFormat();
[249]252 }
[250]253 String timeString = DateFormat.withFormat(timeFormat, date);
[249]254 return StringFormat.withFormat(layout, timeString, dateString);
[243]255 }
256
[249]257 protected static void addUnit(StringBuffer sb, int n, String unit) {
258 if (n == 0) return;
259 if (sb.length() > 0) sb.append(", ");
260 if (n == 1) sb.append("one ").append(unit);
261 else sb.append(n).append(' ').append(unit).append("s");
262 }
263
264 public static String intervalString(int i) { // XXX localize
265 if (i < 0)
266 return "expired";
267 StringBuffer sb = new StringBuffer();
268 if (i < 60) {
269 addUnit(sb, i, "second");
270 } else {
271 i /= 60; // minutes
272 if (i < 24 * 60) {
273 addUnit(sb, i / 60, "hour");
274 addUnit(sb, i % 60, "minute");
275 } else {
276 i /= 60; // hours
277 if (i < 365) {
278 addUnit(sb, i / 24, "day");
279 addUnit(sb, i % 24, "hour");
280 } else {
281 i /= 24; // days
282 addUnit(sb, i / 365, "year"); // XXX actually 365.242199
283 addUnit(sb, i % 365, "day");
284 }
285 }
286 }
287 return sb.toString();
288 }
289
[273]290 public int getSecondsUntilNextIntervalStringUpdate() {
291 int i = remainingInterval();
[275]292 if (i <= 1)
[273]293 return 0;
294 if (i <= 60)
295 return 1;
[275]296 if (i <= 24 * 60 * 60) {
297 i %= 60;
[280]298 return (i == 0 ? 60 : i + 1);
[275]299 }
300 i %= 3600;
[280]301 return (i == 0 ? 3600 : i + 1);
[273]302 }
303
[249]304 public String getDateTimeString(boolean relative) {
305 return dateTimeString(mDate, relative);
306 }
307
[243]308 public String getDateTimeString() {
[249]309 return getDateTimeString(false);
[243]310 }
[249]311
312 public String getIntervalString() {
313 return intervalString(remainingInterval());
314 }
[243]315
[237]316 public String toString() {
[249]317 return getDateTimeString(true) + " - " + mMessage;
[237]318 }
319
[259]320 public int compare(Object arg0, Object arg1) {
321 Alarm alarm0 = (Alarm)arg0, alarm1 = (Alarm)arg1;
322 int result = alarm0.getDate().compareTo(alarm1.getDate());
323 if (result != 0) return result;
324 result = Collator.getInstance().compare(alarm0.getMessage(),
325 alarm1.getMessage());
326 if (result != 0) return result;
327 return alarm0.hashCode() - alarm1.hashCode();
328 }
329
[239]330 public static final int TYPE_PERIODIC = 0;
331 public static final int TYPE_PERIODIC_REPEATING = 1;
332 public static final int TYPE_DATE = 2;
333
[241]334 public static final int STATE_INVALID = 0;
335 public static final int STATE_EDITING = 1;
336 public static final int STATE_SCHEDULED = 2;
[237]337}
Note: See TracBrowser for help on using the repository browser.