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