EditEventView.java revision 3400a2bfc1e6be435ea2bb0f436a6ef338446504
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calendar.event;
18
19import com.android.calendar.CalendarEventModel;
20import com.android.calendar.CalendarEventModel.Attendee;
21import com.android.calendar.EmailAddressAdapter;
22import com.android.calendar.EventInfoFragment;
23import com.android.calendar.GeneralPreferences;
24import com.android.calendar.R;
25import com.android.calendar.TimezoneAdapter;
26import com.android.calendar.TimezoneAdapter.TimezoneRow;
27import com.android.calendar.Utils;
28import com.android.calendar.event.EditEventHelper.EditDoneRunnable;
29import com.android.common.Rfc822InputFilter;
30import com.android.common.Rfc822Validator;
31
32import android.app.Activity;
33import android.app.AlertDialog;
34import android.app.DatePickerDialog;
35import android.app.DatePickerDialog.OnDateSetListener;
36import android.app.ProgressDialog;
37import android.app.TimePickerDialog;
38import android.app.TimePickerDialog.OnTimeSetListener;
39import android.content.Context;
40import android.content.DialogInterface;
41import android.content.Intent;
42import android.content.SharedPreferences;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.graphics.drawable.Drawable;
46import android.pim.EventRecurrence;
47import android.provider.Calendar.Attendees;
48import android.provider.Calendar.Calendars;
49import android.provider.Settings;
50import android.text.Editable;
51import android.text.InputFilter;
52import android.text.TextUtils;
53import android.text.TextWatcher;
54import android.text.format.DateFormat;
55import android.text.format.DateUtils;
56import android.text.format.Time;
57import android.text.util.Rfc822Tokenizer;
58import android.util.Log;
59import android.view.LayoutInflater;
60import android.view.View;
61import android.widget.AdapterView;
62import android.widget.AdapterView.OnItemSelectedListener;
63import android.widget.ArrayAdapter;
64import android.widget.Button;
65import android.widget.CheckBox;
66import android.widget.CompoundButton;
67import android.widget.DatePicker;
68import android.widget.ImageButton;
69import android.widget.LinearLayout;
70import android.widget.MultiAutoCompleteTextView;
71import android.widget.RadioGroup;
72import android.widget.ResourceCursorAdapter;
73import android.widget.ScrollView;
74import android.widget.Spinner;
75import android.widget.TextView;
76import android.widget.TimePicker;
77
78import java.util.ArrayList;
79import java.util.Arrays;
80import java.util.Calendar;
81import java.util.HashMap;
82import java.util.TimeZone;
83
84public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener,
85        DialogInterface.OnClickListener, TextWatcher, OnItemSelectedListener {
86    private static final String TAG = "EditEvent";
87    private static final String GOOGLE_SECONDARY_CALENDAR = "calendar.google.com";
88    private static final int REMINDER_FLING_VELOCITY = 2000;
89    private LayoutInflater mLayoutInflater;
90
91    ArrayList<View> mEditOnlyList = new ArrayList<View>();
92    ArrayList<View> mEditViewList = new ArrayList<View>();
93    ArrayList<View> mViewOnlyList = new ArrayList<View>();
94    TextView mLoadingMessage;
95    ScrollView mScrollView;
96    Button mStartDateButton;
97    Button mEndDateButton;
98    Button mStartTimeButton;
99    Button mEndTimeButton;
100    Button mTimezoneButton;
101    CheckBox mAllDayCheckBox;
102    Spinner mCalendarsSpinner;
103    Spinner mRepeatsSpinner;
104    Spinner mTransparencySpinner;
105    Spinner mVisibilitySpinner;
106    RadioGroup mResponseRadioGroup;
107    TextView mTitleTextView;
108    TextView mLocationTextView;
109    TextView mDescriptionTextView;
110    TextView mWhenView;
111    TextView mTimezoneTextView;
112    TextView mTimezoneLabel;
113    TextView mTimezoneFooterView;
114    LinearLayout mRemindersContainer;
115    MultiAutoCompleteTextView mAttendeesList;
116    ImageButton mAddAttendeesButton;
117    AttendeesView mAttendeesView;
118    AddAttendeeClickListener mAddAttendeesListener;
119    View mCalendarSelectorGroup;
120    View mCalendarStaticGroup;
121    View mRemindersGroup;
122    View mResponseGroup;
123    View mOrganizerGroup;
124    View mAttendeesGroup;
125    View mAttendeesPane;
126    View mColorChip;
127
128    private Drawable mOriginalEditBG;
129
130    private ProgressDialog mLoadingCalendarsDialog;
131    private AlertDialog mNoCalendarsDialog;
132    private AlertDialog mTimezoneDialog;
133    private Activity mActivity;
134    private EditDoneRunnable mDone;
135    private View mView;
136    private CalendarEventModel mModel;
137    private Cursor mCalendarsCursor;
138    private EmailAddressAdapter mAddressAdapter;
139    private Rfc822Validator mEmailValidator;
140    private TimezoneAdapter mTimezoneAdapter;
141
142    private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer>(0);
143    private ArrayList<Integer> mReminderValues;
144    private ArrayList<String> mReminderLabels;
145    private int mDefaultReminderMinutes;
146
147    private boolean mSaveAfterQueryComplete = false;
148
149    private Time mStartTime;
150    private Time mEndTime;
151    private String mTimezone;
152    private int mModification = EditEventHelper.MODIFY_UNINITIALIZED;
153
154    private EventRecurrence mEventRecurrence = new EventRecurrence();
155
156    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
157
158    /* This class is used to update the time buttons. */
159    private class TimeListener implements OnTimeSetListener {
160        private View mView;
161
162        public TimeListener(View view) {
163            mView = view;
164        }
165
166        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
167            // Cache the member variables locally to avoid inner class overhead.
168            Time startTime = mStartTime;
169            Time endTime = mEndTime;
170
171            // Cache the start and end millis so that we limit the number
172            // of calls to normalize() and toMillis(), which are fairly
173            // expensive.
174            long startMillis;
175            long endMillis;
176            if (mView == mStartTimeButton) {
177                // The start time was changed.
178                int hourDuration = endTime.hour - startTime.hour;
179                int minuteDuration = endTime.minute - startTime.minute;
180
181                startTime.hour = hourOfDay;
182                startTime.minute = minute;
183                startMillis = startTime.normalize(true);
184
185                // Also update the end time to keep the duration constant.
186                endTime.hour = hourOfDay + hourDuration;
187                endTime.minute = minute + minuteDuration;
188            } else {
189                // The end time was changed.
190                startMillis = startTime.toMillis(true);
191                endTime.hour = hourOfDay;
192                endTime.minute = minute;
193
194                // Move to the start time if the end time is before the start
195                // time.
196                if (endTime.before(startTime)) {
197                    endTime.monthDay = startTime.monthDay + 1;
198                }
199            }
200
201            endMillis = endTime.normalize(true);
202
203            setDate(mEndDateButton, endMillis);
204            setTime(mStartTimeButton, startMillis);
205            setTime(mEndTimeButton, endMillis);
206        }
207    }
208
209    private class AddAttendeeClickListener implements View.OnClickListener {
210        @Override
211        public void onClick(View v) {
212            // Checking for null since this method may be called even though the
213            // add button wasn't clicked e.g. when the Save button is clicked.
214            // The mAttendeesList may not be setup since the user doesn't have
215            // permission to add attendees.
216            if (mAttendeesList != null) {
217                mAttendeesList.performValidation();
218                mAttendeesView.addAttendees(mAttendeesList.getText().toString());
219                mAttendeesList.setText("");
220                mAttendeesGroup.setVisibility(View.VISIBLE);
221            }
222        }
223    }
224
225    private class TimeClickListener implements View.OnClickListener {
226        private Time mTime;
227
228        public TimeClickListener(Time time) {
229            mTime = time;
230        }
231
232        public void onClick(View v) {
233            new TimePickerDialog(mActivity, new TimeListener(v), mTime.hour, mTime.minute,
234                    DateFormat.is24HourFormat(mActivity)).show();
235        }
236    }
237
238    private class DateListener implements OnDateSetListener {
239        View mView;
240
241        public DateListener(View view) {
242            mView = view;
243        }
244
245        public void onDateSet(DatePicker view, int year, int month, int monthDay) {
246            // Cache the member variables locally to avoid inner class overhead.
247            Time startTime = mStartTime;
248            Time endTime = mEndTime;
249
250            // Cache the start and end millis so that we limit the number
251            // of calls to normalize() and toMillis(), which are fairly
252            // expensive.
253            long startMillis;
254            long endMillis;
255            if (mView == mStartDateButton) {
256                // The start date was changed.
257                int yearDuration = endTime.year - startTime.year;
258                int monthDuration = endTime.month - startTime.month;
259                int monthDayDuration = endTime.monthDay - startTime.monthDay;
260
261                startTime.year = year;
262                startTime.month = month;
263                startTime.monthDay = monthDay;
264                startMillis = startTime.normalize(true);
265
266                // Also update the end date to keep the duration constant.
267                endTime.year = year + yearDuration;
268                endTime.month = month + monthDuration;
269                endTime.monthDay = monthDay + monthDayDuration;
270                endMillis = endTime.normalize(true);
271
272                // If the start date has changed then update the repeats.
273                populateRepeats();
274            } else {
275                // The end date was changed.
276                startMillis = startTime.toMillis(true);
277                endTime.year = year;
278                endTime.month = month;
279                endTime.monthDay = monthDay;
280                endMillis = endTime.normalize(true);
281
282                // Do not allow an event to have an end time before the start
283                // time.
284                if (endTime.before(startTime)) {
285                    endTime.set(startTime);
286                    endMillis = startMillis;
287                }
288            }
289
290            setDate(mStartDateButton, startMillis);
291            setDate(mEndDateButton, endMillis);
292            setTime(mEndTimeButton, endMillis); // In case end time had to be
293            // reset
294        }
295    }
296
297    // Fills in the date and time fields
298    private void populateWhen() {
299        long startMillis = mStartTime.toMillis(false /* use isDst */);
300        long endMillis = mEndTime.toMillis(false /* use isDst */);
301        setDate(mStartDateButton, startMillis);
302        setDate(mEndDateButton, endMillis);
303
304        setTime(mStartTimeButton, startMillis);
305        setTime(mEndTimeButton, endMillis);
306
307        mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
308        mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
309
310        mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
311        mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
312    }
313
314    private void populateTimezone() {
315        mTimezoneButton.setOnClickListener(new View.OnClickListener() {
316            @Override
317            public void onClick(View v) {
318                showTimezoneDialog();
319            }
320        });
321        setTimezone(mTimezoneAdapter.getRowById(mTimezone));
322    }
323
324    private void showTimezoneDialog() {
325        AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
326        mTimezoneAdapter = new TimezoneAdapter(builder.getContext(), mTimezone);
327        builder.setTitle(R.string.timezone_label);
328        builder.setSingleChoiceItems(
329                mTimezoneAdapter, mTimezoneAdapter.getRowById(mTimezone), this);
330        mTimezoneDialog = builder.create();
331        mTimezoneFooterView.setText(mActivity.getString(R.string.edit_event_show_all) + " >");
332        mTimezoneFooterView.setOnClickListener(new View.OnClickListener() {
333            @Override
334            public void onClick(View v) {
335                mTimezoneDialog.getListView().removeFooterView(mTimezoneFooterView);
336                mTimezoneAdapter.showAllTimezones();
337                final int row = mTimezoneAdapter.getRowById(mTimezone);
338                // we need to post the selection changes to have them have
339                // any effect
340                mTimezoneDialog.getListView().post(new Runnable() {
341                    @Override
342                    public void run() {
343                        mTimezoneDialog.getListView().setItemChecked(row, true);
344                        mTimezoneDialog.getListView().setSelection(row);
345                    }
346                });
347            }
348        });
349        mTimezoneDialog.getListView().addFooterView(mTimezoneFooterView);
350        mTimezoneDialog.show();
351    }
352
353    private void populateRepeats() {
354        Time time = mStartTime;
355        Resources r = mActivity.getResources();
356        int resource = android.R.layout.simple_spinner_item;
357
358        String[] days = new String[] {
359                DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
360                DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
361                DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
362                DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
363                DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
364                DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
365                DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM), };
366        String[] ordinals = r.getStringArray(R.array.ordinal_labels);
367
368        // Only display "Custom" in the spinner if the device does not support
369        // the recurrence functionality of the event. Only display every weekday
370        // if the event starts on a weekday.
371        boolean isCustomRecurrence = isCustomRecurrence();
372        boolean isWeekdayEvent = isWeekdayEvent();
373
374        ArrayList<String> repeatArray = new ArrayList<String>(0);
375        ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
376
377        repeatArray.add(r.getString(R.string.does_not_repeat));
378        recurrenceIndexes.add(EditEventHelper.DOES_NOT_REPEAT);
379
380        repeatArray.add(r.getString(R.string.daily));
381        recurrenceIndexes.add(EditEventHelper.REPEATS_DAILY);
382
383        if (isWeekdayEvent) {
384            repeatArray.add(r.getString(R.string.every_weekday));
385            recurrenceIndexes.add(EditEventHelper.REPEATS_EVERY_WEEKDAY);
386        }
387
388        String format = r.getString(R.string.weekly);
389        repeatArray.add(String.format(format, time.format("%A")));
390        recurrenceIndexes.add(EditEventHelper.REPEATS_WEEKLY_ON_DAY);
391
392        // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance
393        // of the given day.
394        int dayNumber = (time.monthDay - 1) / 7;
395        format = r.getString(R.string.monthly_on_day_count);
396        repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
397        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
398
399        format = r.getString(R.string.monthly_on_day);
400        repeatArray.add(String.format(format, time.monthDay));
401        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY);
402
403        long when = time.toMillis(false);
404        format = r.getString(R.string.yearly);
405        int flags = 0;
406        if (DateFormat.is24HourFormat(mActivity)) {
407            flags |= DateUtils.FORMAT_24HOUR;
408        }
409        repeatArray.add(String.format(format, DateUtils.formatDateTime(mActivity, when, flags)));
410        recurrenceIndexes.add(EditEventHelper.REPEATS_YEARLY);
411
412        if (isCustomRecurrence) {
413            repeatArray.add(r.getString(R.string.custom));
414            recurrenceIndexes.add(EditEventHelper.REPEATS_CUSTOM);
415        }
416        mRecurrenceIndexes = recurrenceIndexes;
417
418        int position = recurrenceIndexes.indexOf(EditEventHelper.DOES_NOT_REPEAT);
419        if (mModel.mRrule != null) {
420            if (isCustomRecurrence) {
421                position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_CUSTOM);
422            } else {
423                switch (mEventRecurrence.freq) {
424                    case EventRecurrence.DAILY:
425                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_DAILY);
426                        break;
427                    case EventRecurrence.WEEKLY:
428                        if (mEventRecurrence.repeatsOnEveryWeekDay()) {
429                            position = recurrenceIndexes.indexOf(
430                                    EditEventHelper.REPEATS_EVERY_WEEKDAY);
431                        } else {
432                            position = recurrenceIndexes.indexOf(
433                                    EditEventHelper.REPEATS_WEEKLY_ON_DAY);
434                        }
435                        break;
436                    case EventRecurrence.MONTHLY:
437                        if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
438                            position = recurrenceIndexes.indexOf(
439                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
440                        } else {
441                            position = recurrenceIndexes.indexOf(
442                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY);
443                        }
444                        break;
445                    case EventRecurrence.YEARLY:
446                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_YEARLY);
447                        break;
448                }
449            }
450        }
451        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, resource, repeatArray);
452        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
453        mRepeatsSpinner.setAdapter(adapter);
454        mRepeatsSpinner.setSelection(position);
455
456        // Don't allow the user to make exceptions recurring events.
457        if (mModel.mOriginalEvent != null) {
458            mRepeatsSpinner.setEnabled(false);
459        }
460    }
461
462    private boolean isCustomRecurrence() {
463
464        if (mEventRecurrence.until != null || mEventRecurrence.interval != 0) {
465            return true;
466        }
467
468        if (mEventRecurrence.freq == 0) {
469            return false;
470        }
471
472        switch (mEventRecurrence.freq) {
473            case EventRecurrence.DAILY:
474                return false;
475            case EventRecurrence.WEEKLY:
476                if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
477                    return false;
478                } else if (mEventRecurrence.bydayCount == 1) {
479                    return false;
480                }
481                break;
482            case EventRecurrence.MONTHLY:
483                if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
484                    return false;
485                } else if (mEventRecurrence.bydayCount == 0
486                        && mEventRecurrence.bymonthdayCount == 1) {
487                    return false;
488                }
489                break;
490            case EventRecurrence.YEARLY:
491                return false;
492        }
493
494        return true;
495    }
496
497    private boolean isWeekdayEvent() {
498        if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
499            return true;
500        }
501        return false;
502    }
503
504    private class DateClickListener implements View.OnClickListener {
505        private Time mTime;
506
507        public DateClickListener(Time time) {
508            mTime = time;
509        }
510
511        public void onClick(View v) {
512            new DatePickerDialog(
513                    mActivity, new DateListener(v), mTime.year, mTime.month, mTime.monthDay).show();
514        }
515    }
516
517    static private class CalendarsAdapter extends ResourceCursorAdapter {
518        public CalendarsAdapter(Context context, Cursor c) {
519            super(context, R.layout.calendars_item, c);
520            setDropDownViewResource(R.layout.calendars_dropdown_item);
521        }
522
523        @Override
524        public void bindView(View view, Context context, Cursor cursor) {
525            View colorBar = view.findViewById(R.id.color);
526            int colorColumn = cursor.getColumnIndexOrThrow(Calendars.COLOR);
527            int nameColumn = cursor.getColumnIndexOrThrow(Calendars.DISPLAY_NAME);
528            int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
529            if (colorBar != null) {
530                colorBar.setBackgroundDrawable(Utils.getColorChip(cursor.getInt(colorColumn)));
531            }
532
533            TextView name = (TextView) view.findViewById(R.id.calendar_name);
534            if (name != null) {
535                String displayName = cursor.getString(nameColumn);
536                name.setText(displayName);
537                name.setTextColor(0xFF000000);
538
539                TextView accountName = (TextView) view.findViewById(R.id.account_name);
540                if (accountName != null) {
541                    Resources res = context.getResources();
542                    accountName.setText(cursor.getString(ownerColumn));
543                    accountName.setVisibility(TextView.VISIBLE);
544                    accountName.setTextColor(res.getColor(R.color.calendar_owner_text_color));
545                }
546            }
547        }
548    }
549
550    /**
551     * Does prep steps for saving a calendar event.
552     *
553     * This triggers a parse of the attendees list and checks if the event is
554     * ready to be saved. An event is ready to be saved so long as a model
555     * exists and has a calendar it can be associated with, either because it's
556     * an existing event or we've finished querying.
557     *
558     * @return false if there is no model or no calendar had been loaded yet,
559     * true otherwise.
560     */
561    public boolean prepareForSave() {
562        if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
563            return false;
564        }
565        mAddAttendeesListener.onClick(null);
566        return fillModelFromUI();
567    }
568
569    // This is called if the user clicks on one of the buttons: "Save",
570    // "Discard", or "Delete". This is also called if the user clicks
571    // on the "remove reminder" button.
572    public void onClick(View view) {
573
574        // This must be a click on one of the "remove reminder" buttons
575        LinearLayout reminderItem = (LinearLayout) view.getParent();
576        LinearLayout parent = (LinearLayout) reminderItem.getParent();
577        parent.removeView(reminderItem);
578        mReminderItems.remove(reminderItem);
579        updateRemindersVisibility(mReminderItems.size());
580    }
581
582    // This is called if the user cancels the "No calendars" dialog.
583    // The "No calendars" dialog is shown if there are no syncable calendars.
584    @Override
585    public void onCancel(DialogInterface dialog) {
586        if (dialog == mLoadingCalendarsDialog) {
587            mLoadingCalendarsDialog = null;
588            mSaveAfterQueryComplete = false;
589        } else if (dialog == mNoCalendarsDialog) {
590            mDone.setDoneCode(Utils.DONE_REVERT);
591            mDone.run();
592            return;
593        }
594    }
595
596    // This is called if the user clicks on a dialog button.
597    @Override
598    public void onClick(DialogInterface dialog, int which) {
599        if (dialog == mNoCalendarsDialog) {
600            mDone.setDoneCode(Utils.DONE_REVERT);
601            mDone.run();
602            if (which == DialogInterface.BUTTON_POSITIVE) {
603                Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
604                final String[] array = {"com.android.calendar"};
605                nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
606                mActivity.startActivity(nextIntent);
607            }
608        } else if (dialog == mTimezoneDialog) {
609            if (which >= 0 && which < mTimezoneAdapter.getCount()) {
610                setTimezone(which);
611                dialog.dismiss();
612            }
613        }
614    }
615
616    // Goes through the UI elements and updates the model as necessary
617    private boolean fillModelFromUI() {
618        if (mModel == null) {
619            return false;
620        }
621        mModel.mReminderMinutes = EventViewUtils.reminderItemsToMinutes(
622                mReminderItems, mReminderValues);
623        mModel.mHasAlarm = mReminderItems.size() > 0;
624        mModel.mTitle = mTitleTextView.getText().toString().trim();
625        mModel.mAllDay = mAllDayCheckBox.isChecked();
626        mModel.mLocation = mLocationTextView.getText().toString().trim();
627        mModel.mDescription = mDescriptionTextView.getText().toString().trim();
628
629        int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup
630                .getCheckedRadioButtonId());
631        if (status != Attendees.ATTENDEE_STATUS_NONE) {
632            mModel.mSelfAttendeeStatus = status;
633        }
634
635        if (mAttendeesView != null && mAttendeesView.getChildCount() > 0) {
636            final int size = mAttendeesView.getChildCount();
637            mModel.mAttendeesList.clear();
638            for (int i = 0; i < size; i++) {
639                final Attendee attendee = mAttendeesView.getItem(i);
640                if (attendee == null || mAttendeesView.isMarkAsRemoved(i)) {
641                    continue;
642                }
643                mModel.addAttendee(attendee);
644            }
645        }
646
647        // If this was a new event we need to fill in the Calendar information
648        if (mModel.mUri == null) {
649            mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId();
650            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
651            if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
652                String defaultCalendar = mCalendarsCursor.getString(
653                        EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
654                Utils.setSharedPreference(
655                        mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar);
656                mModel.mOwnerAccount = defaultCalendar;
657                mModel.mOrganizer = defaultCalendar;
658                mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID);
659            }
660        }
661
662        if (mModel.mAllDay) {
663            // Reset start and end time, increment the monthDay by 1, and set
664            // the timezone to UTC, as required for all-day events.
665            mTimezone = Time.TIMEZONE_UTC;
666            mStartTime.hour = 0;
667            mStartTime.minute = 0;
668            mStartTime.second = 0;
669            mStartTime.timezone = mTimezone;
670            mModel.mStart = mStartTime.normalize(true);
671
672            mEndTime.hour = 0;
673            mEndTime.minute = 0;
674            mEndTime.second = 0;
675            mEndTime.timezone = mTimezone;
676            // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time
677            // should be Y + 1 (Oct.30).
678            final long normalizedEndTimeMillis =
679                    mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS;
680            if (normalizedEndTimeMillis < mModel.mStart) {
681                // mEnd should be midnight of the next day of mStart.
682                mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS;
683            } else {
684                mModel.mEnd = normalizedEndTimeMillis;
685            }
686        } else {
687            mStartTime.timezone = mTimezone;
688            mEndTime.timezone = mTimezone;
689            mModel.mStart = mStartTime.toMillis(true);
690            mModel.mEnd = mEndTime.toMillis(true);
691        }
692        mModel.mTimezone = mTimezone;
693        mModel.mVisibility = mVisibilitySpinner.getSelectedItemPosition();
694        mModel.mTransparency = mTransparencySpinner.getSelectedItemPosition() != 0;
695
696        int selection;
697        // If we're making an exception we don't want it to be a repeating
698        // event.
699        if (mModification == EditEventHelper.MODIFY_SELECTED) {
700            selection = EditEventHelper.DOES_NOT_REPEAT;
701        } else {
702            int position = mRepeatsSpinner.getSelectedItemPosition();
703            selection = mRecurrenceIndexes.get(position);
704        }
705
706        EditEventHelper.updateRecurrenceRule(
707                selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1);
708
709        // Save the timezone so we can display it as a standard option next time
710        if (!mModel.mAllDay) {
711            mTimezoneAdapter.saveRecentTimezone(mTimezone);
712        }
713        return true;
714    }
715
716    public EditEventView(Activity activity, View view, EditDoneRunnable done) {
717
718        mActivity = activity;
719        mView = view;
720        mDone = done;
721
722        // cache top level view elements
723        mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
724        mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
725
726        mLayoutInflater = activity.getLayoutInflater();
727
728        // cache all the widgets
729        mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner);
730        mTitleTextView = (TextView) view.findViewById(R.id.title);
731        mLocationTextView = (TextView) view.findViewById(R.id.location);
732        mDescriptionTextView = (TextView) view.findViewById(R.id.description);
733        mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label);
734        mTimezoneFooterView = (TextView) mLayoutInflater.inflate(R.layout.timezone_footer, null);
735        mStartDateButton = (Button) view.findViewById(R.id.start_date);
736        mEndDateButton = (Button) view.findViewById(R.id.end_date);
737        mWhenView = (TextView) mView.findViewById(R.id.when);
738        mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView);
739        mStartTimeButton = (Button) view.findViewById(R.id.start_time);
740        mEndTimeButton = (Button) view.findViewById(R.id.end_time);
741        mTimezoneButton = (Button) view.findViewById(R.id.timezone_button);
742        mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day);
743        mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats);
744        mTransparencySpinner = (Spinner) view.findViewById(R.id.availability);
745        mVisibilitySpinner = (Spinner) view.findViewById(R.id.visibility);
746        mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group);
747        mCalendarStaticGroup = view.findViewById(R.id.calendar_group);
748        mRemindersGroup = view.findViewById(R.id.reminders_row);
749        mResponseGroup = view.findViewById(R.id.response_row);
750        mOrganizerGroup = view.findViewById(R.id.organizer_row);
751        mAttendeesGroup = view.findViewById(R.id.attendees_row);
752        mAttendeesPane = view.findViewById(R.id.attendees_group);
753
754        mOriginalEditBG = mTitleTextView.getBackground();
755        mEditViewList.add(mTitleTextView);
756        mEditViewList.add(mLocationTextView);
757        mEditViewList.add(mDescriptionTextView);
758
759        mViewOnlyList.add(view.findViewById(R.id.when_row));
760        mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row));
761
762        mEditOnlyList.add(view.findViewById(R.id.all_day_row));
763        mEditOnlyList.add(view.findViewById(R.id.availability_row));
764        mEditOnlyList.add(view.findViewById(R.id.visibility_row));
765        mEditOnlyList.add(view.findViewById(R.id.from_row));
766        mEditOnlyList.add(view.findViewById(R.id.to_row));
767        mEditOnlyList.add(view.findViewById(R.id.timezone_button_row));
768        mEditOnlyList.add(view.findViewById(R.id.add_attendees_row));
769
770        mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value);
771        mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
772
773        mAddAttendeesButton = (ImageButton) view.findViewById(R.id.add_attendee_button);
774        mAddAttendeesListener = new AddAttendeeClickListener();
775        mAddAttendeesButton.setEnabled(false);
776        mAddAttendeesButton.setOnClickListener(mAddAttendeesListener);
777
778        mAttendeesView = (AttendeesView)view.findViewById(R.id.attendee_list);
779
780        mStartTime = new Time();
781        mEndTime = new Time();
782        mTimezone = TimeZone.getDefault().getID();
783        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
784
785        mColorChip = view.findViewById(R.id.color_chip);
786
787        // Display loading screen
788        setModel(null);
789    }
790
791    /**
792     * Fill in the view with the contents of the given event model. This allows
793     * an edit view to be initialized before the event has been loaded. Passing
794     * in null for the model will display a loading screen. A non-null model
795     * will fill in the view's fields with the data contained in the model.
796     *
797     * @param model The event model to pull the data from
798     */
799    public void setModel(CalendarEventModel model) {
800        mModel = model;
801
802        // Need to close the autocomplete adapter to prevent leaking cursors.
803        if (mAddressAdapter != null) {
804            mAddressAdapter.close();
805            mAddressAdapter = null;
806        }
807
808        if (model == null) {
809            // Display loading screen
810            mLoadingMessage.setVisibility(View.VISIBLE);
811            mScrollView.setVisibility(View.GONE);
812            return;
813        }
814
815        boolean canModifyCalendar = EditEventHelper.canModifyCalendar(model);
816        boolean canModifyEvent = EditEventHelper.canModifyEvent(model);
817        boolean canRespond = EditEventHelper.canRespond(model);
818
819        long begin = model.mStart;
820        long end = model.mEnd;
821        mTimezone = model.mTimezone; // this will be UTC for all day events
822
823        // Set up the starting times
824        if (begin > 0) {
825            mStartTime.timezone = mTimezone;
826            mStartTime.set(begin);
827            mStartTime.normalize(true);
828        }
829        if (end > 0) {
830            mEndTime.timezone = mTimezone;
831            mEndTime.set(end);
832            mEndTime.normalize(true);
833        }
834        String rrule = model.mRrule;
835        if (rrule != null) {
836            mEventRecurrence.parse(rrule);
837        }
838
839        // If the user is allowed to change the attendees set up the view and
840        // validator
841        if (!model.mHasAttendeeData) {
842            mView.findViewById(R.id.attendees_group).setVisibility(View.GONE);
843        } else if (!canModifyEvent) {
844            // Hide views used for adding attendees
845            mView.findViewById(R.id.add_attendees_label).setVisibility(View.GONE);
846            mView.findViewById(R.id.add_attendees_group).setVisibility(View.GONE);
847            mAddAttendeesButton.setVisibility(View.GONE);
848        } else {
849            String domain = "gmail.com";
850            if (!TextUtils.isEmpty(model.mOwnerAccount)) {
851                String ownerDomain = EditEventHelper.extractDomain(model.mOwnerAccount);
852                if (!TextUtils.isEmpty(ownerDomain)) {
853                    domain = ownerDomain;
854                }
855            }
856            mAddressAdapter = new EmailAddressAdapter(mActivity);
857            mEmailValidator = new Rfc822Validator(domain);
858            mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees);
859            mAttendeesList.addTextChangedListener(this);
860        }
861
862        mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
863            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
864                setAllDayViewsVisibility(isChecked);
865            }
866        });
867
868        if (model.mAllDay) {
869            mAllDayCheckBox.setChecked(true);
870            // put things back in local time for all day events
871            mTimezone = TimeZone.getDefault().getID();
872            mStartTime.timezone = mTimezone;
873            mStartTime.normalize(true);
874            mEndTime.timezone = mTimezone;
875            mEndTime.normalize(true);
876        } else {
877            mAllDayCheckBox.setChecked(false);
878        }
879
880        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
881        if (mTimezoneDialog != null) {
882            mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter);
883        }
884
885        // Initialize the reminder values array.
886        Resources r = mActivity.getResources();
887        String[] strings = r.getStringArray(R.array.reminder_minutes_values);
888        int size = strings.length;
889        ArrayList<Integer> list = new ArrayList<Integer>(size);
890        for (int i = 0; i < size; i++) {
891            list.add(Integer.parseInt(strings[i]));
892        }
893        mReminderValues = list;
894        String[] labels = r.getStringArray(R.array.reminder_minutes_labels);
895        mReminderLabels = new ArrayList<String>(Arrays.asList(labels));
896
897        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
898        String durationString = prefs.getString(GeneralPreferences.KEY_DEFAULT_REMINDER, "0");
899        mDefaultReminderMinutes = Integer.parseInt(durationString);
900
901        int numReminders = 0;
902        if (model.mHasAlarm) {
903            ArrayList<Integer> minutes = model.mReminderMinutes;
904            numReminders = minutes.size();
905            for (Integer minute : minutes) {
906                EventViewUtils.addMinutesToList(
907                        mActivity, mReminderValues, mReminderLabels, minute);
908                EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
909                        mReminderValues, mReminderLabels, minute);
910            }
911        }
912
913        ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add);
914        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
915            public void onClick(View v) {
916                addReminder();
917            }
918        };
919        reminderAddButton.setOnClickListener(addReminderOnClickListener);
920        updateRemindersVisibility(numReminders);
921
922        mTitleTextView.setText(model.mTitle);
923        if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer)
924                || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) {
925            mView.findViewById(R.id.organizer_label).setVisibility(View.GONE);
926            mView.findViewById(R.id.organizer).setVisibility(View.GONE);
927        } else {
928            ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName);
929        }
930        mLocationTextView.setText(model.mLocation);
931        mDescriptionTextView.setText(model.mDescription);
932        mTransparencySpinner.setSelection(model.mTransparency ? 1 : 0);
933        mVisibilitySpinner.setSelection(model.mVisibility);
934
935        View responseLabel = mView.findViewById(R.id.response_label);
936        if (canRespond) {
937            int buttonToCheck = EventInfoFragment
938                    .findButtonIdForResponse(model.mSelfAttendeeStatus);
939            mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
940            mResponseRadioGroup.setVisibility(View.VISIBLE);
941            responseLabel.setVisibility(View.VISIBLE);
942        } else {
943            responseLabel.setVisibility(View.GONE);
944            mResponseRadioGroup.setVisibility(View.GONE);
945        }
946
947        if (model.mUri != null) {
948            // This is an existing event so hide the calendar spinner
949            // since we can't change the calendar.
950            View calendarGroup = mView.findViewById(R.id.calendar_selector_group);
951            calendarGroup.setVisibility(View.GONE);
952            TextView tv = (TextView) mView.findViewById(R.id.calendar_textview);
953            tv.setText(model.mCalendarDisplayName);
954            mColorChip.setBackgroundColor(model.mCalendarColor);
955        } else {
956            View calendarGroup = mView.findViewById(R.id.calendar_group);
957            calendarGroup.setVisibility(View.GONE);
958        }
959
960        populateWhen();
961        populateTimezone();
962        populateRepeats();
963        updateAttendees(model.mAttendeesList);
964
965        updateView();
966        mScrollView.setVisibility(View.VISIBLE);
967        mLoadingMessage.setVisibility(View.GONE);
968    }
969
970    /**
971     * Creates a single line string for the time/duration
972     */
973    protected void setWhenString() {
974        String when;
975        int flags = DateUtils.FORMAT_SHOW_DATE;
976        if (mModel.mAllDay) {
977            flags |= DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY;
978        } else {
979            flags |= DateUtils.FORMAT_SHOW_TIME;
980            if (DateFormat.is24HourFormat(mActivity)) {
981                flags |= DateUtils.FORMAT_24HOUR;
982            }
983        }
984        long startMillis = mStartTime.normalize(true);
985        long endMillis = mEndTime.normalize(true);
986        when = Utils.formatDateRange(mActivity, startMillis, endMillis, flags);
987        mWhenView.setText(when);
988    }
989
990    public void setCalendarsCursor(Cursor cursor, boolean userVisible) {
991        // If there are no syncable calendars, then we cannot allow
992        // creating a new event.
993        mCalendarsCursor = cursor;
994        if (cursor == null || cursor.getCount() == 0) {
995            // Cancel the "loading calendars" dialog if it exists
996            if (mSaveAfterQueryComplete) {
997                mLoadingCalendarsDialog.cancel();
998            }
999            if (!userVisible) {
1000                return;
1001            }
1002            // Create an error message for the user that, when clicked,
1003            // will exit this activity without saving the event.
1004            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
1005            builder.setTitle(R.string.no_syncable_calendars)
1006                    .setIcon(android.R.drawable.ic_dialog_alert)
1007                    .setMessage(R.string.no_calendars_found)
1008                    .setPositiveButton(R.string.add_account, this)
1009                    .setNegativeButton(android.R.string.no, this)
1010                    .setOnCancelListener(this);
1011            mNoCalendarsDialog = builder.show();
1012            return;
1013        }
1014
1015        int defaultCalendarPosition = findDefaultCalendarPosition(cursor);
1016
1017        // populate the calendars spinner
1018        CalendarsAdapter adapter = new CalendarsAdapter(mActivity, cursor);
1019        mCalendarsSpinner.setAdapter(adapter);
1020        mCalendarsSpinner.setSelection(defaultCalendarPosition);
1021        mCalendarsSpinner.setOnItemSelectedListener(this);
1022
1023        int colorColumn = cursor.getColumnIndexOrThrow(Calendars.COLOR);
1024        mColorChip.setBackgroundColor(cursor.getInt(colorColumn));
1025
1026
1027        // Find user domain and set it to the validator.
1028        // TODO: we may want to update this validator if the user actually picks
1029        // a different calendar. maybe not. depends on what we want for the
1030        // user experience. this may change when we add support for multiple
1031        // accounts, anyway.
1032        if (mModel != null && mModel.mHasAttendeeData
1033                && cursor.moveToPosition(defaultCalendarPosition)) {
1034            String ownEmail = cursor.getString(EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
1035            if (ownEmail != null) {
1036                String domain = EditEventHelper.extractDomain(ownEmail);
1037                if (domain != null) {
1038                    mEmailValidator = new Rfc822Validator(domain);
1039                    mAttendeesList.setValidator(mEmailValidator);
1040                }
1041            }
1042        }
1043        if (mSaveAfterQueryComplete) {
1044            mLoadingCalendarsDialog.cancel();
1045            if (prepareForSave() && fillModelFromUI()) {
1046                int exit = userVisible ? Utils.DONE_EXIT : 0;
1047                mDone.setDoneCode(Utils.DONE_SAVE | exit);
1048                mDone.run();
1049            } else if (userVisible) {
1050                mDone.setDoneCode(Utils.DONE_EXIT);
1051                mDone.run();
1052            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
1053                Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view");
1054            }
1055            return;
1056        }
1057    }
1058
1059    /**
1060     * Updates the view based on {@link #mModification} and {@link #mModel}
1061     */
1062    public void updateView() {
1063        if (mModel == null) {
1064            return;
1065        }
1066        if (EditEventHelper.canModifyEvent(mModel)) {
1067            setViewStates(mModification);
1068        } else {
1069            setViewStates(Utils.MODIFY_UNINITIALIZED);
1070        }
1071    }
1072
1073    private void setViewStates(int mode) {
1074        // Extra canModify check just in case
1075        if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) {
1076            setWhenString();
1077
1078            for (View v : mViewOnlyList) {
1079                v.setVisibility(View.VISIBLE);
1080            }
1081            for (View v : mEditOnlyList) {
1082                v.setVisibility(View.GONE);
1083            }
1084            for (View v : mEditViewList) {
1085                v.setEnabled(false);
1086                v.setBackgroundColor(0);
1087            }
1088            mCalendarSelectorGroup.setVisibility(View.GONE);
1089            mCalendarStaticGroup.setVisibility(View.VISIBLE);
1090            mRepeatsSpinner.setEnabled(false);
1091            if (EditEventHelper.canAddReminders(mModel)) {
1092                mRemindersGroup.setVisibility(View.VISIBLE);
1093            } else {
1094                mRemindersGroup.setVisibility(View.GONE);
1095            }
1096            setAttendeesEditable(View.GONE);
1097            if (mAttendeesView.getChildCount() == 0) {
1098                mAttendeesPane.setVisibility(View.GONE);
1099            } else {
1100                mAttendeesPane.setVisibility(View.VISIBLE);
1101            }
1102            if (mAllDayCheckBox.isChecked()) {
1103                mView.findViewById(R.id.timezone_textview_row).setVisibility(View.GONE);
1104            }
1105        } else {
1106            for (View v : mViewOnlyList) {
1107                v.setVisibility(View.GONE);
1108            }
1109            for (View v : mEditOnlyList) {
1110                v.setVisibility(View.VISIBLE);
1111            }
1112            for (View v : mEditViewList) {
1113                v.setEnabled(true);
1114                v.setBackgroundDrawable(mOriginalEditBG);
1115            }
1116            if (mModel.mUri == null) {
1117                mCalendarSelectorGroup.setVisibility(View.VISIBLE);
1118                mCalendarStaticGroup.setVisibility(View.GONE);
1119            } else {
1120                mCalendarSelectorGroup.setVisibility(View.GONE);
1121                mCalendarStaticGroup.setVisibility(View.VISIBLE);
1122            }
1123            if (mModel.mOriginalEvent == null) {
1124                mRepeatsSpinner.setEnabled(true);
1125            } else {
1126                mRepeatsSpinner.setEnabled(false);
1127            }
1128            mRemindersGroup.setVisibility(View.VISIBLE);
1129            setAttendeesEditable(View.VISIBLE);
1130            mAttendeesPane.setVisibility(View.VISIBLE);
1131
1132            // force an update of allDay dependent fields
1133            setAllDayViewsVisibility(mAllDayCheckBox.isChecked());
1134        }
1135    }
1136
1137    /**
1138     * Shows or hides the Guests view and sets the buttons for removing
1139     * attendees based on the value passed in.
1140     *
1141     * @param visibility View.GONE or View.VISIBLE
1142     */
1143    protected void setAttendeesEditable(int visibility) {
1144        int attCount = mAttendeesView.getChildCount();
1145        if (attCount > 0) {
1146            mResponseGroup.setVisibility(View.VISIBLE);
1147            mAttendeesGroup.setVisibility(View.VISIBLE);
1148            for (int i = 0; i < attCount; i++) {
1149                View v = mAttendeesView.getChildAt(i);
1150                View button = v.findViewById(R.id.contact_remove);
1151                if (button != null) {
1152                    button.setVisibility(visibility);
1153                }
1154            }
1155        } else {
1156            mResponseGroup.setVisibility(View.GONE);
1157            mAttendeesGroup.setVisibility(View.GONE);
1158        }
1159    }
1160
1161    public void setModification(int modifyWhich) {
1162        mModification = modifyWhich;
1163        updateView();
1164    }
1165
1166    // Find the calendar position in the cursor that matches calendar in
1167    // preference
1168    private int findDefaultCalendarPosition(Cursor calendarsCursor) {
1169        if (calendarsCursor.getCount() <= 0) {
1170            return -1;
1171        }
1172
1173        String defaultCalendar = Utils.getSharedPreference(
1174                mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, null);
1175
1176        if (defaultCalendar == null) {
1177            return 0;
1178        }
1179        int calendarsOwnerColumn = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
1180        int position = 0;
1181        calendarsCursor.moveToPosition(-1);
1182        while (calendarsCursor.moveToNext()) {
1183            if (defaultCalendar.equals(calendarsCursor.getString(calendarsOwnerColumn))) {
1184                return position;
1185            }
1186            position++;
1187        }
1188        return 0;
1189    }
1190
1191    private void updateAttendees(HashMap<String, Attendee> attendeesList) {
1192        mAttendeesView.setRfc822Validator(mEmailValidator);
1193        mAttendeesView.addAttendees(attendeesList);
1194    }
1195
1196    private void updateRemindersVisibility(int numReminders) {
1197        if (numReminders == 0) {
1198            mRemindersContainer.setVisibility(View.GONE);
1199        } else {
1200            mRemindersContainer.setVisibility(View.VISIBLE);
1201        }
1202    }
1203
1204    private void addReminder() {
1205        // TODO: when adding a new reminder, make it different from the
1206        // last one in the list (if any).
1207        if (mDefaultReminderMinutes == 0) {
1208            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1209                    mReminderValues, mReminderLabels, 10 /* minutes */);
1210        } else {
1211            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1212                    mReminderValues, mReminderLabels, mDefaultReminderMinutes);
1213        }
1214        updateRemindersVisibility(mReminderItems.size());
1215        mScrollView.fling(REMINDER_FLING_VELOCITY);
1216    }
1217
1218    // From com.google.android.gm.ComposeActivity
1219    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) {
1220        MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) mView.findViewById(res);
1221        list.setAdapter(mAddressAdapter);
1222        list.setTokenizer(new Rfc822Tokenizer());
1223        list.setValidator(mEmailValidator);
1224
1225        // NOTE: assumes no other filters are set
1226        list.setFilters(sRecipientFilters);
1227
1228        return list;
1229    }
1230
1231    /**
1232     * From com.google.android.gm.ComposeActivity Implements special address
1233     * cleanup rules: The first space key entry following an "@" symbol that is
1234     * followed by any combination of letters and symbols, including one+ dots
1235     * and zero commas, should insert an extra comma (followed by the space).
1236     */
1237    private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
1238
1239    private void setDate(TextView view, long millis) {
1240        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
1241                | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH
1242                | DateUtils.FORMAT_ABBREV_WEEKDAY;
1243
1244        // Unfortunately, DateUtils doesn't support a timezone other than the
1245        // default timezone provided by the system, so we have this ugly hack
1246        // here to trick it into formatting our time correctly. In order to
1247        // prevent all sorts of craziness, we synchronize on the TimeZone class
1248        // to prevent other threads from reading an incorrect timezone from
1249        // calls to TimeZone#getDefault()
1250        // TODO fix this if/when DateUtils allows for passing in a timezone
1251        String dateString;
1252        synchronized (TimeZone.class) {
1253            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1254            dateString = DateUtils.formatDateTime(mActivity, millis, flags);
1255            // setting the default back to null restores the correct behavior
1256            TimeZone.setDefault(null);
1257        }
1258        view.setText(dateString);
1259    }
1260
1261    private void setTime(TextView view, long millis) {
1262        int flags = DateUtils.FORMAT_SHOW_TIME;
1263        if (DateFormat.is24HourFormat(mActivity)) {
1264            flags |= DateUtils.FORMAT_24HOUR;
1265        }
1266
1267        // Unfortunately, DateUtils doesn't support a timezone other than the
1268        // default timezone provided by the system, so we have this ugly hack
1269        // here to trick it into formatting our time correctly. In order to
1270        // prevent all sorts of craziness, we synchronize on the TimeZone class
1271        // to prevent other threads from reading an incorrect timezone from
1272        // calls to TimeZone#getDefault()
1273        // TODO fix this if/when DateUtils allows for passing in a timezone
1274        String timeString;
1275        synchronized (TimeZone.class) {
1276            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1277            timeString = DateUtils.formatDateTime(mActivity, millis, flags);
1278            TimeZone.setDefault(null);
1279        }
1280        view.setText(timeString);
1281    }
1282
1283    private void setTimezone(int i) {
1284        if (i < 0 || i >= mTimezoneAdapter.getCount()) {
1285            return; // do nothing
1286        }
1287        TimezoneRow timezone = mTimezoneAdapter.getItem(i);
1288        mTimezoneTextView.setText(timezone.toString());
1289        mTimezoneButton.setText(timezone.toString());
1290        mTimezone = timezone.mId;
1291        mStartTime.timezone = mTimezone;
1292        mStartTime.normalize(true);
1293        mEndTime.timezone = mTimezone;
1294        mEndTime.normalize(true);
1295        mTimezoneAdapter.setCurrentTimezone(mTimezone);
1296    }
1297
1298    // TextWatcher interface
1299    @Override
1300    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1301    }
1302
1303    // TextWatcher interface
1304    @Override
1305    public void onTextChanged(CharSequence s, int start, int before, int count) {
1306    }
1307
1308    // TextWatcher interface
1309    @Override
1310    public void afterTextChanged(Editable s) {
1311        mAddAttendeesButton.setEnabled(s.length() > 0);
1312    }
1313
1314    /**
1315     * @param isChecked
1316     */
1317    protected void setAllDayViewsVisibility(boolean isChecked) {
1318        if (isChecked) {
1319            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1320                mEndTime.monthDay--;
1321                long endMillis = mEndTime.normalize(true);
1322
1323                // Do not allow an event to have an end time
1324                // before the
1325                // start time.
1326                if (mEndTime.before(mStartTime)) {
1327                    mEndTime.set(mStartTime);
1328                    endMillis = mEndTime.normalize(true);
1329                }
1330                setDate(mEndDateButton, endMillis);
1331                setTime(mEndTimeButton, endMillis);
1332            }
1333
1334            mStartTimeButton.setVisibility(View.GONE);
1335            mEndTimeButton.setVisibility(View.GONE);
1336            mTimezoneButton.setVisibility(View.GONE);
1337            mTimezoneLabel.setVisibility(View.GONE);
1338        } else {
1339            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1340                mEndTime.monthDay++;
1341                long endMillis = mEndTime.normalize(true);
1342                setDate(mEndDateButton, endMillis);
1343                setTime(mEndTimeButton, endMillis);
1344            }
1345            mStartTimeButton.setVisibility(View.VISIBLE);
1346            mEndTimeButton.setVisibility(View.VISIBLE);
1347            mTimezoneButton.setVisibility(View.VISIBLE);
1348            mTimezoneLabel.setVisibility(View.VISIBLE);
1349        }
1350    }
1351
1352    @Override
1353    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1354        Cursor c = (Cursor) parent.getItemAtPosition(position);
1355        if (c != null) {
1356            int colorColumn = c.getColumnIndexOrThrow(Calendars.COLOR);
1357            mColorChip.setBackgroundColor(c.getInt(colorColumn));
1358        }
1359    }
1360
1361    @Override
1362    public void onNothingSelected(AdapterView<?> parent) {
1363        mColorChip.setBackgroundColor(0);
1364
1365    }
1366}
1367