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