2 * Copyright (C) 2011 Morphoss Ltd
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com
.morphoss
.acal
.davacal
;
21 import java
.io
.Serializable
;
22 import java
.text
.SimpleDateFormat
;
23 import java
.util
.ArrayList
;
24 import java
.util
.List
;
27 import android
.content
.ContentValues
;
28 import android
.content
.Context
;
29 import android
.graphics
.Color
;
30 import android
.os
.Parcel
;
31 import android
.os
.Parcelable
;
32 import android
.util
.Log
;
34 import com
.morphoss
.acal
.Constants
;
35 import com
.morphoss
.acal
.acaltime
.AcalDateRange
;
36 import com
.morphoss
.acal
.acaltime
.AcalDateTime
;
37 import com
.morphoss
.acal
.acaltime
.AcalDuration
;
38 import com
.morphoss
.acal
.providers
.DavResources
;
39 import com
.morphoss
.acal
.providers
.PendingChanges
;
43 * @author Morphoss Ltd
46 public class AcalEvent
implements Serializable
, Parcelable
, Comparable
<AcalEvent
>{
48 private static final long serialVersionUID
= 1L;
49 public static final String TAG
= "AcalEvent";
50 private AcalDateTime dtstart
;
51 private AcalDateTime dtend
;
52 private String summary
;
53 private String description
;
54 private String location
;
55 private String repetition
;
57 public final boolean hasAlarms
;
58 public final int resourceId
;
59 private List
<AcalAlarm
> alarmList
= new ArrayList
<AcalAlarm
>();
60 private final String originalBlob
;
61 private int collectionId
;
62 public final boolean isPending
;
63 private boolean alarmEnabled
;
64 private int action
= ACTION_CREATE
;
65 private boolean dirty
;
66 private final boolean[] dirtyFlags
= new boolean[EVENT_FIELD
.values().length
];
68 public static final int ACTION_CREATE
= 0;
69 public static final int ACTION_MODIFY_SINGLE
= 1;
70 public static final int ACTION_MODIFY_ALL
= 2;
71 public static final int ACTION_MODIFY_ALL_FUTURE
= 3;
72 public static final int ACTION_DELETE_SINGLE
= 4;
73 public static final int ACTION_DELETE_ALL
= 5;
74 public static final int ACTION_DELETE_ALL_FUTURE
= 6;
76 public static enum EVENT_FIELD
{
89 public static final Parcelable
.Creator
<AcalEvent
> CREATOR
= new Parcelable
.Creator
<AcalEvent
>() {
90 public AcalEvent
createFromParcel(Parcel in
) {
91 return getInstanceFromParcel(in
);
94 public AcalEvent
[] newArray(int size
) {
95 return new AcalEvent
[size
];
99 public static AcalEvent
getInstanceFromParcel(Parcel in
) {
100 AcalEvent event
= null;
101 event
= new AcalEvent(in
);
107 * Construct an AcalEvent from a row from the database. We optionally allow overwriting
108 * the start date/time for the event.
111 * @return The AcalEvent we read, or null.
113 public static AcalEvent
fromDatabase( Context context
, int resourceId
, AcalDateTime dtStart
) {
114 ContentValues resourceValues
= DavResources
.getRow(resourceId
, context
.getContentResolver());
115 ContentValues pendingValues
= PendingChanges
.getByResource(resourceId
, context
.getContentResolver());
116 if ( pendingValues
!= null ) {
117 String newData
= pendingValues
.getAsString(PendingChanges
.NEW_DATA
);
118 if ( newData
== null ) newData
= "";
119 resourceValues
.put(DavResources
.RESOURCE_DATA
, newData
);
120 resourceValues
.put(DavResources
.IS_PENDING
, 1);
121 if ( Constants
.LOG_DEBUG
)
122 Log
.i(TAG
,"Using pending change record for AcalEvent fromDatabase.");
127 vc
= VComponent
.createComponentFromResource( resourceValues
,
128 AcalCollection
.fromDatabase(context
, resourceValues
.getAsLong(DavResources
.COLLECTION_ID
)));
130 catch (VComponentCreationException e
) {
131 Log
.e(TAG
,Log
.getStackTraceString(e
));
134 if ( ! (vc
instanceof VCalendar
) ) {
135 Log
.w(TAG
,"Trying to build AcalEvent but resource "+resourceId
+" is not a VCalendar it is a "+vc
.name
);
138 Masterable event
= ((VCalendar
) vc
).getMasterChild();
139 if ( ! (event
instanceof VEvent
) ) {
140 Log
.w(TAG
,"Trying to build AcalEvent but resource contained in "+resourceId
+" is not a VEvent");
144 // This bit of mucking around preserves any timezone we got from the database
145 AcalDateTime eventStart
= event
.getStart();
146 AcalDateTime eventEnd
= event
.getEnd();
147 if ( dtStart
!= null ) {
148 if ( ! dtStart
.equals(eventStart
) ) {
149 long delta
= dtStart
.getEpoch() - eventStart
.getEpoch();
150 eventStart
.addSeconds(delta
);
151 eventEnd
.addSeconds(delta
);
154 return new AcalEvent( (VEvent
) event
, eventStart
, eventEnd
, false );
158 public int describeContents() {
162 public void writeToParcel(Parcel out
, int flags
) {
163 getStart().writeToParcel( out
, flags
);
164 getEnd().writeToParcel(out
, 0);
165 out
.writeString(getSummary());
166 out
.writeString(getLocation());
167 out
.writeString(getDescription());
168 out
.writeString(getRepetition());
169 out
.writeInt(getColour());
170 out
.writeByte((byte) (hasAlarms() ?
'T' : 'F'));
171 out
.writeByte((byte) (alarmEnabled ?
'T' : 'F'));
172 out
.writeInt(getResourceId());
173 out
.writeString(originalBlob
);
174 out
.writeInt(collectionId
);
175 out
.writeTypedList(alarmList
);
176 out
.writeByte((byte) (isPending ?
'T' : 'F'));
179 public AcalEvent(Parcel in
) {
180 this.dtstart
= AcalDateTime
.unwrapParcel(in
);
181 this.dtend
= AcalDateTime
.unwrapParcel(in
);
182 this.summary
= in
.readString();
183 this.location
= in
.readString();
184 this.description
= in
.readString();
185 this.repetition
= in
.readString();
186 this.colour
= in
.readInt();
187 this.hasAlarms
= in
.readByte() == 'T';
188 this.alarmEnabled
= in
.readByte() == 'T';
189 this.resourceId
= in
.readInt();
190 this.originalBlob
= in
.readString();
191 this.collectionId
= in
.readInt();
192 in
.readTypedList(this.alarmList
, AcalAlarm
.CREATOR
);
193 this.isPending
= in
.readByte() == 'T';
197 public int compareTo(AcalEvent other
) {
198 if ( getLocalStart().before(other
.getLocalStart())) return -1;
199 if ( getLocalStart().after(other
.getLocalStart())) return 1;
200 if ( getLocalEnd().before(other
.getLocalEnd())) return -1;
201 if ( getLocalEnd().after(other
.getLocalEnd())) return 1;
205 public AcalEvent(VEvent event
, AcalDateTime startDate
, AcalDateTime endDate
, boolean isPending
) {
206 this.resourceId
= event
.getResourceId();
207 this.dtstart
= startDate
;
208 this.dtend
= endDate
;
209 this.summary
= safeEventPropertyValue(event
, PropertyName
.SUMMARY
);
210 this.description
= safeEventPropertyValue(event
, PropertyName
.DESCRIPTION
);
211 this.location
= safeEventPropertyValue(event
, PropertyName
.LOCATION
);
212 this.originalBlob
= event
.getTopParent().getOriginalBlob();
213 String repeatRule
= safeEventPropertyValue(event
,PropertyName
.RRULE
);
214 String repeatInstances
= safeEventPropertyValue(event
,PropertyName
.RDATE
);
215 this.repetition
= repeatRule
+ (repeatInstances
.equals("")?
"":"\n"+repeatInstances
);
217 List
<AcalAlarm
> theseAlarms
= new ArrayList
<AcalAlarm
>();
218 for( VComponent child
: event
.getChildren() ) {
219 if ( child
instanceof VAlarm
) {
220 theseAlarms
.add( new AcalAlarm((VAlarm
) child
, (Masterable
) event
,
221 (dtstart
== null ?
null : dtstart
.clone()),
222 (dtend
== null ?
null : dtend
.clone()) ) );
226 alarmList
.addAll(theseAlarms
);
227 hasAlarms
= alarmList
.size() > 0;
229 int aColor
= Color
.BLUE
;
230 boolean alarmsForCollection
= true;
232 aColor
= event
.getCollectionColour();
233 alarmsForCollection
= event
.getAlarmEnabled();
234 } catch (Exception e
) {
235 Log
.e(TAG
,"Error Creating AcalEvent - "+e
.getMessage());
236 Log
.e(TAG
,Log
.getStackTraceString(e
));
239 alarmEnabled
= alarmsForCollection
;
241 int fromCollectionId
;
243 fromCollectionId
= event
.getCollectionId();
245 catch( NullPointerException e
) {
246 fromCollectionId
= -1;
248 collectionId
= fromCollectionId
;
249 this.isPending
= isPending
;
253 private String
safeEventPropertyValue(VComponent event
, PropertyName summary2
) {
254 String propertyValue
= null;
256 propertyValue
= event
.getProperty(summary2
).getValue();
258 catch (Exception e
) {
260 if (propertyValue
== null) propertyValue
= "";
261 return propertyValue
;
267 public List
<AcalAlarm
> getAlarms() {
268 return this.alarmList
;
271 public int getColour() {
275 public boolean getAlarmEnabled() {
276 return this.alarmEnabled
;
279 public String
getDescription() {
280 return this.description
;
283 public AcalDateTime
getEnd() {
284 return this.dtend
.clone();
287 public AcalDuration
getDuration() {
288 return dtstart
.getDurationTo(dtend
);
291 public String
getLocation() {
292 return this.location
;
295 public String
getRepetition() {
296 return this.repetition
;
299 public void setRepetition(String newRepetition
) {
300 this.repetition
= newRepetition
;
304 @SuppressWarnings("unchecked")
305 public void setField(EVENT_FIELD field
, Object val
) {
308 this.dtstart
= ((AcalDateTime
) val
).clone();
311 this.dtend
= ((AcalDateTime
) val
).clone();
314 this.summary
= (String
) val
;
317 this.description
= (String
) val
;
320 this.location
= (String
) val
;
323 this.repetition
= (String
) val
;
326 this.collectionId
= (Integer
) val
;
329 this.colour
= (Integer
) val
;
332 this.alarmList
= (List
<AcalAlarm
>) val
;
335 throw new IllegalStateException("The "+field
.toString()+" is not modifiable.");
337 dirtyFlags
[field
.ordinal()] = true;
341 public AcalDateTime
getStart() {
342 return this.dtstart
.clone();
345 public AcalDateTime
getLocalStart() {
346 return this.getStart().applyLocalTimeZone();
349 public AcalDateTime
getLocalEnd() {
350 AcalDateTime finish
= getEnd();
351 if ( finish
!= null ) finish
.applyLocalTimeZone();
355 public String
getTimeText(AcalDateTime viewDateStart
, AcalDateTime viewDateEnd
, boolean as24HourTime
) {
356 AcalDateTime start
= this.getLocalStart();
357 AcalDateTime finish
= this.getLocalEnd();
358 String timeText
= "";
359 String timeFormatString
= (as24HourTime ?
"HH:mm" : "hh:mmaa");
360 SimpleDateFormat timeFormatter
= new SimpleDateFormat(timeFormatString
);
362 if ( start
.before(viewDateStart
) || (finish
!= null && finish
.after(viewDateEnd
)) ){
363 if ( start
.isDate() ) {
364 timeText
= AcalDateTime
.fmtDayMonthYear(start
)+ ", all day";
367 SimpleDateFormat startFormatter
= timeFormatter
;
368 SimpleDateFormat finishFormatter
= timeFormatter
;
370 if ( start
.before(viewDateStart
) || start
.after(viewDateEnd
) ) {
371 startFormatter
= new SimpleDateFormat("MMM d, "+timeFormatString
);
372 if ( (finish
.getYear() > start
.getYear()) || (finish
.getYearDay() > start
.getYearDay()) )
373 finishFormatter
= new SimpleDateFormat("MMM d, "+timeFormatString
);
376 timeText
= (startFormatter
.format(start
.toJavaDate())+" - "
377 + (finish
== null ?
"null" : finishFormatter
.format(finish
.toJavaDate())));
380 else if ( start
.isDate()) {
381 timeText
= "All Day";
384 timeText
= (timeFormatter
.format(start
.toJavaDate())+" - "
385 + (finish
== null ?
"null" : timeFormatter
.format(finish
.toJavaDate())));
390 public boolean overlaps( AcalDateRange range
) {
391 return range
.overlaps(dtstart
, dtend
);
395 * Get the value from the SUMMARY field.
398 public String
getSummary() {
403 * Ascertain whether the event has alarms.
404 * @return true, if the event has alarms
406 public boolean hasAlarms() {
407 return this.hasAlarms
;
410 public int getResourceId() {
411 return this.resourceId
;
414 public int getCollectionId() {
415 return this.collectionId
;
418 public String
getOriginalBlob() {
419 return this.originalBlob
;
422 public void setAction(int action
) {
423 this.action
= action
;
426 public boolean isDirty() { return dirty
; }
427 public int getAction() {
431 public boolean isModifyAction() {
432 return (this.action
== ACTION_MODIFY_SINGLE
) ||
433 (this.action
== ACTION_MODIFY_ALL
) ||
434 (this.action
== ACTION_MODIFY_ALL_FUTURE
);
438 public AcalEvent(Map
<EVENT_FIELD
, Object
> defaults
) {
439 this.dtstart
= (AcalDateTime
) defaults
.get(EVENT_FIELD
.startDate
);
440 this.dtend
= (AcalDateTime
) defaults
.get(EVENT_FIELD
.endDate
);
441 this.summary
= (String
) defaults
.get(EVENT_FIELD
.summary
);
442 this.description
= (String
) defaults
.get(EVENT_FIELD
.description
);
443 this.location
= (String
) defaults
.get(EVENT_FIELD
.location
);
444 this.repetition
= (String
) defaults
.get(EVENT_FIELD
.repeatRule
);
445 this.colour
= (Integer
) defaults
.get(EVENT_FIELD
.colour
);
446 @SuppressWarnings("unchecked")
447 List
<AcalAlarm
> alarmList
= (List
<AcalAlarm
>) defaults
.get(EVENT_FIELD
.alarmList
);
448 this.hasAlarms
= ! alarmList
.isEmpty();
449 this.alarmList
.addAll(alarmList
);
452 if ( (Integer
) defaults
.get(EVENT_FIELD
.resourceId
) != null )
453 theId
= (Integer
) defaults
.get(EVENT_FIELD
.resourceId
);
454 this.resourceId
= theId
;
457 if ( (Integer
) defaults
.get(EVENT_FIELD
.collectionId
) != null )
458 theId
= (Integer
) defaults
.get(EVENT_FIELD
.collectionId
);
459 this.collectionId
= theId
;
461 this.originalBlob
= null;
462 this.isPending
= false;
463 this.alarmEnabled
= true;
468 public AcalEvent(VEvent vEvent
, AcalDateTime dtstart
, AcalDuration duration
, boolean isPending
) {
469 this(vEvent
, dtstart
, dtstart
.clone().addDuration(duration
), isPending
);