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