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