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