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