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

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

Move discard alert text to resource. Fix listener for
EVENT_TIME_CHANGED I broke a few days ago. Only use
Alarm.mAbsoluteFireTime to measure real time if the alarm was
scheduled/rescheduled (through user action, not restoration from a
datastore) since boot. Don't display dates in the past as if they're
in the following week.

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