EditEventView.java revision 4afba187f8990ae2b3afaf8fcdb6039f231f4914
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 android.app.Activity;
20import android.app.AlertDialog;
21import android.app.DatePickerDialog;
22import android.app.DatePickerDialog.OnDateSetListener;
23import android.app.FragmentManager;
24import android.app.ProgressDialog;
25import android.app.Service;
26import android.app.TimePickerDialog;
27import android.app.TimePickerDialog.OnTimeSetListener;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.Intent;
31import android.content.SharedPreferences;
32import android.content.res.Resources;
33import android.database.Cursor;
34import android.graphics.drawable.Drawable;
35import android.provider.CalendarContract;
36import android.provider.CalendarContract.Attendees;
37import android.provider.CalendarContract.Calendars;
38import android.provider.CalendarContract.Events;
39import android.provider.CalendarContract.Reminders;
40import android.provider.Settings;
41import android.text.InputFilter;
42import android.text.TextUtils;
43import android.text.format.DateFormat;
44import android.text.format.DateUtils;
45import android.text.format.Time;
46import android.text.util.Rfc822Tokenizer;
47import android.util.Log;
48import android.view.KeyEvent;
49import android.view.LayoutInflater;
50import android.view.View;
51import android.view.View.OnClickListener;
52import android.view.ViewGroup;
53import android.view.accessibility.AccessibilityEvent;
54import android.view.accessibility.AccessibilityManager;
55import android.view.inputmethod.EditorInfo;
56import android.widget.AdapterView;
57import android.widget.AdapterView.OnItemSelectedListener;
58import android.widget.ArrayAdapter;
59import android.widget.AutoCompleteTextView;
60import android.widget.Button;
61import android.widget.CalendarView;
62import android.widget.CheckBox;
63import android.widget.CompoundButton;
64import android.widget.DatePicker;
65import android.widget.ImageButton;
66import android.widget.LinearLayout;
67import android.widget.MultiAutoCompleteTextView;
68import android.widget.RadioButton;
69import android.widget.RadioGroup;
70import android.widget.ResourceCursorAdapter;
71import android.widget.ScrollView;
72import android.widget.Spinner;
73import android.widget.TextView;
74import android.widget.TextView.OnEditorActionListener;
75import android.widget.TimePicker;
76
77import com.android.calendar.CalendarEventModel;
78import com.android.calendar.CalendarEventModel.Attendee;
79import com.android.calendar.CalendarEventModel.ReminderEntry;
80import com.android.calendar.EmailAddressAdapter;
81import com.android.calendar.EventInfoFragment;
82import com.android.calendar.GeneralPreferences;
83import com.android.calendar.R;
84import com.android.calendar.RecipientAdapter;
85import com.android.calendar.TimezoneAdapter;
86import com.android.calendar.TimezoneAdapter.TimezoneRow;
87import com.android.calendar.Utils;
88import com.android.calendar.color.ColorPickerSwatch.OnColorSelectedListener;
89import com.android.calendar.event.EditEventHelper.EditDoneRunnable;
90import com.android.calendarcommon2.EventRecurrence;
91import com.android.common.Rfc822InputFilter;
92import com.android.common.Rfc822Validator;
93import com.android.ex.chips.AccountSpecifier;
94import com.android.ex.chips.BaseRecipientAdapter;
95import com.android.ex.chips.ChipsUtil;
96import com.android.ex.chips.RecipientEditTextView;
97
98import java.util.ArrayList;
99import java.util.Arrays;
100import java.util.Calendar;
101import java.util.Formatter;
102import java.util.HashMap;
103import java.util.Locale;
104import java.util.TimeZone;
105
106public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener,
107        DialogInterface.OnClickListener, OnItemSelectedListener {
108    private static final String TAG = "EditEvent";
109    private static final String GOOGLE_SECONDARY_CALENDAR = "calendar.google.com";
110    private static final String PERIOD_SPACE = ". ";
111
112    ArrayList<View> mEditOnlyList = new ArrayList<View>();
113    ArrayList<View> mEditViewList = new ArrayList<View>();
114    ArrayList<View> mViewOnlyList = new ArrayList<View>();
115    TextView mLoadingMessage;
116    ScrollView mScrollView;
117    Button mStartDateButton;
118    Button mEndDateButton;
119    Button mStartTimeButton;
120    Button mEndTimeButton;
121    Button mTimezoneButton;
122    View mColorPickerNewEvent;
123    View mColorPickerExistingEvent;
124    OnClickListener mChangeColorOnClickListener;
125    View mTimezoneRow;
126    TextView mStartTimeHome;
127    TextView mStartDateHome;
128    TextView mEndTimeHome;
129    TextView mEndDateHome;
130    CheckBox mAllDayCheckBox;
131    Spinner mCalendarsSpinner;
132    Spinner mRepeatsSpinner;
133    Spinner mAvailabilitySpinner;
134    Spinner mAccessLevelSpinner;
135    RadioGroup mResponseRadioGroup;
136    TextView mTitleTextView;
137    AutoCompleteTextView mLocationTextView;
138    EventLocationAdapter mLocationAdapter;
139    TextView mDescriptionTextView;
140    TextView mWhenView;
141    TextView mTimezoneTextView;
142    TextView mTimezoneLabel;
143    LinearLayout mRemindersContainer;
144    MultiAutoCompleteTextView mAttendeesList;
145    View mCalendarSelectorGroup;
146    View mCalendarSelectorWrapper;
147    View mCalendarStaticGroup;
148    View mLocationGroup;
149    View mDescriptionGroup;
150    View mRemindersGroup;
151    View mResponseGroup;
152    View mOrganizerGroup;
153    View mAttendeesGroup;
154    View mStartHomeGroup;
155    View mEndHomeGroup;
156
157    private int[] mOriginalPadding = new int[4];
158    private int[] mOriginalSpinnerPadding = new int[4];
159
160    public boolean mIsMultipane;
161    private ProgressDialog mLoadingCalendarsDialog;
162    private AlertDialog mNoCalendarsDialog;
163    private AlertDialog mTimezoneDialog;
164    private Activity mActivity;
165    private EditDoneRunnable mDone;
166    private View mView;
167    private CalendarEventModel mModel;
168    private Cursor mCalendarsCursor;
169    private AccountSpecifier mAddressAdapter;
170    private Rfc822Validator mEmailValidator;
171    private TimezoneAdapter mTimezoneAdapter;
172
173    private ArrayList<Integer> mRecurrenceIndexes = new ArrayList<Integer>(0);
174
175    /**
176     * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
177     * with any additional values that were already associated with the event.
178     */
179    private ArrayList<Integer> mReminderMinuteValues;
180    private ArrayList<String> mReminderMinuteLabels;
181
182    /**
183     * Contents of the "methods" spinner.  The "values" list specifies the method constant
184     * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
185     * aren't allowed by the Calendar will be removed.
186     */
187    private ArrayList<Integer> mReminderMethodValues;
188    private ArrayList<String> mReminderMethodLabels;
189
190    /**
191     * Contents of the "availability" spinner. The "values" list specifies the
192     * type constant (e.g. {@link Events#AVAILABILITY_BUSY}) associated with the
193     * labels. Any types that aren't allowed by the Calendar will be removed.
194     */
195    private ArrayList<Integer> mAvailabilityValues;
196    private ArrayList<String> mAvailabilityLabels;
197
198    private int mDefaultReminderMinutes;
199
200    private boolean mSaveAfterQueryComplete = false;
201
202    private Time mStartTime;
203    private Time mEndTime;
204    private String mTimezone;
205    private boolean mAllDay = false;
206    private int mModification = EditEventHelper.MODIFY_UNINITIALIZED;
207
208    private EventRecurrence mEventRecurrence = new EventRecurrence();
209
210    private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
211    private ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
212
213    private static StringBuilder mSB = new StringBuilder(50);
214    private static Formatter mF = new Formatter(mSB, Locale.getDefault());
215
216    /* This class is used to update the time buttons. */
217    private class TimeListener implements OnTimeSetListener {
218        private View mView;
219
220        public TimeListener(View view) {
221            mView = view;
222        }
223
224        @Override
225        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
226            // Cache the member variables locally to avoid inner class overhead.
227            Time startTime = mStartTime;
228            Time endTime = mEndTime;
229
230            // Cache the start and end millis so that we limit the number
231            // of calls to normalize() and toMillis(), which are fairly
232            // expensive.
233            long startMillis;
234            long endMillis;
235            if (mView == mStartTimeButton) {
236                // The start time was changed.
237                int hourDuration = endTime.hour - startTime.hour;
238                int minuteDuration = endTime.minute - startTime.minute;
239
240                startTime.hour = hourOfDay;
241                startTime.minute = minute;
242                startMillis = startTime.normalize(true);
243
244                // Also update the end time to keep the duration constant.
245                endTime.hour = hourOfDay + hourDuration;
246                endTime.minute = minute + minuteDuration;
247
248                // Update tz in case the start time switched from/to DLS
249                populateTimezone(startMillis);
250            } else {
251                // The end time was changed.
252                startMillis = startTime.toMillis(true);
253                endTime.hour = hourOfDay;
254                endTime.minute = minute;
255
256                // Move to the start time if the end time is before the start
257                // time.
258                if (endTime.before(startTime)) {
259                    endTime.monthDay = startTime.monthDay + 1;
260                }
261                // Call populateTimezone if we support end time zone as well
262            }
263
264            endMillis = endTime.normalize(true);
265
266            setDate(mEndDateButton, endMillis);
267            setTime(mStartTimeButton, startMillis);
268            setTime(mEndTimeButton, endMillis);
269            updateHomeTime();
270        }
271    }
272
273    private class TimeClickListener implements View.OnClickListener {
274        private Time mTime;
275
276        public TimeClickListener(Time time) {
277            mTime = time;
278        }
279
280        @Override
281        public void onClick(View v) {
282            TimePickerDialog tp = new TimePickerDialog(mActivity, new TimeListener(v), mTime.hour,
283                    mTime.minute, DateFormat.is24HourFormat(mActivity));
284            tp.setCanceledOnTouchOutside(true);
285            tp.show();
286        }
287    }
288
289    private class DateListener implements OnDateSetListener {
290        View mView;
291
292        public DateListener(View view) {
293            mView = view;
294        }
295
296        @Override
297        public void onDateSet(DatePicker view, int year, int month, int monthDay) {
298            Log.d(TAG, "onDateSet: " + year +  " " + month +  " " + monthDay);
299            // Cache the member variables locally to avoid inner class overhead.
300            Time startTime = mStartTime;
301            Time endTime = mEndTime;
302
303            // Cache the start and end millis so that we limit the number
304            // of calls to normalize() and toMillis(), which are fairly
305            // expensive.
306            long startMillis;
307            long endMillis;
308            if (mView == mStartDateButton) {
309                // The start date was changed.
310                int yearDuration = endTime.year - startTime.year;
311                int monthDuration = endTime.month - startTime.month;
312                int monthDayDuration = endTime.monthDay - startTime.monthDay;
313
314                startTime.year = year;
315                startTime.month = month;
316                startTime.monthDay = monthDay;
317                startMillis = startTime.normalize(true);
318
319                // Also update the end date to keep the duration constant.
320                endTime.year = year + yearDuration;
321                endTime.month = month + monthDuration;
322                endTime.monthDay = monthDay + monthDayDuration;
323                endMillis = endTime.normalize(true);
324
325                // If the start date has changed then update the repeats.
326                populateRepeats();
327
328                // Update tz in case the start time switched from/to DLS
329                populateTimezone(startMillis);
330            } else {
331                // The end date was changed.
332                startMillis = startTime.toMillis(true);
333                endTime.year = year;
334                endTime.month = month;
335                endTime.monthDay = monthDay;
336                endMillis = endTime.normalize(true);
337
338                // Do not allow an event to have an end time before the start
339                // time.
340                if (endTime.before(startTime)) {
341                    endTime.set(startTime);
342                    endMillis = startMillis;
343                }
344                // Call populateTimezone if we support end time zone as well
345            }
346
347            setDate(mStartDateButton, startMillis);
348            setDate(mEndDateButton, endMillis);
349            setTime(mEndTimeButton, endMillis); // In case end time had to be
350            // reset
351            updateHomeTime();
352        }
353    }
354
355    // Fills in the date and time fields
356    private void populateWhen() {
357        long startMillis = mStartTime.toMillis(false /* use isDst */);
358        long endMillis = mEndTime.toMillis(false /* use isDst */);
359        setDate(mStartDateButton, startMillis);
360        setDate(mEndDateButton, endMillis);
361
362        setTime(mStartTimeButton, startMillis);
363        setTime(mEndTimeButton, endMillis);
364
365        mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
366        mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
367
368        mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
369        mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
370    }
371
372    private void populateTimezone(long eventStartTime) {
373        if (mTimezoneAdapter == null) {
374            mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone, eventStartTime);
375        } else {
376            mTimezoneAdapter.setTime(eventStartTime);
377        }
378
379        if (mTimezoneDialog != null) {
380            mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter);
381        }
382
383        mTimezoneButton.setOnClickListener(new View.OnClickListener() {
384            @Override
385            public void onClick(View v) {
386                showTimezoneDialog();
387            }
388        });
389        setTimezone(mTimezoneAdapter.getRowById(mTimezone));
390    }
391
392    private void showTimezoneDialog() {
393        AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
394        final Context alertDialogContext = builder.getContext();
395        builder.setTitle(R.string.timezone_label);
396        builder.setSingleChoiceItems(
397                mTimezoneAdapter, mTimezoneAdapter.getRowById(mTimezone), this);
398        mTimezoneDialog = builder.create();
399
400        LayoutInflater layoutInflater = (LayoutInflater) alertDialogContext
401                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
402        final TextView timezoneFooterView = (TextView) layoutInflater.inflate(
403                R.layout.timezone_footer, null);
404
405        timezoneFooterView.setText(mActivity.getString(R.string.edit_event_show_all) + " >");
406        timezoneFooterView.setOnClickListener(new View.OnClickListener() {
407            @Override
408            public void onClick(View v) {
409                mTimezoneDialog.getListView().removeFooterView(timezoneFooterView);
410                mTimezoneAdapter.showAllTimezones();
411                final int row = mTimezoneAdapter.getRowById(mTimezone);
412                // we need to post the selection changes to have them have
413                // any effect
414                mTimezoneDialog.getListView().post(new Runnable() {
415                    @Override
416                    public void run() {
417                        mTimezoneDialog.getListView().setItemChecked(row, true);
418                        mTimezoneDialog.getListView().setSelection(row);
419                    }
420                });
421            }
422        });
423        mTimezoneDialog.getListView().addFooterView(timezoneFooterView);
424        mTimezoneDialog.setCanceledOnTouchOutside(true);
425        mTimezoneDialog.show();
426    }
427
428    private void populateRepeats() {
429        Time time = mStartTime;
430        Resources r = mActivity.getResources();
431
432        String[] days = new String[] {
433                DateUtils.getDayOfWeekString(Calendar.SUNDAY, DateUtils.LENGTH_MEDIUM),
434                DateUtils.getDayOfWeekString(Calendar.MONDAY, DateUtils.LENGTH_MEDIUM),
435                DateUtils.getDayOfWeekString(Calendar.TUESDAY, DateUtils.LENGTH_MEDIUM),
436                DateUtils.getDayOfWeekString(Calendar.WEDNESDAY, DateUtils.LENGTH_MEDIUM),
437                DateUtils.getDayOfWeekString(Calendar.THURSDAY, DateUtils.LENGTH_MEDIUM),
438                DateUtils.getDayOfWeekString(Calendar.FRIDAY, DateUtils.LENGTH_MEDIUM),
439                DateUtils.getDayOfWeekString(Calendar.SATURDAY, DateUtils.LENGTH_MEDIUM), };
440        String[] ordinals = r.getStringArray(R.array.ordinal_labels);
441
442        // Only display "Custom" in the spinner if the device does not support
443        // the recurrence functionality of the event. Only display every weekday
444        // if the event starts on a weekday.
445        boolean isCustomRecurrence = isCustomRecurrence();
446        boolean isWeekdayEvent = isWeekdayEvent();
447
448        ArrayList<String> repeatArray = new ArrayList<String>(0);
449        ArrayList<Integer> recurrenceIndexes = new ArrayList<Integer>(0);
450
451        repeatArray.add(r.getString(R.string.does_not_repeat));
452        recurrenceIndexes.add(EditEventHelper.DOES_NOT_REPEAT);
453
454        repeatArray.add(r.getString(R.string.daily));
455        recurrenceIndexes.add(EditEventHelper.REPEATS_DAILY);
456
457        if (isWeekdayEvent) {
458            repeatArray.add(r.getString(R.string.every_weekday));
459            recurrenceIndexes.add(EditEventHelper.REPEATS_EVERY_WEEKDAY);
460        }
461
462        String format = r.getString(R.string.weekly);
463        repeatArray.add(String.format(format, time.format("%A")));
464        recurrenceIndexes.add(EditEventHelper.REPEATS_WEEKLY_ON_DAY);
465
466        // Calculate whether this is the 1st, 2nd, 3rd, 4th, or last appearance
467        // of the given day.
468        int dayNumber = (time.monthDay - 1) / 7;
469        format = r.getString(R.string.monthly_on_day_count);
470        repeatArray.add(String.format(format, ordinals[dayNumber], days[time.weekDay]));
471        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
472
473        format = r.getString(R.string.monthly_on_day);
474        repeatArray.add(String.format(format, time.monthDay));
475        recurrenceIndexes.add(EditEventHelper.REPEATS_MONTHLY_ON_DAY);
476
477        long when = time.toMillis(false);
478        format = r.getString(R.string.yearly);
479        int flags = 0;
480        if (DateFormat.is24HourFormat(mActivity)) {
481            flags |= DateUtils.FORMAT_24HOUR;
482        }
483        repeatArray.add(String.format(format, DateUtils.formatDateTime(mActivity, when, flags)));
484        recurrenceIndexes.add(EditEventHelper.REPEATS_YEARLY);
485
486        if (isCustomRecurrence) {
487            repeatArray.add(r.getString(R.string.custom));
488            recurrenceIndexes.add(EditEventHelper.REPEATS_CUSTOM);
489        }
490        mRecurrenceIndexes = recurrenceIndexes;
491
492        int position = recurrenceIndexes.indexOf(EditEventHelper.DOES_NOT_REPEAT);
493        if (!TextUtils.isEmpty(mModel.mRrule)) {
494            if (isCustomRecurrence) {
495                position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_CUSTOM);
496            } else {
497                switch (mEventRecurrence.freq) {
498                    case EventRecurrence.DAILY:
499                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_DAILY);
500                        break;
501                    case EventRecurrence.WEEKLY:
502                        if (mEventRecurrence.repeatsOnEveryWeekDay()) {
503                            position = recurrenceIndexes.indexOf(
504                                    EditEventHelper.REPEATS_EVERY_WEEKDAY);
505                        } else {
506                            position = recurrenceIndexes.indexOf(
507                                    EditEventHelper.REPEATS_WEEKLY_ON_DAY);
508                        }
509                        break;
510                    case EventRecurrence.MONTHLY:
511                        if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
512                            position = recurrenceIndexes.indexOf(
513                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY_COUNT);
514                        } else {
515                            position = recurrenceIndexes.indexOf(
516                                    EditEventHelper.REPEATS_MONTHLY_ON_DAY);
517                        }
518                        break;
519                    case EventRecurrence.YEARLY:
520                        position = recurrenceIndexes.indexOf(EditEventHelper.REPEATS_YEARLY);
521                        break;
522                }
523            }
524        }
525        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
526                android.R.layout.simple_spinner_item, repeatArray);
527        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
528        mRepeatsSpinner.setAdapter(adapter);
529        mRepeatsSpinner.setSelection(position);
530
531        // Don't allow the user to make exceptions recurring events.
532        if (mModel.mOriginalSyncId != null) {
533            mRepeatsSpinner.setEnabled(false);
534        }
535    }
536
537    private boolean isCustomRecurrence() {
538
539        if (mEventRecurrence.until != null
540                || (mEventRecurrence.interval != 0 && mEventRecurrence.interval != 1)
541                || mEventRecurrence.count != 0) {
542            return true;
543        }
544
545        if (mEventRecurrence.freq == 0) {
546            return false;
547        }
548
549        switch (mEventRecurrence.freq) {
550            case EventRecurrence.DAILY:
551                return false;
552            case EventRecurrence.WEEKLY:
553                if (mEventRecurrence.repeatsOnEveryWeekDay() && isWeekdayEvent()) {
554                    return false;
555                } else if (mEventRecurrence.bydayCount == 1) {
556                    return false;
557                }
558                break;
559            case EventRecurrence.MONTHLY:
560                if (mEventRecurrence.repeatsMonthlyOnDayCount()) {
561                    /* this is a "3rd Tuesday of every month" sort of rule */
562                    return false;
563                } else if (mEventRecurrence.bydayCount == 0
564                        && mEventRecurrence.bymonthdayCount == 1
565                        && mEventRecurrence.bymonthday[0] > 0) {
566                    /* this is a "22nd day of every month" sort of rule */
567                    return false;
568                }
569                break;
570            case EventRecurrence.YEARLY:
571                return false;
572        }
573
574        return true;
575    }
576
577    private boolean isWeekdayEvent() {
578        if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
579            return true;
580        }
581        return false;
582    }
583
584    private class DateClickListener implements View.OnClickListener {
585        private Time mTime;
586
587        public DateClickListener(Time time) {
588            mTime = time;
589        }
590
591        public void onClick(View v) {
592            DatePickerDialog dpd = new DatePickerDialog(
593                    mActivity, new DateListener(v), mTime.year, mTime.month, mTime.monthDay);
594            CalendarView cv = dpd.getDatePicker().getCalendarView();
595            cv.setShowWeekNumber(Utils.getShowWeekNumber(mActivity));
596            int startOfWeek = Utils.getFirstDayOfWeek(mActivity);
597            // Utils returns Time days while CalendarView wants Calendar days
598            if (startOfWeek == Time.SATURDAY) {
599                startOfWeek = Calendar.SATURDAY;
600            } else if (startOfWeek == Time.SUNDAY) {
601                startOfWeek = Calendar.SUNDAY;
602            } else {
603                startOfWeek = Calendar.MONDAY;
604            }
605            cv.setFirstDayOfWeek(startOfWeek);
606            dpd.setCanceledOnTouchOutside(true);
607            dpd.show();
608        }
609    }
610
611    public static class CalendarsAdapter extends ResourceCursorAdapter {
612        public CalendarsAdapter(Context context, int resourceId, Cursor c) {
613            super(context, resourceId, c);
614            setDropDownViewResource(R.layout.calendars_dropdown_item);
615        }
616
617        @Override
618        public void bindView(View view, Context context, Cursor cursor) {
619            View colorBar = view.findViewById(R.id.color);
620            int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
621            int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME);
622            int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
623            if (colorBar != null) {
624                colorBar.setBackgroundColor(Utils.getDisplayColorFromColor(cursor
625                        .getInt(colorColumn)));
626            }
627
628            TextView name = (TextView) view.findViewById(R.id.calendar_name);
629            if (name != null) {
630                String displayName = cursor.getString(nameColumn);
631                name.setText(displayName);
632
633                TextView accountName = (TextView) view.findViewById(R.id.account_name);
634                if (accountName != null) {
635                    accountName.setText(cursor.getString(ownerColumn));
636                    accountName.setVisibility(TextView.VISIBLE);
637                }
638            }
639        }
640    }
641
642    /**
643     * Does prep steps for saving a calendar event.
644     *
645     * This triggers a parse of the attendees list and checks if the event is
646     * ready to be saved. An event is ready to be saved so long as a model
647     * exists and has a calendar it can be associated with, either because it's
648     * an existing event or we've finished querying.
649     *
650     * @return false if there is no model or no calendar had been loaded yet,
651     * true otherwise.
652     */
653    public boolean prepareForSave() {
654        if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
655            return false;
656        }
657        return fillModelFromUI();
658    }
659
660    public boolean fillModelFromReadOnlyUi() {
661        if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
662            return false;
663        }
664        mModel.mReminders = EventViewUtils.reminderItemsToReminders(
665                    mReminderItems, mReminderMinuteValues, mReminderMethodValues);
666        mModel.mReminders.addAll(mUnsupportedReminders);
667        mModel.normalizeReminders();
668        int status = EventInfoFragment.getResponseFromButtonId(
669                mResponseRadioGroup.getCheckedRadioButtonId());
670        if (status != Attendees.ATTENDEE_STATUS_NONE) {
671            mModel.mSelfAttendeeStatus = status;
672        }
673        return true;
674    }
675
676    // This is called if the user clicks on one of the buttons: "Save",
677    // "Discard", or "Delete". This is also called if the user clicks
678    // on the "remove reminder" button.
679    @Override
680    public void onClick(View view) {
681
682        // This must be a click on one of the "remove reminder" buttons
683        LinearLayout reminderItem = (LinearLayout) view.getParent();
684        LinearLayout parent = (LinearLayout) reminderItem.getParent();
685        parent.removeView(reminderItem);
686        mReminderItems.remove(reminderItem);
687        updateRemindersVisibility(mReminderItems.size());
688        EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
689    }
690
691    // This is called if the user cancels the "No calendars" dialog.
692    // The "No calendars" dialog is shown if there are no syncable calendars.
693    @Override
694    public void onCancel(DialogInterface dialog) {
695        if (dialog == mLoadingCalendarsDialog) {
696            mLoadingCalendarsDialog = null;
697            mSaveAfterQueryComplete = false;
698        } else if (dialog == mNoCalendarsDialog) {
699            mDone.setDoneCode(Utils.DONE_REVERT);
700            mDone.run();
701            return;
702        }
703    }
704
705    // This is called if the user clicks on a dialog button.
706    @Override
707    public void onClick(DialogInterface dialog, int which) {
708        if (dialog == mNoCalendarsDialog) {
709            mDone.setDoneCode(Utils.DONE_REVERT);
710            mDone.run();
711            if (which == DialogInterface.BUTTON_POSITIVE) {
712                Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
713                final String[] array = {"com.android.calendar"};
714                nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
715                nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
716                mActivity.startActivity(nextIntent);
717            }
718        } else if (dialog == mTimezoneDialog) {
719            if (which >= 0 && which < mTimezoneAdapter.getCount()) {
720                setTimezone(which);
721                updateHomeTime();
722                dialog.dismiss();
723            }
724        }
725    }
726
727    // Goes through the UI elements and updates the model as necessary
728    private boolean fillModelFromUI() {
729        if (mModel == null) {
730            return false;
731        }
732        mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems,
733                mReminderMinuteValues, mReminderMethodValues);
734        mModel.mReminders.addAll(mUnsupportedReminders);
735        mModel.normalizeReminders();
736        mModel.mHasAlarm = mReminderItems.size() > 0;
737        mModel.mTitle = mTitleTextView.getText().toString();
738        mModel.mAllDay = mAllDayCheckBox.isChecked();
739        mModel.mLocation = mLocationTextView.getText().toString();
740        mModel.mDescription = mDescriptionTextView.getText().toString();
741        if (TextUtils.isEmpty(mModel.mLocation)) {
742            mModel.mLocation = null;
743        }
744        if (TextUtils.isEmpty(mModel.mDescription)) {
745            mModel.mDescription = null;
746        }
747
748        int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup
749                .getCheckedRadioButtonId());
750        if (status != Attendees.ATTENDEE_STATUS_NONE) {
751            mModel.mSelfAttendeeStatus = status;
752        }
753
754        if (mAttendeesList != null) {
755            mEmailValidator.setRemoveInvalid(true);
756            mAttendeesList.performValidation();
757            mModel.mAttendeesList.clear();
758            mModel.addAttendees(mAttendeesList.getText().toString(), mEmailValidator);
759            mEmailValidator.setRemoveInvalid(false);
760        }
761
762        // If this was a new event we need to fill in the Calendar information
763        if (mModel.mUri == null) {
764            mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId();
765            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
766            if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
767                String defaultCalendar = mCalendarsCursor.getString(
768                        EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
769                Utils.setSharedPreference(
770                        mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar);
771                mModel.mOwnerAccount = defaultCalendar;
772                mModel.mOrganizer = defaultCalendar;
773                mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID);
774            }
775        }
776
777        if (mModel.mAllDay) {
778            // Reset start and end time, increment the monthDay by 1, and set
779            // the timezone to UTC, as required for all-day events.
780            mTimezone = Time.TIMEZONE_UTC;
781            mStartTime.hour = 0;
782            mStartTime.minute = 0;
783            mStartTime.second = 0;
784            mStartTime.timezone = mTimezone;
785            mModel.mStart = mStartTime.normalize(true);
786
787            mEndTime.hour = 0;
788            mEndTime.minute = 0;
789            mEndTime.second = 0;
790            mEndTime.timezone = mTimezone;
791            // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time
792            // should be Y + 1 (Oct.30).
793            final long normalizedEndTimeMillis =
794                    mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS;
795            if (normalizedEndTimeMillis < mModel.mStart) {
796                // mEnd should be midnight of the next day of mStart.
797                mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS;
798            } else {
799                mModel.mEnd = normalizedEndTimeMillis;
800            }
801        } else {
802            mStartTime.timezone = mTimezone;
803            mEndTime.timezone = mTimezone;
804            mModel.mStart = mStartTime.toMillis(true);
805            mModel.mEnd = mEndTime.toMillis(true);
806        }
807        mModel.mTimezone = mTimezone;
808        mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition();
809        // TODO set correct availability value
810        mModel.mAvailability = mAvailabilityValues.get(mAvailabilitySpinner
811                .getSelectedItemPosition());
812
813        int selection;
814        // If we're making an exception we don't want it to be a repeating
815        // event.
816        if (mModification == EditEventHelper.MODIFY_SELECTED) {
817            selection = EditEventHelper.DOES_NOT_REPEAT;
818        } else {
819            int position = mRepeatsSpinner.getSelectedItemPosition();
820            selection = mRecurrenceIndexes.get(position);
821        }
822
823        EditEventHelper.updateRecurrenceRule(
824                selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1);
825
826        // Save the timezone so we can display it as a standard option next time
827        if (!mModel.mAllDay) {
828            mTimezoneAdapter.saveRecentTimezone(mTimezone);
829        }
830        return true;
831    }
832
833    public EditEventView(Activity activity, View view, EditDoneRunnable done) {
834
835        mActivity = activity;
836        mView = view;
837        mDone = done;
838
839        // cache top level view elements
840        mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
841        mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
842
843        // cache all the widgets
844        mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner);
845        mTitleTextView = (TextView) view.findViewById(R.id.title);
846        mLocationTextView = (AutoCompleteTextView) view.findViewById(R.id.location);
847        mDescriptionTextView = (TextView) view.findViewById(R.id.description);
848        mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label);
849        mStartDateButton = (Button) view.findViewById(R.id.start_date);
850        mEndDateButton = (Button) view.findViewById(R.id.end_date);
851        mWhenView = (TextView) mView.findViewById(R.id.when);
852        mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView);
853        mStartTimeButton = (Button) view.findViewById(R.id.start_time);
854        mEndTimeButton = (Button) view.findViewById(R.id.end_time);
855        mTimezoneButton = (Button) view.findViewById(R.id.timezone_button);
856        mTimezoneRow = view.findViewById(R.id.timezone_button_row);
857        mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz);
858        mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz);
859        mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz);
860        mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz);
861        mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day);
862        mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats);
863        mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability);
864        mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility);
865        mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group);
866        mCalendarSelectorWrapper = view.findViewById(R.id.calendar_selector_wrapper);
867        mCalendarStaticGroup = view.findViewById(R.id.calendar_group);
868        mRemindersGroup = view.findViewById(R.id.reminders_row);
869        mResponseGroup = view.findViewById(R.id.response_row);
870        mOrganizerGroup = view.findViewById(R.id.organizer_row);
871        mAttendeesGroup = view.findViewById(R.id.add_attendees_row);
872        mLocationGroup = view.findViewById(R.id.where_row);
873        mDescriptionGroup = view.findViewById(R.id.description_row);
874        mStartHomeGroup = view.findViewById(R.id.from_row_home_tz);
875        mEndHomeGroup = view.findViewById(R.id.to_row_home_tz);
876        mAttendeesList = (MultiAutoCompleteTextView) view.findViewById(R.id.attendees);
877
878        mColorPickerNewEvent = view.findViewById(R.id.change_color_new_event);
879        mColorPickerExistingEvent = view.findViewById(R.id.change_color_existing_event);
880
881        mTitleTextView.setTag(mTitleTextView.getBackground());
882        mLocationTextView.setTag(mLocationTextView.getBackground());
883        mLocationAdapter = new EventLocationAdapter(activity);
884        mLocationTextView.setAdapter(mLocationAdapter);
885        mLocationTextView.setOnEditorActionListener(new OnEditorActionListener() {
886            @Override
887            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
888                if (actionId == EditorInfo.IME_ACTION_DONE) {
889                    // Dismiss the suggestions dropdown.  Return false so the other
890                    // side effects still occur (soft keyboard going away, etc.).
891                    mLocationTextView.dismissDropDown();
892                }
893                return false;
894            }
895        });
896
897
898        mDescriptionTextView.setTag(mDescriptionTextView.getBackground());
899        mRepeatsSpinner.setTag(mRepeatsSpinner.getBackground());
900        mAttendeesList.setTag(mAttendeesList.getBackground());
901        mOriginalPadding[0] = mLocationTextView.getPaddingLeft();
902        mOriginalPadding[1] = mLocationTextView.getPaddingTop();
903        mOriginalPadding[2] = mLocationTextView.getPaddingRight();
904        mOriginalPadding[3] = mLocationTextView.getPaddingBottom();
905        mOriginalSpinnerPadding[0] = mRepeatsSpinner.getPaddingLeft();
906        mOriginalSpinnerPadding[1] = mRepeatsSpinner.getPaddingTop();
907        mOriginalSpinnerPadding[2] = mRepeatsSpinner.getPaddingRight();
908        mOriginalSpinnerPadding[3] = mRepeatsSpinner.getPaddingBottom();
909        mEditViewList.add(mTitleTextView);
910        mEditViewList.add(mLocationTextView);
911        mEditViewList.add(mDescriptionTextView);
912        mEditViewList.add(mAttendeesList);
913
914        mViewOnlyList.add(view.findViewById(R.id.when_row));
915        mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row));
916
917        mEditOnlyList.add(view.findViewById(R.id.all_day_row));
918        mEditOnlyList.add(view.findViewById(R.id.availability_row));
919        mEditOnlyList.add(view.findViewById(R.id.visibility_row));
920        mEditOnlyList.add(view.findViewById(R.id.from_row));
921        mEditOnlyList.add(view.findViewById(R.id.to_row));
922        mEditOnlyList.add(mTimezoneRow);
923        mEditOnlyList.add(mStartHomeGroup);
924        mEditOnlyList.add(mEndHomeGroup);
925
926        mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value);
927        mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
928
929        mTimezone = Utils.getTimeZone(activity, null);
930        mIsMultipane = activity.getResources().getBoolean(R.bool.tablet_config);
931        mStartTime = new Time(mTimezone);
932        mEndTime = new Time(mTimezone);
933        mEmailValidator = new Rfc822Validator(null);
934        initMultiAutoCompleteTextView((RecipientEditTextView) mAttendeesList);
935
936        // Display loading screen
937        setModel(null);
938    }
939
940
941    /**
942     * Loads an integer array asset into a list.
943     */
944    private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
945        int[] vals = r.getIntArray(resNum);
946        int size = vals.length;
947        ArrayList<Integer> list = new ArrayList<Integer>(size);
948
949        for (int i = 0; i < size; i++) {
950            list.add(vals[i]);
951        }
952
953        return list;
954    }
955
956    /**
957     * Loads a String array asset into a list.
958     */
959    private static ArrayList<String> loadStringArray(Resources r, int resNum) {
960        String[] labels = r.getStringArray(resNum);
961        ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
962        return list;
963    }
964
965    private void prepareAvailability() {
966        Resources r = mActivity.getResources();
967
968        mAvailabilityValues = loadIntegerArray(r, R.array.availability_values);
969        mAvailabilityLabels = loadStringArray(r, R.array.availability);
970
971        if (mModel.mCalendarAllowedAvailability != null) {
972            EventViewUtils.reduceMethodList(mAvailabilityValues, mAvailabilityLabels,
973                    mModel.mCalendarAllowedAvailability);
974        }
975
976        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
977                android.R.layout.simple_spinner_item, mAvailabilityLabels);
978        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
979        mAvailabilitySpinner.setAdapter(adapter);
980    }
981
982    /**
983     * Prepares the reminder UI elements.
984     * <p>
985     * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as
986     * needed for the current set of reminders and calendar properties, and then creates UI
987     * elements.
988     */
989    private void prepareReminders() {
990        CalendarEventModel model = mModel;
991        Resources r = mActivity.getResources();
992
993        // Load the labels and corresponding numeric values for the minutes and methods lists
994        // from the assets.  If we're switching calendars, we need to clear and re-populate the
995        // lists (which may have elements added and removed based on calendar properties).  This
996        // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
997        // new event that aren't in the default set.
998        mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
999        mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
1000        mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
1001        mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
1002
1003        // Remove any reminder methods that aren't allowed for this calendar.  If this is
1004        // a new event, mCalendarAllowedReminders may not be set the first time we're called.
1005        if (mModel.mCalendarAllowedReminders != null) {
1006            EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
1007                    mModel.mCalendarAllowedReminders);
1008        }
1009
1010        int numReminders = 0;
1011        if (model.mHasAlarm) {
1012            ArrayList<ReminderEntry> reminders = model.mReminders;
1013            numReminders = reminders.size();
1014            // Insert any minute values that aren't represented in the minutes list.
1015            for (ReminderEntry re : reminders) {
1016                if (mReminderMethodValues.contains(re.getMethod())) {
1017                    EventViewUtils.addMinutesToList(mActivity, mReminderMinuteValues,
1018                            mReminderMinuteLabels, re.getMinutes());
1019                }
1020            }
1021
1022            // Create a UI element for each reminder.  We display all of the reminders we get
1023            // from the provider, even if the count exceeds the calendar maximum.  (Also, for
1024            // a new event, we won't have a maxReminders value available.)
1025            mUnsupportedReminders.clear();
1026            for (ReminderEntry re : reminders) {
1027                if (mReminderMethodValues.contains(re.getMethod())
1028                        || re.getMethod() == Reminders.METHOD_DEFAULT) {
1029                    EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1030                            mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
1031                            mReminderMethodLabels, re, Integer.MAX_VALUE, null);
1032                } else {
1033                    // TODO figure out a way to display unsupported reminders
1034                    mUnsupportedReminders.add(re);
1035                }
1036            }
1037        }
1038
1039        updateRemindersVisibility(numReminders);
1040        EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
1041    }
1042
1043    /**
1044     * Fill in the view with the contents of the given event model. This allows
1045     * an edit view to be initialized before the event has been loaded. Passing
1046     * in null for the model will display a loading screen. A non-null model
1047     * will fill in the view's fields with the data contained in the model.
1048     *
1049     * @param model The event model to pull the data from
1050     */
1051    public void setModel(CalendarEventModel model) {
1052        mModel = model;
1053
1054        // Need to close the autocomplete adapter to prevent leaking cursors.
1055        if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) {
1056            ((EmailAddressAdapter)mAddressAdapter).close();
1057            mAddressAdapter = null;
1058        }
1059
1060        if (model == null) {
1061            // Display loading screen
1062            mLoadingMessage.setVisibility(View.VISIBLE);
1063            mScrollView.setVisibility(View.GONE);
1064            return;
1065        }
1066
1067        boolean canRespond = EditEventHelper.canRespond(model);
1068
1069        final long eventId = model.mId;
1070        final long calendarId = model.mCalendarId;
1071
1072        long begin = model.mStart;
1073        long end = model.mEnd;
1074        mTimezone = model.mTimezone; // this will be UTC for all day events
1075
1076        // Set up the starting times
1077        if (begin > 0) {
1078            mStartTime.timezone = mTimezone;
1079            mStartTime.set(begin);
1080            mStartTime.normalize(true);
1081        }
1082        if (end > 0) {
1083            mEndTime.timezone = mTimezone;
1084            mEndTime.set(end);
1085            mEndTime.normalize(true);
1086        }
1087        String rrule = model.mRrule;
1088        if (!TextUtils.isEmpty(rrule)) {
1089            mEventRecurrence.parse(rrule);
1090        }
1091
1092        // If the user is allowed to change the attendees set up the view and
1093        // validator
1094        if (!model.mHasAttendeeData) {
1095            mAttendeesGroup.setVisibility(View.GONE);
1096        }
1097
1098        mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
1099            @Override
1100            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
1101                setAllDayViewsVisibility(isChecked);
1102            }
1103        });
1104
1105        boolean prevAllDay = mAllDayCheckBox.isChecked();
1106        mAllDay = false; // default to false. Let setAllDayViewsVisibility update it as needed
1107        if (model.mAllDay) {
1108            mAllDayCheckBox.setChecked(true);
1109            // put things back in local time for all day events
1110            mTimezone = Utils.getTimeZone(mActivity, null);
1111            mStartTime.timezone = mTimezone;
1112            mEndTime.timezone = mTimezone;
1113            mEndTime.normalize(true);
1114        } else {
1115            mAllDayCheckBox.setChecked(false);
1116        }
1117        // On a rotation we need to update the views but onCheckedChanged
1118        // doesn't get called
1119        if (prevAllDay == mAllDayCheckBox.isChecked()) {
1120            setAllDayViewsVisibility(prevAllDay);
1121        }
1122
1123        populateTimezone(mStartTime.normalize(true));
1124
1125        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
1126        String defaultReminderString = prefs.getString(
1127                GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
1128        mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
1129
1130        prepareReminders();
1131        prepareAvailability();
1132
1133        View reminderAddButton = mView.findViewById(R.id.reminder_add);
1134        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
1135            @Override
1136            public void onClick(View v) {
1137                addReminder();
1138            }
1139        };
1140        reminderAddButton.setOnClickListener(addReminderOnClickListener);
1141
1142        if (!mIsMultipane) {
1143            mView.findViewById(R.id.is_all_day_label).setOnClickListener(
1144                    new View.OnClickListener() {
1145                        @Override
1146                        public void onClick(View v) {
1147                            mAllDayCheckBox.setChecked(!mAllDayCheckBox.isChecked());
1148                        }
1149                    });
1150        }
1151
1152        if (model.mTitle != null) {
1153            mTitleTextView.setTextKeepState(model.mTitle);
1154        }
1155
1156        if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer)
1157                || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) {
1158            mView.findViewById(R.id.organizer_label).setVisibility(View.GONE);
1159            mView.findViewById(R.id.organizer).setVisibility(View.GONE);
1160            mOrganizerGroup.setVisibility(View.GONE);
1161        } else {
1162            ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName);
1163        }
1164
1165        if (model.mLocation != null) {
1166            mLocationTextView.setTextKeepState(model.mLocation);
1167        }
1168
1169        if (model.mDescription != null) {
1170            mDescriptionTextView.setTextKeepState(model.mDescription);
1171        }
1172
1173        int availIndex = mAvailabilityValues.indexOf(model.mAvailability);
1174        if (availIndex != -1) {
1175            mAvailabilitySpinner.setSelection(availIndex);
1176        }
1177        mAccessLevelSpinner.setSelection(model.mAccessLevel);
1178
1179        View responseLabel = mView.findViewById(R.id.response_label);
1180        if (canRespond) {
1181            int buttonToCheck = EventInfoFragment
1182                    .findButtonIdForResponse(model.mSelfAttendeeStatus);
1183            mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
1184            mResponseRadioGroup.setVisibility(View.VISIBLE);
1185            responseLabel.setVisibility(View.VISIBLE);
1186        } else {
1187            responseLabel.setVisibility(View.GONE);
1188            mResponseRadioGroup.setVisibility(View.GONE);
1189            mResponseGroup.setVisibility(View.GONE);
1190        }
1191
1192        if (model.mUri != null) {
1193            // This is an existing event so hide the calendar spinner
1194            // since we can't change the calendar.
1195            View calendarGroup = mView.findViewById(R.id.calendar_selector_group);
1196            calendarGroup.setVisibility(View.GONE);
1197            TextView tv = (TextView) mView.findViewById(R.id.calendar_textview);
1198            tv.setText(model.mCalendarDisplayName);
1199            tv = (TextView) mView.findViewById(R.id.calendar_textview_secondary);
1200            if (tv != null) {
1201                tv.setText(model.mOwnerAccount);
1202            }
1203        } else {
1204            View calendarGroup = mView.findViewById(R.id.calendar_group);
1205            calendarGroup.setVisibility(View.GONE);
1206        }
1207        updateHeadlineColor(model, model.mEventColor);
1208
1209        populateWhen();
1210        populateRepeats();
1211        updateAttendees(model.mAttendeesList);
1212
1213        updateView();
1214        mScrollView.setVisibility(View.VISIBLE);
1215        mLoadingMessage.setVisibility(View.GONE);
1216        sendAccessibilityEvent();
1217    }
1218
1219    public void updateHeadlineColor(CalendarEventModel model, int displayColor) {
1220        if (model.mUri != null) {
1221            if (mIsMultipane) {
1222                mView.findViewById(R.id.calendar_textview_with_colorpicker)
1223                    .setBackgroundColor(displayColor);
1224            } else {
1225                mView.findViewById(R.id.calendar_group).setBackgroundColor(displayColor);
1226            }
1227        } else {
1228            if (mIsMultipane) {
1229                mCalendarSelectorWrapper.setBackgroundColor(displayColor);
1230            } else {
1231                mCalendarSelectorGroup.setBackgroundColor(displayColor);
1232            }
1233        }
1234    }
1235
1236    private void sendAccessibilityEvent() {
1237        AccessibilityManager am =
1238            (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE);
1239        if (!am.isEnabled() || mModel == null) {
1240            return;
1241        }
1242        StringBuilder b = new StringBuilder();
1243        addFieldsRecursive(b, mView);
1244        CharSequence msg = b.toString();
1245
1246        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1247        event.setClassName(getClass().getName());
1248        event.setPackageName(mActivity.getPackageName());
1249        event.getText().add(msg);
1250        event.setAddedCount(msg.length());
1251
1252        am.sendAccessibilityEvent(event);
1253    }
1254
1255    private void addFieldsRecursive(StringBuilder b, View v) {
1256        if (v == null || v.getVisibility() != View.VISIBLE) {
1257            return;
1258        }
1259        if (v instanceof TextView) {
1260            CharSequence tv = ((TextView) v).getText();
1261            if (!TextUtils.isEmpty(tv.toString().trim())) {
1262                b.append(tv + PERIOD_SPACE);
1263            }
1264        } else if (v instanceof RadioGroup) {
1265            RadioGroup rg = (RadioGroup) v;
1266            int id = rg.getCheckedRadioButtonId();
1267            if (id != View.NO_ID) {
1268                b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE);
1269            }
1270        } else if (v instanceof Spinner) {
1271            Spinner s = (Spinner) v;
1272            if (s.getSelectedItem() instanceof String) {
1273                String str = ((String) (s.getSelectedItem())).trim();
1274                if (!TextUtils.isEmpty(str)) {
1275                    b.append(str + PERIOD_SPACE);
1276                }
1277            }
1278        } else if (v instanceof ViewGroup) {
1279            ViewGroup vg = (ViewGroup) v;
1280            int children = vg.getChildCount();
1281            for (int i = 0; i < children; i++) {
1282                addFieldsRecursive(b, vg.getChildAt(i));
1283            }
1284        }
1285    }
1286
1287    /**
1288     * Creates a single line string for the time/duration
1289     */
1290    protected void setWhenString() {
1291        String when;
1292        int flags = DateUtils.FORMAT_SHOW_DATE;
1293        String tz = mTimezone;
1294        if (mModel.mAllDay) {
1295            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
1296            tz = Time.TIMEZONE_UTC;
1297        } else {
1298            flags |= DateUtils.FORMAT_SHOW_TIME;
1299            if (DateFormat.is24HourFormat(mActivity)) {
1300                flags |= DateUtils.FORMAT_24HOUR;
1301            }
1302        }
1303        long startMillis = mStartTime.normalize(true);
1304        long endMillis = mEndTime.normalize(true);
1305        mSB.setLength(0);
1306        when = DateUtils
1307                .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString();
1308        mWhenView.setText(when);
1309    }
1310
1311    /**
1312     * Configures the Calendars spinner.  This is only done for new events, because only new
1313     * events allow you to select a calendar while editing an event.
1314     * <p>
1315     * We tuck a reference to a Cursor with calendar database data into the spinner, so that
1316     * we can easily extract calendar-specific values when the value changes (the spinner's
1317     * onItemSelected callback is configured).
1318     */
1319    public void setCalendarsCursor(Cursor cursor, boolean userVisible, long selectedCalendarId) {
1320        // If there are no syncable calendars, then we cannot allow
1321        // creating a new event.
1322        mCalendarsCursor = cursor;
1323        if (cursor == null || cursor.getCount() == 0) {
1324            // Cancel the "loading calendars" dialog if it exists
1325            if (mSaveAfterQueryComplete) {
1326                mLoadingCalendarsDialog.cancel();
1327            }
1328            if (!userVisible) {
1329                return;
1330            }
1331            // Create an error message for the user that, when clicked,
1332            // will exit this activity without saving the event.
1333            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
1334            builder.setTitle(R.string.no_syncable_calendars).setIconAttribute(
1335                    android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found)
1336                    .setPositiveButton(R.string.add_account, this)
1337                    .setNegativeButton(android.R.string.no, this).setOnCancelListener(this);
1338            mNoCalendarsDialog = builder.show();
1339            return;
1340        }
1341
1342        int selection;
1343        if (selectedCalendarId != -1) {
1344            selection = findSelectedCalendarPosition(cursor, selectedCalendarId);
1345        } else {
1346            selection = findDefaultCalendarPosition(cursor);
1347        }
1348
1349        // populate the calendars spinner
1350        CalendarsAdapter adapter = new CalendarsAdapter(mActivity,
1351            R.layout.calendars_spinner_item, cursor);
1352        mCalendarsSpinner.setAdapter(adapter);
1353        mCalendarsSpinner.setSelection(selection);
1354        mCalendarsSpinner.setOnItemSelectedListener(this);
1355
1356        if (mSaveAfterQueryComplete) {
1357            mLoadingCalendarsDialog.cancel();
1358            if (prepareForSave() && fillModelFromUI()) {
1359                int exit = userVisible ? Utils.DONE_EXIT : 0;
1360                mDone.setDoneCode(Utils.DONE_SAVE | exit);
1361                mDone.run();
1362            } else if (userVisible) {
1363                mDone.setDoneCode(Utils.DONE_EXIT);
1364                mDone.run();
1365            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
1366                Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view");
1367            }
1368            return;
1369        }
1370    }
1371
1372    /**
1373     * Updates the view based on {@link #mModification} and {@link #mModel}
1374     */
1375    public void updateView() {
1376        if (mModel == null) {
1377            return;
1378        }
1379        if (EditEventHelper.canModifyEvent(mModel)) {
1380            setViewStates(mModification);
1381        } else {
1382            setViewStates(Utils.MODIFY_UNINITIALIZED);
1383        }
1384    }
1385
1386    private void setViewStates(int mode) {
1387        // Extra canModify check just in case
1388        if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) {
1389            setWhenString();
1390
1391            for (View v : mViewOnlyList) {
1392                v.setVisibility(View.VISIBLE);
1393            }
1394            for (View v : mEditOnlyList) {
1395                v.setVisibility(View.GONE);
1396            }
1397            for (View v : mEditViewList) {
1398                v.setEnabled(false);
1399                v.setBackgroundDrawable(null);
1400            }
1401            mCalendarSelectorGroup.setVisibility(View.GONE);
1402            mCalendarStaticGroup.setVisibility(View.VISIBLE);
1403            mRepeatsSpinner.setEnabled(false);
1404            mRepeatsSpinner.setBackgroundDrawable(null);
1405            if (EditEventHelper.canAddReminders(mModel)) {
1406                mRemindersGroup.setVisibility(View.VISIBLE);
1407            } else {
1408                mRemindersGroup.setVisibility(View.GONE);
1409            }
1410            if (TextUtils.isEmpty(mLocationTextView.getText())) {
1411                mLocationGroup.setVisibility(View.GONE);
1412            }
1413            if (TextUtils.isEmpty(mDescriptionTextView.getText())) {
1414                mDescriptionGroup.setVisibility(View.GONE);
1415            }
1416        } else {
1417            for (View v : mViewOnlyList) {
1418                v.setVisibility(View.GONE);
1419            }
1420            for (View v : mEditOnlyList) {
1421                v.setVisibility(View.VISIBLE);
1422            }
1423            for (View v : mEditViewList) {
1424                v.setEnabled(true);
1425                if (v.getTag() != null) {
1426                    v.setBackgroundDrawable((Drawable) v.getTag());
1427                    v.setPadding(mOriginalPadding[0], mOriginalPadding[1], mOriginalPadding[2],
1428                            mOriginalPadding[3]);
1429                }
1430            }
1431            if (mModel.mUri == null) {
1432                mCalendarSelectorGroup.setVisibility(View.VISIBLE);
1433                mCalendarStaticGroup.setVisibility(View.GONE);
1434            } else {
1435                mCalendarSelectorGroup.setVisibility(View.GONE);
1436                mCalendarStaticGroup.setVisibility(View.VISIBLE);
1437            }
1438            mRepeatsSpinner.setBackgroundDrawable((Drawable) mRepeatsSpinner.getTag());
1439            mRepeatsSpinner.setPadding(mOriginalSpinnerPadding[0], mOriginalSpinnerPadding[1],
1440                    mOriginalSpinnerPadding[2], mOriginalSpinnerPadding[3]);
1441            if (mModel.mOriginalSyncId == null) {
1442                mRepeatsSpinner.setEnabled(true);
1443            } else {
1444                mRepeatsSpinner.setEnabled(false);
1445            }
1446            mRemindersGroup.setVisibility(View.VISIBLE);
1447
1448            mLocationGroup.setVisibility(View.VISIBLE);
1449            mDescriptionGroup.setVisibility(View.VISIBLE);
1450        }
1451        setAllDayViewsVisibility(mAllDayCheckBox.isChecked());
1452    }
1453
1454    public void setModification(int modifyWhich) {
1455        mModification = modifyWhich;
1456        updateView();
1457        updateHomeTime();
1458    }
1459
1460    private int findSelectedCalendarPosition(Cursor calendarsCursor, long calendarId) {
1461        if (calendarsCursor.getCount() <= 0) {
1462            return -1;
1463        }
1464        int calendarIdColumn = calendarsCursor.getColumnIndexOrThrow(Calendars._ID);
1465        int position = 0;
1466        calendarsCursor.moveToPosition(-1);
1467        while (calendarsCursor.moveToNext()) {
1468            if (calendarsCursor.getLong(calendarIdColumn) == calendarId) {
1469                return position;
1470            }
1471            position++;
1472        }
1473        return 0;
1474    }
1475
1476    // Find the calendar position in the cursor that matches calendar in
1477    // preference
1478    private int findDefaultCalendarPosition(Cursor calendarsCursor) {
1479        if (calendarsCursor.getCount() <= 0) {
1480            return -1;
1481        }
1482
1483        String defaultCalendar = Utils.getSharedPreference(
1484                mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, (String) null);
1485
1486        int calendarsOwnerIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
1487        int accountNameIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME);
1488        int accountTypeIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE);
1489        int position = 0;
1490        calendarsCursor.moveToPosition(-1);
1491        while (calendarsCursor.moveToNext()) {
1492            String calendarOwner = calendarsCursor.getString(calendarsOwnerIndex);
1493            if (defaultCalendar == null) {
1494                // There is no stored default upon the first time running.  Use a primary
1495                // calendar in this case.
1496                if (calendarOwner != null &&
1497                        calendarOwner.equals(calendarsCursor.getString(accountNameIndex)) &&
1498                        !CalendarContract.ACCOUNT_TYPE_LOCAL.equals(
1499                                calendarsCursor.getString(accountTypeIndex))) {
1500                    return position;
1501                }
1502            } else if (defaultCalendar.equals(calendarOwner)) {
1503                // Found the default calendar.
1504                return position;
1505            }
1506            position++;
1507        }
1508        return 0;
1509    }
1510
1511    private void updateAttendees(HashMap<String, Attendee> attendeesList) {
1512        if (attendeesList == null || attendeesList.isEmpty()) {
1513            return;
1514        }
1515        mAttendeesList.setText(null);
1516        for (Attendee attendee : attendeesList.values()) {
1517            mAttendeesList.append(attendee.mEmail);
1518        }
1519    }
1520
1521    private void updateRemindersVisibility(int numReminders) {
1522        if (numReminders == 0) {
1523            mRemindersContainer.setVisibility(View.GONE);
1524        } else {
1525            mRemindersContainer.setVisibility(View.VISIBLE);
1526        }
1527    }
1528
1529    /**
1530     * Add a new reminder when the user hits the "add reminder" button.  We use the default
1531     * reminder time and method.
1532     */
1533    private void addReminder() {
1534        // TODO: when adding a new reminder, make it different from the
1535        // last one in the list (if any).
1536        if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
1537            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1538                    mReminderMinuteValues, mReminderMinuteLabels,
1539                    mReminderMethodValues, mReminderMethodLabels,
1540                    ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME),
1541                    mModel.mCalendarMaxReminders, null);
1542        } else {
1543            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1544                    mReminderMinuteValues, mReminderMinuteLabels,
1545                    mReminderMethodValues, mReminderMethodLabels,
1546                    ReminderEntry.valueOf(mDefaultReminderMinutes),
1547                    mModel.mCalendarMaxReminders, null);
1548        }
1549        updateRemindersVisibility(mReminderItems.size());
1550        EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
1551    }
1552
1553    // From com.google.android.gm.ComposeActivity
1554    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(RecipientEditTextView list) {
1555        if (ChipsUtil.supportsChipsUi()) {
1556            mAddressAdapter = new RecipientAdapter(mActivity);
1557            list.setAdapter((BaseRecipientAdapter) mAddressAdapter);
1558            list.setOnFocusListShrinkRecipients(false);
1559        } else {
1560            mAddressAdapter = new EmailAddressAdapter(mActivity);
1561            list.setAdapter((EmailAddressAdapter)mAddressAdapter);
1562        }
1563        list.setTokenizer(new Rfc822Tokenizer());
1564        list.setValidator(mEmailValidator);
1565
1566        // NOTE: assumes no other filters are set
1567        list.setFilters(sRecipientFilters);
1568
1569        return list;
1570    }
1571
1572    /**
1573     * From com.google.android.gm.ComposeActivity Implements special address
1574     * cleanup rules: The first space key entry following an "@" symbol that is
1575     * followed by any combination of letters and symbols, including one+ dots
1576     * and zero commas, should insert an extra comma (followed by the space).
1577     */
1578    private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
1579
1580    private void setDate(TextView view, long millis) {
1581        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
1582                | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH
1583                | DateUtils.FORMAT_ABBREV_WEEKDAY;
1584
1585        // Unfortunately, DateUtils doesn't support a timezone other than the
1586        // default timezone provided by the system, so we have this ugly hack
1587        // here to trick it into formatting our time correctly. In order to
1588        // prevent all sorts of craziness, we synchronize on the TimeZone class
1589        // to prevent other threads from reading an incorrect timezone from
1590        // calls to TimeZone#getDefault()
1591        // TODO fix this if/when DateUtils allows for passing in a timezone
1592        String dateString;
1593        synchronized (TimeZone.class) {
1594            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1595            dateString = DateUtils.formatDateTime(mActivity, millis, flags);
1596            // setting the default back to null restores the correct behavior
1597            TimeZone.setDefault(null);
1598        }
1599        view.setText(dateString);
1600    }
1601
1602    private void setTime(TextView view, long millis) {
1603        int flags = DateUtils.FORMAT_SHOW_TIME;
1604        if (DateFormat.is24HourFormat(mActivity)) {
1605            flags |= DateUtils.FORMAT_24HOUR;
1606        }
1607
1608        // Unfortunately, DateUtils doesn't support a timezone other than the
1609        // default timezone provided by the system, so we have this ugly hack
1610        // here to trick it into formatting our time correctly. In order to
1611        // prevent all sorts of craziness, we synchronize on the TimeZone class
1612        // to prevent other threads from reading an incorrect timezone from
1613        // calls to TimeZone#getDefault()
1614        // TODO fix this if/when DateUtils allows for passing in a timezone
1615        String timeString;
1616        synchronized (TimeZone.class) {
1617            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1618            timeString = DateUtils.formatDateTime(mActivity, millis, flags);
1619            TimeZone.setDefault(null);
1620        }
1621        view.setText(timeString);
1622    }
1623
1624    private void setTimezone(int i) {
1625        if (i < 0 || i >= mTimezoneAdapter.getCount()) {
1626            return; // do nothing
1627        }
1628        TimezoneRow timezone = mTimezoneAdapter.getItem(i);
1629        mTimezoneTextView.setText(timezone.toString());
1630        mTimezoneButton.setText(timezone.toString());
1631        mTimezone = timezone.mId;
1632        mStartTime.timezone = mTimezone;
1633        mStartTime.normalize(true);
1634        mEndTime.timezone = mTimezone;
1635        mEndTime.normalize(true);
1636        mTimezoneAdapter.setCurrentTimezone(mTimezone);
1637    }
1638
1639    /**
1640     * @param isChecked
1641     */
1642    protected void setAllDayViewsVisibility(boolean isChecked) {
1643        if (isChecked) {
1644            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1645                if (mAllDay != isChecked) {
1646                    mEndTime.monthDay--;
1647                }
1648
1649                long endMillis = mEndTime.normalize(true);
1650
1651                // Do not allow an event to have an end time
1652                // before the
1653                // start time.
1654                if (mEndTime.before(mStartTime)) {
1655                    mEndTime.set(mStartTime);
1656                    endMillis = mEndTime.normalize(true);
1657                }
1658                setDate(mEndDateButton, endMillis);
1659                setTime(mEndTimeButton, endMillis);
1660            }
1661
1662            mStartTimeButton.setVisibility(View.GONE);
1663            mEndTimeButton.setVisibility(View.GONE);
1664            mTimezoneRow.setVisibility(View.GONE);
1665        } else {
1666            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1667                if (mAllDay != isChecked) {
1668                    mEndTime.monthDay++;
1669                }
1670
1671                long endMillis = mEndTime.normalize(true);
1672                setDate(mEndDateButton, endMillis);
1673                setTime(mEndTimeButton, endMillis);
1674            }
1675            mStartTimeButton.setVisibility(View.VISIBLE);
1676            mEndTimeButton.setVisibility(View.VISIBLE);
1677            mTimezoneRow.setVisibility(View.VISIBLE);
1678        }
1679        mAllDay = isChecked;
1680        updateHomeTime();
1681    }
1682
1683    public void setColorPickerButtonStates(int[] eventColors) {
1684        if (eventColors == null || eventColors.length == 0) {
1685            mColorPickerNewEvent.setEnabled(false);
1686            mColorPickerExistingEvent.setEnabled(false);
1687        } else {
1688            mColorPickerNewEvent.setEnabled(true);
1689            mColorPickerExistingEvent.setEnabled(true);
1690        }
1691    }
1692
1693    @Override
1694    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1695        // This is only used for the Calendar spinner in new events, and only fires when the
1696        // calendar selection changes or on screen rotation
1697        Cursor c = (Cursor) parent.getItemAtPosition(position);
1698        if (c == null) {
1699            // TODO: can this happen? should we drop this check?
1700            Log.w(TAG, "Cursor not set on calendar item");
1701            return;
1702        }
1703
1704        // Do nothing if the selection didn't change so that reminders will not get lost
1705        int idColumn = c.getColumnIndexOrThrow(Calendars._ID);
1706        long calendarId = c.getLong(idColumn);
1707        if (calendarId == mModel.mCalendarId) {
1708            return;
1709        }
1710
1711        int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
1712        int color = c.getInt(colorColumn);
1713        int displayColor = Utils.getDisplayColorFromColor(color);
1714
1715        if (mIsMultipane) {
1716            mCalendarSelectorWrapper.setBackgroundColor(displayColor);
1717        } else {
1718            mCalendarSelectorGroup.setBackgroundColor(displayColor);
1719        }
1720
1721        mModel.mCalendarId = calendarId;
1722        mModel.mCalendarColor = displayColor;
1723        mModel.mCalendarAccountName = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_NAME);
1724        mModel.mCalendarAccountType = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_TYPE);
1725        mModel.mEventColor = mModel.mCalendarColor;
1726
1727        setColorPickerButtonStates(mModel.getCalendarEventColors());
1728
1729        // Update the max/allowed reminders with the new calendar properties.
1730        int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS);
1731        mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn);
1732        int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS);
1733        mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn);
1734        int allowedAttendeeTypesColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_ATTENDEE_TYPES);
1735        mModel.mCalendarAllowedAttendeeTypes = c.getString(allowedAttendeeTypesColumn);
1736        int allowedAvailabilityColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_AVAILABILITY);
1737        mModel.mCalendarAllowedAvailability = c.getString(allowedAvailabilityColumn);
1738
1739        // Discard the current reminders and replace them with the model's default reminder set.
1740        // We could attempt to save & restore the reminders that have been added, but that's
1741        // probably more trouble than it's worth.
1742        mModel.mReminders.clear();
1743        mModel.mReminders.addAll(mModel.mDefaultReminders);
1744        mModel.mHasAlarm = mModel.mReminders.size() != 0;
1745
1746        // Update the UI elements.
1747        mReminderItems.clear();
1748        LinearLayout reminderLayout =
1749            (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container);
1750        reminderLayout.removeAllViews();
1751        prepareReminders();
1752        prepareAvailability();
1753    }
1754
1755    /**
1756     * Checks if the start and end times for this event should be displayed in
1757     * the Calendar app's time zone as well and formats and displays them.
1758     */
1759    private void updateHomeTime() {
1760        String tz = Utils.getTimeZone(mActivity, null);
1761        if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone)
1762                && mModification != EditEventHelper.MODIFY_UNINITIALIZED) {
1763            int flags = DateUtils.FORMAT_SHOW_TIME;
1764            boolean is24Format = DateFormat.is24HourFormat(mActivity);
1765            if (is24Format) {
1766                flags |= DateUtils.FORMAT_24HOUR;
1767            }
1768            long millisStart = mStartTime.toMillis(false);
1769            long millisEnd = mEndTime.toMillis(false);
1770
1771            boolean isDSTStart = mStartTime.isDst != 0;
1772            boolean isDSTEnd = mEndTime.isDst != 0;
1773
1774            // First update the start date and times
1775            String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1776                    isDSTStart, TimeZone.SHORT, Locale.getDefault());
1777            StringBuilder time = new StringBuilder();
1778
1779            mSB.setLength(0);
1780            time.append(DateUtils
1781                    .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz))
1782                    .append(" ").append(tzDisplay);
1783            mStartTimeHome.setText(time.toString());
1784
1785            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1786                    | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1787            mSB.setLength(0);
1788            mStartDateHome
1789                    .setText(DateUtils.formatDateRange(
1790                            mActivity, mF, millisStart, millisStart, flags, tz).toString());
1791
1792            // Make any adjustments needed for the end times
1793            if (isDSTEnd != isDSTStart) {
1794                tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1795                        isDSTEnd, TimeZone.SHORT, Locale.getDefault());
1796            }
1797            flags = DateUtils.FORMAT_SHOW_TIME;
1798            if (is24Format) {
1799                flags |= DateUtils.FORMAT_24HOUR;
1800            }
1801
1802            // Then update the end times
1803            time.setLength(0);
1804            mSB.setLength(0);
1805            time.append(DateUtils.formatDateRange(
1806                    mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay);
1807            mEndTimeHome.setText(time.toString());
1808
1809            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1810                    | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1811            mSB.setLength(0);
1812            mEndDateHome.setText(DateUtils.formatDateRange(
1813                            mActivity, mF, millisEnd, millisEnd, flags, tz).toString());
1814
1815            mStartHomeGroup.setVisibility(View.VISIBLE);
1816            mEndHomeGroup.setVisibility(View.VISIBLE);
1817        } else {
1818            mStartHomeGroup.setVisibility(View.GONE);
1819            mEndHomeGroup.setVisibility(View.GONE);
1820        }
1821    }
1822
1823    @Override
1824    public void onNothingSelected(AdapterView<?> parent) {
1825    }
1826}
1827