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