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

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

Properly update display of periodic alarms when time changes. Add calendar IPC message creation (not hooked up yet, maybe never). Be slightly more efficient about tooltip drawing.

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