CalendarEventModel.java revision c27e56f7150b77da3d8ae22e48a4b3699bd2e19c
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.calendar;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.SharedPreferences;
22import android.provider.Calendar.Attendees;
23import android.provider.Calendar.Calendars;
24import android.provider.Calendar.Events;
25import android.provider.Calendar.Reminders;
26import android.text.TextUtils;
27import android.util.Log;
28
29import java.io.Serializable;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collections;
33import java.util.LinkedHashMap;
34import java.util.TimeZone;
35
36/**
37 * Stores all the information needed to fill out an entry in the events table.
38 * This is a convenient way for storing information needed by the UI to write to
39 * the events table. Only fields that are important to the UI are included.
40 */
41public class CalendarEventModel implements Serializable {
42    private static final String TAG = "CalendarEventModel";
43
44    public static class Attendee implements Serializable {
45        @Override
46        public int hashCode() {
47            return (mEmail == null) ? 0 : mEmail.hashCode();
48        }
49
50        @Override
51        public boolean equals(Object obj) {
52            if (this == obj) {
53                return true;
54            }
55            if (!(obj instanceof Attendee)) {
56                return false;
57            }
58            Attendee other = (Attendee) obj;
59            if (!TextUtils.equals(mEmail, other.mEmail)) {
60                return false;
61            }
62            return true;
63        }
64
65        public String mName;
66        public String mEmail;
67        public int mStatus;
68
69        public Attendee(String name, String email) {
70            mName = name;
71            mEmail = email;
72            mStatus = Attendees.ATTENDEE_STATUS_NONE;
73        }
74    }
75
76    /**
77     * A single reminder entry.
78     *
79     * Instances of the class are immutable.
80     */
81    public static class ReminderEntry implements Comparable<ReminderEntry>, Serializable {
82        private final int mMinutes;
83        private final int mMethod;
84
85        /**
86         * Returns a new ReminderEntry, with the specified minutes and method.
87         *
88         * @param minutes Number of minutes before the start of the event that the alert will fire.
89         * @param method Type of alert ({@link Reminders#METHOD_ALERT}, etc).
90         */
91        public static ReminderEntry valueOf(int minutes, int method) {
92            // TODO: cache common instances
93            return new ReminderEntry(minutes, method);
94        }
95
96        /**
97         * Returns a ReminderEntry, with the specified number of minutes and a default alert method.
98         *
99         * @param minutes Number of minutes before the start of the event that the alert will fire.
100         */
101       public static ReminderEntry valueOf(int minutes) {
102            return valueOf(minutes, Reminders.METHOD_DEFAULT);
103        }
104
105        /**
106         * Constructs a new ReminderEntry.
107         *
108         * @param minutes Number of minutes before the start of the event that the alert will fire.
109         * @param method Type of alert ({@link Reminders#METHOD_ALERT}, etc).
110         */
111        private ReminderEntry(int minutes, int method) {
112            // TODO: error-check args
113            mMinutes = minutes;
114            mMethod = method;
115        }
116
117        @Override
118        public int hashCode() {
119            return mMinutes * 10 + mMethod;
120        }
121
122        @Override
123        public boolean equals(Object obj) {
124            if (this == obj)
125                return true;
126            if (!(obj instanceof ReminderEntry)) {
127                return false;
128            }
129
130            ReminderEntry re = (ReminderEntry) obj;
131
132            if (re.mMinutes != mMinutes)
133                return false;
134
135            // treat ALERT and DEFAULT as equivalent (TODO: is this necessary?)
136            return re.mMethod == mMethod ||
137                (re.mMethod == Reminders.METHOD_DEFAULT && mMethod == Reminders.METHOD_ALERT) ||
138                (re.mMethod == Reminders.METHOD_ALERT && mMethod == Reminders.METHOD_DEFAULT);
139        }
140
141        @Override
142        public String toString() {
143            return "ReminderEntry min=" + mMinutes + " meth=" + mMethod;
144        }
145
146        /**
147         * Comparison function for a sort ordered descending by minutes.
148         */
149        public int compareTo(ReminderEntry re) {
150            if (re.mMinutes != mMinutes) {
151                return re.mMinutes - mMinutes;
152            }
153            if (re.mMethod != mMethod) {
154                return re.mMethod - mMethod;
155            }
156            return 0;
157        }
158
159        /** Returns the minutes. */
160        public int getMinutes() {
161            return mMinutes;
162        }
163
164        /** Returns the alert method. */
165        public int getMethod() {
166            return mMethod;
167        }
168    }
169
170    // TODO strip out fields that don't ever get used
171    /**
172     * The uri of the event in the db. This should only be null for new events.
173     */
174    public String mUri = null;
175    public long mId = -1;
176    public long mCalendarId = -1;
177    public String mCalendarDisplayName = ""; // Make sure this is in sync with the mCalendarId
178    public int mCalendarColor = 0;
179
180    public String mSyncId = null;
181    public String mSyncAccount = null;
182    public String mSyncAccountType = null;
183
184    // PROVIDER_NOTES owner account comes from the calendars table
185    public String mOwnerAccount = null;
186    public String mTitle = null;
187    public String mLocation = null;
188    public String mDescription = null;
189    public String mRrule = null;
190    public String mOrganizer = null;
191    public String mOrganizerDisplayName = null;
192    /**
193     * Read-Only - Derived from other fields
194     */
195    public boolean mIsOrganizer = true;
196    public boolean mIsFirstEventInSeries = true;
197
198    // This should be set the same as mStart when created and is used for making changes to
199    // recurring events. It should not be updated after it is initially set.
200    public long mOriginalStart = -1;
201    public long mStart = -1;
202
203    // This should be set the same as mEnd when created and is used for making changes to
204    // recurring events. It should not be updated after it is initially set.
205    public long mOriginalEnd = -1;
206    public long mEnd = -1;
207    public String mDuration = null;
208    public String mTimezone = null;
209    public String mTimezone2 = null;
210    public boolean mAllDay = false;
211    public boolean mHasAlarm = false;
212    public boolean mTransparency = false;
213
214    // PROVIDER_NOTES How does an event not have attendee data? The owner is added
215    // as an attendee by default.
216    public boolean mHasAttendeeData = true;
217    public int mSelfAttendeeStatus = -1;
218    public int mOwnerAttendeeId = -1;
219    public String mOriginalEvent = null;
220    public Long mOriginalTime = null;
221    public Boolean mOriginalAllDay = null;
222    public boolean mGuestsCanModify = false;
223    public boolean mGuestsCanInviteOthers = false;
224    public boolean mGuestsCanSeeGuests = false;
225
226    public boolean mOrganizerCanRespond = false;
227    public int mCalendarAccessLevel = Calendars.CONTRIBUTOR_ACCESS;
228
229    // The model can't be updated with a calendar cursor until it has been
230    // updated with an event cursor.
231    public boolean mModelUpdatedWithEventCursor;
232
233    public int mVisibility = 0;
234    public ArrayList<ReminderEntry> mReminders;
235
236    // PROVIDER_NOTES Using EditEventHelper the owner should not be included in this
237    // list and will instead be added by saveEvent. Is this what we want?
238    public LinkedHashMap<String, Attendee> mAttendeesList;
239
240    public CalendarEventModel() {
241        mReminders = new ArrayList<ReminderEntry>();
242        mAttendeesList = new LinkedHashMap<String, Attendee>();
243        mTimezone = TimeZone.getDefault().getID();
244    }
245
246    public CalendarEventModel(Context context) {
247        this();
248
249        mTimezone = Utils.getTimeZone(context, null);
250        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
251
252        String defaultReminder = prefs.getString(
253                GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
254        int defaultReminderMins = Integer.parseInt(defaultReminder);
255        if (defaultReminderMins != GeneralPreferences.NO_REMINDER) {
256            mHasAlarm = true;
257            mReminders.add(ReminderEntry.valueOf(defaultReminderMins));
258        }
259    }
260
261    public CalendarEventModel(Context context, Intent intent) {
262        this(context);
263
264        String title = intent.getStringExtra(Events.TITLE);
265        if (title != null) {
266            mTitle = title;
267        }
268
269        String location = intent.getStringExtra(Events.EVENT_LOCATION);
270        if (location != null) {
271            mLocation = location;
272        }
273
274        String description = intent.getStringExtra(Events.DESCRIPTION);
275        if (description != null) {
276            mDescription = description;
277        }
278
279        int transparency = intent.getIntExtra(Events.AVAILABILITY, -1);
280        if (transparency != -1) {
281            mTransparency = transparency != 0;
282        }
283
284        int visibility = intent.getIntExtra(Events.ACCESS_LEVEL, -1);
285        if (visibility != -1) {
286            mVisibility = visibility;
287        }
288
289        String rrule = intent.getStringExtra(Events.RRULE);
290        if (!TextUtils.isEmpty(rrule)) {
291            mRrule = rrule;
292        }
293    }
294
295    public boolean isValid() {
296        if (mCalendarId == -1) {
297            return false;
298        }
299        if (TextUtils.isEmpty(mOwnerAccount)) {
300            return false;
301        }
302        return true;
303    }
304
305    private boolean isEmpty() {
306        if (mTitle != null && mTitle.length() > 0) {
307            return false;
308        }
309
310        if (mLocation != null && mLocation.length() > 0) {
311            return false;
312        }
313
314        if (mDescription != null && mDescription.length() > 0) {
315            return false;
316        }
317
318        return true;
319    }
320
321    public void clear() {
322        mUri = null;
323        mId = -1;
324        mCalendarId = -1;
325
326        mSyncId = null;
327        mSyncAccount = null;
328        mSyncAccountType = null;
329        mOwnerAccount = null;
330
331        mTitle = null;
332        mLocation = null;
333        mDescription = null;
334        mRrule = null;
335        mOrganizer = null;
336        mOrganizerDisplayName = null;
337        mIsOrganizer = true;
338        mIsFirstEventInSeries = true;
339
340        mOriginalStart = -1;
341        mStart = -1;
342        mOriginalEnd = -1;
343        mEnd = -1;
344        mDuration = null;
345        mTimezone = null;
346        mTimezone2 = null;
347        mAllDay = false;
348        mHasAlarm = false;
349
350        mHasAttendeeData = true;
351        mSelfAttendeeStatus = -1;
352        mOwnerAttendeeId = -1;
353        mOriginalEvent = null;
354        mOriginalTime = null;
355        mOriginalAllDay = null;
356
357        mGuestsCanModify = false;
358        mGuestsCanInviteOthers = false;
359        mGuestsCanSeeGuests = false;
360        mVisibility = 0;
361        mOrganizerCanRespond = false;
362        mCalendarAccessLevel = Calendars.CONTRIBUTOR_ACCESS;
363        mModelUpdatedWithEventCursor = false;
364
365        mReminders = new ArrayList<ReminderEntry>();
366        mAttendeesList.clear();
367    }
368
369    public void addAttendee(Attendee attendee) {
370        mAttendeesList.put(attendee.mEmail, attendee);
371    }
372
373    public void removeAttendee(Attendee attendee) {
374        mAttendeesList.remove(attendee.mEmail);
375    }
376
377    public String getAttendeesString() {
378        StringBuilder b = new StringBuilder();
379        for (Attendee attendee : mAttendeesList.values()) {
380            String name = attendee.mName;
381            String email = attendee.mEmail;
382            String status = Integer.toString(attendee.mStatus);
383            b.append("name:").append(name);
384            b.append(" email:").append(email);
385            b.append(" status:").append(status);
386        }
387        return b.toString();
388    }
389
390    @Override
391    public int hashCode() {
392        final int prime = 31;
393        int result = 1;
394        result = prime * result + (mAllDay ? 1231 : 1237);
395        result = prime * result + ((mAttendeesList == null) ? 0 : getAttendeesString().hashCode());
396        result = prime * result + (int) (mCalendarId ^ (mCalendarId >>> 32));
397        result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
398        result = prime * result + ((mDuration == null) ? 0 : mDuration.hashCode());
399        result = prime * result + (int) (mEnd ^ (mEnd >>> 32));
400        result = prime * result + (mGuestsCanInviteOthers ? 1231 : 1237);
401        result = prime * result + (mGuestsCanModify ? 1231 : 1237);
402        result = prime * result + (mGuestsCanSeeGuests ? 1231 : 1237);
403        result = prime * result + (mOrganizerCanRespond ? 1231 : 1237);
404        result = prime * result + (mModelUpdatedWithEventCursor ? 1231 : 1237);
405        result = prime * result + mCalendarAccessLevel;
406        result = prime * result + (mHasAlarm ? 1231 : 1237);
407        result = prime * result + (mHasAttendeeData ? 1231 : 1237);
408        result = prime * result + (int) (mId ^ (mId >>> 32));
409        result = prime * result + (mIsFirstEventInSeries ? 1231 : 1237);
410        result = prime * result + (mIsOrganizer ? 1231 : 1237);
411        result = prime * result + ((mLocation == null) ? 0 : mLocation.hashCode());
412        result = prime * result + ((mOrganizer == null) ? 0 : mOrganizer.hashCode());
413        result = prime * result + ((mOriginalAllDay == null) ? 0 : mOriginalAllDay.hashCode());
414        result = prime * result + (int) (mOriginalEnd ^ (mOriginalEnd >>> 32));
415        result = prime * result + ((mOriginalEvent == null) ? 0 : mOriginalEvent.hashCode());
416        result = prime * result + (int) (mOriginalStart ^ (mOriginalStart >>> 32));
417        result = prime * result + ((mOriginalTime == null) ? 0 : mOriginalTime.hashCode());
418        result = prime * result + ((mOwnerAccount == null) ? 0 : mOwnerAccount.hashCode());
419        result = prime * result + ((mReminders == null) ? 0 : mReminders.hashCode());
420        result = prime * result + ((mRrule == null) ? 0 : mRrule.hashCode());
421        result = prime * result + mSelfAttendeeStatus;
422        result = prime * result + mOwnerAttendeeId;
423        result = prime * result + (int) (mStart ^ (mStart >>> 32));
424        result = prime * result + ((mSyncAccount == null) ? 0 : mSyncAccount.hashCode());
425        result = prime * result + ((mSyncAccountType == null) ? 0 : mSyncAccountType.hashCode());
426        result = prime * result + ((mSyncId == null) ? 0 : mSyncId.hashCode());
427        result = prime * result + ((mTimezone == null) ? 0 : mTimezone.hashCode());
428        result = prime * result + ((mTimezone2 == null) ? 0 : mTimezone2.hashCode());
429        result = prime * result + ((mTitle == null) ? 0 : mTitle.hashCode());
430        result = prime * result + (mTransparency ? 1231 : 1237);
431        result = prime * result + ((mUri == null) ? 0 : mUri.hashCode());
432        result = prime * result + mVisibility;
433        return result;
434    }
435
436    // Autogenerated equals method
437    @Override
438    public boolean equals(Object obj) {
439        if (this == obj) {
440            return true;
441        }
442        if (obj == null) {
443            return false;
444        }
445        if (!(obj instanceof CalendarEventModel)) {
446            return false;
447        }
448
449        CalendarEventModel other = (CalendarEventModel) obj;
450        if (!checkOriginalModelFields(other)) {
451            return false;
452        }
453
454        if (mEnd != other.mEnd) {
455            return false;
456        }
457        if (mIsFirstEventInSeries != other.mIsFirstEventInSeries) {
458            return false;
459        }
460        if (mOriginalEnd != other.mOriginalEnd) {
461            return false;
462        }
463
464        if (mOriginalStart != other.mOriginalStart) {
465            return false;
466        }
467        if (mStart != other.mStart) {
468            return false;
469        }
470
471        if (mOriginalEvent == null) {
472            if (other.mOriginalEvent != null) {
473                return false;
474            }
475        } else if (!mOriginalEvent.equals(other.mOriginalEvent)) {
476            return false;
477        }
478
479        if (mRrule == null) {
480            if (other.mRrule != null) {
481                return false;
482            }
483        } else if (!mRrule.equals(other.mRrule)) {
484            return false;
485        }
486        return true;
487    }
488
489    /**
490     * Whether the event has been modified based on its original model.
491     *
492     * @param originalModel
493     * @return true if the model is unchanged, false otherwise
494     */
495    public boolean isUnchanged(CalendarEventModel originalModel) {
496        if (this == originalModel) {
497            return true;
498        }
499        if (originalModel == null) {
500            return false;
501        }
502
503        if (!checkOriginalModelFields(originalModel)) {
504            return false;
505        }
506        if (mEnd != mOriginalEnd) {
507            return false;
508        }
509        if (mStart != mOriginalStart) {
510            return false;
511        }
512
513        if (mRrule == null) {
514            if (originalModel.mRrule != null) {
515                if (mOriginalEvent == null || !mOriginalEvent.equals(originalModel.mSyncId)) {
516                    return false;
517                }
518            }
519        } else if (!mRrule.equals(originalModel.mRrule)) {
520            return false;
521        }
522
523        return true;
524    }
525
526    /**
527     * Checks against an original model for changes to an event. This covers all
528     * the fields that should remain consistent between an original event model
529     * and the new one if nothing in the event was modified. This is also the
530     * portion that overlaps with equality between two event models.
531     *
532     * @param originalModel
533     * @return true if these fields are unchanged, false otherwise
534     */
535    protected boolean checkOriginalModelFields(CalendarEventModel originalModel) {
536        if (mAllDay != originalModel.mAllDay) {
537            return false;
538        }
539        if (mAttendeesList == null) {
540            if (originalModel.mAttendeesList != null) {
541                return false;
542            }
543        } else if (!mAttendeesList.equals(originalModel.mAttendeesList)) {
544            return false;
545        }
546
547        if (mCalendarId != originalModel.mCalendarId) {
548            return false;
549        }
550
551        if (mDescription == null) {
552            if (originalModel.mDescription != null) {
553                return false;
554            }
555        } else if (!mDescription.equals(originalModel.mDescription)) {
556            return false;
557        }
558
559        if (mDuration == null) {
560            if (originalModel.mDuration != null) {
561                return false;
562            }
563        } else if (!mDuration.equals(originalModel.mDuration)) {
564            return false;
565        }
566
567        if (mGuestsCanInviteOthers != originalModel.mGuestsCanInviteOthers) {
568            return false;
569        }
570        if (mGuestsCanModify != originalModel.mGuestsCanModify) {
571            return false;
572        }
573        if (mGuestsCanSeeGuests != originalModel.mGuestsCanSeeGuests) {
574            return false;
575        }
576        if (mOrganizerCanRespond != originalModel.mOrganizerCanRespond) {
577            return false;
578        }
579        if (mCalendarAccessLevel != originalModel.mCalendarAccessLevel) {
580            return false;
581        }
582        if (mModelUpdatedWithEventCursor != originalModel.mModelUpdatedWithEventCursor) {
583            return false;
584        }
585        if (mHasAlarm != originalModel.mHasAlarm) {
586            return false;
587        }
588        if (mHasAttendeeData != originalModel.mHasAttendeeData) {
589            return false;
590        }
591        if (mId != originalModel.mId) {
592            return false;
593        }
594        if (mIsOrganizer != originalModel.mIsOrganizer) {
595            return false;
596        }
597
598        if (mLocation == null) {
599            if (originalModel.mLocation != null) {
600                return false;
601            }
602        } else if (!mLocation.equals(originalModel.mLocation)) {
603            return false;
604        }
605
606        if (mOrganizer == null) {
607            if (originalModel.mOrganizer != null) {
608                return false;
609            }
610        } else if (!mOrganizer.equals(originalModel.mOrganizer)) {
611            return false;
612        }
613
614        if (mOriginalAllDay == null) {
615            if (originalModel.mOriginalAllDay != null) {
616                return false;
617            }
618        } else if (!mOriginalAllDay.equals(originalModel.mOriginalAllDay)) {
619            return false;
620        }
621
622        if (mOriginalTime == null) {
623            if (originalModel.mOriginalTime != null) {
624                return false;
625            }
626        } else if (!mOriginalTime.equals(originalModel.mOriginalTime)) {
627            return false;
628        }
629
630        if (mOwnerAccount == null) {
631            if (originalModel.mOwnerAccount != null) {
632                return false;
633            }
634        } else if (!mOwnerAccount.equals(originalModel.mOwnerAccount)) {
635            return false;
636        }
637
638        if (mReminders == null) {
639            if (originalModel.mReminders != null) {
640                return false;
641            }
642        } else if (!mReminders.equals(originalModel.mReminders)) {
643            return false;
644        }
645
646        if (mSelfAttendeeStatus != originalModel.mSelfAttendeeStatus) {
647            return false;
648        }
649        if (mOwnerAttendeeId != originalModel.mOwnerAttendeeId) {
650            return false;
651        }
652        if (mSyncAccount == null) {
653            if (originalModel.mSyncAccount != null) {
654                return false;
655            }
656        } else if (!mSyncAccount.equals(originalModel.mSyncAccount)) {
657            return false;
658        }
659
660        if (mSyncAccountType == null) {
661            if (originalModel.mSyncAccountType != null) {
662                return false;
663            }
664        } else if (!mSyncAccountType.equals(originalModel.mSyncAccountType)) {
665            return false;
666        }
667
668        if (mSyncId == null) {
669            if (originalModel.mSyncId != null) {
670                return false;
671            }
672        } else if (!mSyncId.equals(originalModel.mSyncId)) {
673            return false;
674        }
675
676        if (mTimezone == null) {
677            if (originalModel.mTimezone != null) {
678                return false;
679            }
680        } else if (!mTimezone.equals(originalModel.mTimezone)) {
681            return false;
682        }
683
684        if (mTimezone2 == null) {
685            if (originalModel.mTimezone2 != null) {
686                return false;
687            }
688        } else if (!mTimezone2.equals(originalModel.mTimezone2)) {
689            return false;
690        }
691
692        if (mTitle == null) {
693            if (originalModel.mTitle != null) {
694                return false;
695            }
696        } else if (!mTitle.equals(originalModel.mTitle)) {
697            return false;
698        }
699
700        if (mTransparency != originalModel.mTransparency) {
701            return false;
702        }
703
704        if (mUri == null) {
705            if (originalModel.mUri != null) {
706                return false;
707            }
708        } else if (!mUri.equals(originalModel.mUri)) {
709            return false;
710        }
711
712        if (mVisibility != originalModel.mVisibility) {
713            return false;
714        }
715        return true;
716    }
717
718    /**
719     * Sort and uniquify mReminderMinutes.
720     *
721     * @return true (for convenience of caller)
722     */
723    public boolean normalizeReminders() {
724        if (mReminders.size() <= 1) {
725            return true;
726        }
727
728        // sort
729        Collections.sort(mReminders);
730
731        // remove duplicates
732        ReminderEntry prev = mReminders.get(mReminders.size()-1);
733        for (int i = mReminders.size()-2; i >= 0; --i) {
734            ReminderEntry cur = mReminders.get(i);
735            if (prev.equals(cur)) {
736                // match, remove later entry
737                mReminders.remove(i+1);
738            }
739            prev = cur;
740        }
741
742        return true;
743    }
744}
745