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