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

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

Better alarm debug description, datastore debugging; use absolute fire time for snoozed alarms too; replace the buggy alarm stack and misaligned sleep message with a painfully constructed alert.

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