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