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