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