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

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

If you're editing an alarm, focus on the time/interval edit field
rather than the message. Relayout set alarm display when time/date
format changes (yay positionDynamically!). Attempt to fix tooltip for

1-minute alarm refreshing/flashing without changing (needs more

testing). Build against 3.0 libraries by default.

File size: 8.5 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;
[255]33 private int mAbsoluteFireTime; // only valid if periodic
[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;
[237]41 }
42
43 public String getMessage() {
44 return mMessage;
45 }
[240]46 public int getPeriod() {
[250]47 return mPeriod;
[237]48 }
49 public boolean getUsesPeriod() {
[239]50 return mType != TYPE_DATE;
[237]51 }
[240]52 public boolean getRepeating() {
53 return mType == TYPE_PERIODIC_REPEATING;
54 }
[237]55 public Date getDate() {
56 return mDate;
57 }
[244]58 public RingToneObject getAlert() {
59 return mAlert;
60 }
[276]61 public int getUID() {
62 return mUID;
63 }
[237]64
65 public void setMessage(String message) {
66 mMessage = message;
67 }
[240]68 public void setPeriod(int period, boolean repeating) {
[239]69 mType = repeating ? TYPE_PERIODIC_REPEATING : TYPE_PERIODIC;
[250]70 mPeriod = period;
[237]71 }
72 public void setDate(Date date) {
[239]73 mType = TYPE_DATE;
[237]74 mDate = date;
75 }
[244]76 public void setAlert(RingToneObject alert) {
77 mAlert = alert.isValid() ? alert : null;
78 }
[276]79 public void setUID(int uid) {
80 mUID = uid;
81 }
[237]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);
[250]90 dataStream.writeInt(mPeriod);
[237]91 dataStream.writeInt(mDate.getUnixTimeGMT());
[239]92 dataStream.writeInt(mType);
[253]93 dataStream.writeInt(mAlert == null ? 0 : mAlert.getID());
[237]94 dataStream.flush();
95 return byteStream.toByteArray();
96 } catch (Exception e) {
97 // XXX do something
[253]98 DEBUG.p("failed to write alarm:" + e);
[237]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();
[250]113 mPeriod = dataStream.readInt();
[253]114 mDate = new Date(dataStream.readInt());
[239]115 mType = dataStream.readInt();
[253]116 int alertID = dataStream.readInt();
117 mAlert = (alertID == 0 ? null : new RingToneObject(alertID));
[237]118 } catch (Exception e) {
119 // XXX do something
[253]120 DEBUG.p("failed to read alarm:" + e);
[237]121 }
122 }
123
124 void beginEditing() {
[241]125 mState = STATE_EDITING;
[242]126 mAlarm.deactivate();
[237]127 }
128
[249]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
[243]137 void resume() {
138 mState = STATE_SCHEDULED;
[249]139 int interval = remainingInterval();
140 if (interval < 0) interval = 0;
141 mAlarm.resetWake(interval);
142 DEBUG.p("resetWake: " + description() + " - " + interval + "s left");
[243]143 update();
144 }
145
[255]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
[277]154 update();
[255]155 } else {
156 resume();
157 }
158 }
159
[243]160 void schedule() {
161 if (getUsesPeriod()) {
162 mDate = new Date();
[250]163 mDate.addSeconds(mPeriod);
[255]164 mAbsoluteFireTime = Hardware.getAbsoluteTime() + mPeriod;
[243]165 }
166 resume();
167 }
168
[242]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
[277]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
[243]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 }
[240]199 switch (mType) {
200 case TYPE_PERIODIC:
[250]201 sb.append("periodic (").append(mPeriod).append("s) "); break;
[240]202 case TYPE_PERIODIC_REPEATING:
[250]203 sb.append("repeating periodic (").append(mPeriod).append("s) ");
[243]204 break;
[240]205 case TYPE_DATE:
[243]206 sb.append("date (").append(getDateTimeString()).append(") "); break;
[240]207 }
[243]208 sb.append("alarm: ");
209 sb.append('"').append(mMessage).append('"');
210 return sb.toString();
[237]211 }
[243]212
[249]213 public static String dateTimeString(Date date, boolean relative) {
[243]214 if (date == null)
215 return null;
216 String layout = LocaleUtils.getDateTimePattern();
[247]217 String dateFormat = LocaleUtils.getMediumDateFormat();
[250]218 String dateString;
219 String timeFormat;
[249]220 if (relative) {
[250]221 timeFormat = LocaleUtils.getShortTimeFormat();
[249]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();
[250]229 else
230 dateString = DateFormat.withFormat(dateFormat, date);
231 } else {
232 dateString = DateFormat.withFormat(dateFormat, date);
233 timeFormat = LocaleUtils.getMediumTimeFormat();
[249]234 }
[250]235 String timeString = DateFormat.withFormat(timeFormat, date);
[249]236 return StringFormat.withFormat(layout, timeString, dateString);
[243]237 }
238
[249]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
[273]272 public int getSecondsUntilNextIntervalStringUpdate() {
273 int i = remainingInterval();
[275]274 if (i <= 1)
[273]275 return 0;
276 if (i <= 60)
277 return 1;
[275]278 if (i <= 24 * 60 * 60) {
279 i %= 60;
[280]280 return (i == 0 ? 60 : i + 1);
[275]281 }
282 i %= 3600;
[280]283 return (i == 0 ? 3600 : i + 1);
[273]284 }
285
[249]286 public String getDateTimeString(boolean relative) {
287 return dateTimeString(mDate, relative);
288 }
289
[243]290 public String getDateTimeString() {
[249]291 return getDateTimeString(false);
[243]292 }
[249]293
294 public String getIntervalString() {
295 return intervalString(remainingInterval());
296 }
[243]297
[237]298 public String toString() {
[249]299 return getDateTimeString(true) + " - " + mMessage;
[237]300 }
301
[259]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
[239]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
[241]316 public static final int STATE_INVALID = 0;
317 public static final int STATE_EDITING = 1;
318 public static final int STATE_SCHEDULED = 2;
[237]319}
Note: See TracBrowser for help on using the repository browser.