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