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

Last change on this file since 299 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
Line 
1package net.sabi.pester;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.DataInputStream;
6import java.io.DataOutputStream;
7import danger.app.Application;
8import danger.app.IPCMessage;
9import danger.audio.RingToneObject;
10import danger.internal.Date;
11import danger.system.Hardware;
12import danger.text.Collator;
13import danger.util.LocaleUtils;
14import danger.util.StdActiveList;
15import danger.util.StdActiveObject;
16import danger.util.DEBUG;
17import danger.util.format.DateFormat;
18import danger.util.format.StringFormat;
19import java.util.Comparator;
20
21public class Alarm extends StdActiveObject implements Comparator {
22 private static final int VERSION_1 = 1;
23
24 // persisted
25 private String mMessage;
26 private int mType;
27 private int mPeriod;
28 private Date mDate;
29 private RingToneObject mAlert;
30 private int mCreationID;
31
32 // transient
33 private int mState;
34 private int mAbsoluteFireTime; // valid if periodic or snoozed,
35 // and modified since reboot
36 private danger.app.Alarm mAlarm;
37 private int mUID;
38
39 public Alarm() {
40 mState = STATE_INVALID;
41 mAlarm = new danger.app.Alarm(0, Application.getCurrentApp(), this);
42 mCreationID = Hardware.getSystemTime();
43 mUID = 0;
44 mAbsoluteFireTime = -1;
45 }
46
47 public String getMessage() {
48 return mMessage;
49 }
50 public int getPeriod() {
51 return mPeriod;
52 }
53 public boolean getUsesPeriod() {
54 return mType != TYPE_DATE;
55 }
56 public boolean getRepeating() {
57 return mType == TYPE_PERIODIC_REPEATING;
58 }
59 public Date getDate() {
60 return mDate;
61 }
62 public RingToneObject getAlert() {
63 return mAlert;
64 }
65 public int getUID() {
66 return mUID;
67 }
68 public int getCreationID() {
69 return mCreationID;
70 }
71
72 public void setMessage(String message) {
73 mMessage = message;
74 }
75 public void setPeriod(int period, boolean repeating) {
76 mType = repeating ? TYPE_PERIODIC_REPEATING : TYPE_PERIODIC;
77 mPeriod = period;
78 }
79 public void setDate(Date date) {
80 mType = TYPE_DATE;
81 mDate = date;
82 }
83 public void setAlert(RingToneObject alert) {
84 mAlert = alert.isValid() ? alert : null;
85 }
86 public void setUID(int uid) {
87 mUID = uid;
88 }
89 public void snoozeForMinutes(int minutes) {
90 mDate = new Date();
91 mDate.addMinutes(minutes);
92 mAbsoluteFireTime = Hardware.getAbsoluteTime() + (minutes * 60);
93 resume();
94 }
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);
103 dataStream.writeInt(mPeriod);
104 dataStream.writeInt(mDate.getUnixTimeGMT());
105 dataStream.writeInt(mType);
106 dataStream.writeInt(mAlert == null ? 0 : mAlert.getID());
107 dataStream.writeInt(mCreationID);
108 dataStream.flush();
109 return byteStream.toByteArray();
110 } catch (Exception e) {
111 // XXX do something
112 DEBUG.p("failed to write alarm: " + e);
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();
127 mPeriod = dataStream.readInt();
128 mDate = new Date(dataStream.readInt());
129 mType = dataStream.readInt();
130 int alertID = dataStream.readInt();
131 mAlert = (alertID == 0 ? null : new RingToneObject(alertID));
132 mCreationID = dataStream.readInt();
133 mAbsoluteFireTime = -1; // just in case
134 } catch (Exception e) {
135 // XXX do something, prevernt alarm from being activated
136 DEBUG.p("failed to read alarm: " + e);
137 }
138 }
139
140 void beginEditing() {
141 mState = STATE_EDITING;
142 mAlarm.deactivate();
143 }
144
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
153 void resume() {
154 mState = STATE_SCHEDULED;
155 int interval = remainingInterval();
156 if (interval < 0) interval = 0;
157 mAlarm.resetWake(interval);
158 DEBUG.p("resetWake: " + description() + " - " + interval + "s left");
159 update();
160 }
161
162 void timeChanged() {
163 if (mState != STATE_SCHEDULED)
164 return;
165 if (mAbsoluteFireTime != -1) {
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
170 update();
171 } else {
172 resume();
173 }
174 }
175
176 void schedule() {
177 if (getUsesPeriod()) {
178 mDate = new Date();
179 mDate.addSeconds(mPeriod);
180 mAbsoluteFireTime = Hardware.getAbsoluteTime() + mPeriod;
181 }
182 resume();
183 }
184
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
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
208 public String description() {
209 StringBuffer sb = new StringBuffer();
210 if (mUID != 0) sb.append('(').append(mUID).append(") ");
211 switch (mState) {
212 case STATE_INVALID: sb.append("inv "); break;
213 case STATE_EDITING: sb.append("edi "); break;
214 case STATE_SCHEDULED: sb.append("sch "); break;
215 }
216 switch (mType) {
217 case TYPE_PERIODIC_REPEATING:
218 sb.append("r-");
219 case TYPE_PERIODIC:
220 sb.append("per(").append(mPeriod).append("s)");
221 if (mState != STATE_SCHEDULED)
222 break;
223 case TYPE_DATE:
224 sb.append("(").append(getDateTimeString()).append(")"); break;
225 }
226 sb.append(": ");
227 sb.append('"').append(mMessage).append('"');
228 return sb.toString();
229 }
230
231 public static String dateTimeString(Date date, boolean relative) {
232 if (date == null)
233 return null;
234 String layout = LocaleUtils.getDateTimePattern();
235 String dateFormat = LocaleUtils.getMediumDateFormat();
236 String dateString;
237 String timeFormat;
238 if (relative) {
239 timeFormat = LocaleUtils.getShortTimeFormat();
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
245 else if (daysFromToday > 1 && daysFromToday < 7)
246 dateString = date.getDayString();
247 else
248 dateString = DateFormat.withFormat(dateFormat, date);
249 } else {
250 dateString = DateFormat.withFormat(dateFormat, date);
251 timeFormat = LocaleUtils.getMediumTimeFormat();
252 }
253 String timeString = DateFormat.withFormat(timeFormat, date);
254 return StringFormat.withFormat(layout, timeString, dateString);
255 }
256
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
290 public int getSecondsUntilNextIntervalStringUpdate() {
291 int i = remainingInterval();
292 if (i <= 1)
293 return 0;
294 if (i <= 60)
295 return 1;
296 if (i <= 24 * 60 * 60) {
297 i %= 60;
298 return (i == 0 ? 60 : i + 1);
299 }
300 i %= 3600;
301 return (i == 0 ? 3600 : i + 1);
302 }
303
304 public String getDateTimeString(boolean relative) {
305 return dateTimeString(mDate, relative);
306 }
307
308 public String getDateTimeString() {
309 return getDateTimeString(false);
310 }
311
312 public String getIntervalString() {
313 return intervalString(remainingInterval());
314 }
315
316 public String toString() {
317 return getDateTimeString(true) + " - " + mMessage;
318 }
319
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
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
334 public static final int STATE_INVALID = 0;
335 public static final int STATE_EDITING = 1;
336 public static final int STATE_SCHEDULED = 2;
337}
Note: See TracBrowser for help on using the repository browser.