EditEventView.java revision 18cabd263b91d7d09f3b93b0bf6c78533bbc2c6a
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.common.Rfc822InputFilter;
32import com.android.common.Rfc822Validator;
33import com.android.ex.chips.AccountSpecifier;
34import com.android.ex.chips.BaseRecipientAdapter;
35import com.android.ex.chips.ChipsUtil;
36import com.android.ex.chips.RecipientEditTextView;
37
38import android.app.Activity;
39import android.app.AlertDialog;
40import android.app.DatePickerDialog;
41import android.app.DatePickerDialog.OnDateSetListener;
42import android.app.ProgressDialog;
43import android.app.Service;
44import android.app.TimePickerDialog;
45import android.app.TimePickerDialog.OnTimeSetListener;
46import android.content.Context;
47import android.content.DialogInterface;
48import android.content.Intent;
49import android.content.SharedPreferences;
50import android.content.res.Resources;
51import android.database.Cursor;
52import android.graphics.Bitmap;
53import android.graphics.BitmapFactory;
54import android.graphics.drawable.Drawable;
55import android.pim.EventRecurrence;
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                    return false;
541                } else if (mEventRecurrence.bydayCount == 0
542                        && mEventRecurrence.bymonthdayCount == 1) {
543                    return false;
544                }
545                break;
546            case EventRecurrence.YEARLY:
547                return false;
548        }
549
550        return true;
551    }
552
553    private boolean isWeekdayEvent() {
554        if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) {
555            return true;
556        }
557        return false;
558    }
559
560    private class DateClickListener implements View.OnClickListener {
561        private Time mTime;
562
563        public DateClickListener(Time time) {
564            mTime = time;
565        }
566
567        public void onClick(View v) {
568            DatePickerDialog dpd = new DatePickerDialog(
569                    mActivity, new DateListener(v), mTime.year, mTime.month, mTime.monthDay);
570            CalendarView cv = dpd.getDatePicker().getCalendarView();
571            cv.setShowWeekNumber(Utils.getShowWeekNumber(mActivity));
572            int startOfWeek = Utils.getFirstDayOfWeek(mActivity);
573            // Utils returns Time days while CalendarView wants Calendar days
574            if (startOfWeek == Time.SATURDAY) {
575                startOfWeek = Calendar.SATURDAY;
576            } else if (startOfWeek == Time.SUNDAY) {
577                startOfWeek = Calendar.SUNDAY;
578            } else {
579                startOfWeek = Calendar.MONDAY;
580            }
581            cv.setFirstDayOfWeek(startOfWeek);
582            dpd.show();
583        }
584    }
585
586    static private class CalendarsAdapter extends ResourceCursorAdapter {
587        public CalendarsAdapter(Context context, Cursor c) {
588            super(context, R.layout.calendars_item, c);
589            setDropDownViewResource(R.layout.calendars_dropdown_item);
590        }
591
592        @Override
593        public void bindView(View view, Context context, Cursor cursor) {
594            View colorBar = view.findViewById(R.id.color);
595            int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
596            int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME);
597            int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
598            if (colorBar != null) {
599                colorBar.setBackgroundColor(cursor.getInt(colorColumn));
600            }
601
602            TextView name = (TextView) view.findViewById(R.id.calendar_name);
603            if (name != null) {
604                String displayName = cursor.getString(nameColumn);
605                name.setText(displayName);
606
607                TextView accountName = (TextView) view.findViewById(R.id.account_name);
608                if (accountName != null) {
609                    Resources res = context.getResources();
610                    accountName.setText(cursor.getString(ownerColumn));
611                    accountName.setVisibility(TextView.VISIBLE);
612                    accountName.setTextColor(res.getColor(R.color.calendar_owner_text_color));
613                }
614            }
615        }
616    }
617
618    /**
619     * Does prep steps for saving a calendar event.
620     *
621     * This triggers a parse of the attendees list and checks if the event is
622     * ready to be saved. An event is ready to be saved so long as a model
623     * exists and has a calendar it can be associated with, either because it's
624     * an existing event or we've finished querying.
625     *
626     * @return false if there is no model or no calendar had been loaded yet,
627     * true otherwise.
628     */
629    public boolean prepareForSave() {
630        if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
631            return false;
632        }
633        mAddAttendeesListener.onClick(null);
634        return fillModelFromUI();
635    }
636
637    public boolean fillModelFromReadOnlyUi() {
638        if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
639            return false;
640        }
641        mModel.mReminders = EventViewUtils.reminderItemsToReminders(
642                    mReminderItems, mReminderMinuteValues, mReminderMethodValues);
643        int status = EventInfoFragment.getResponseFromButtonId(
644                mResponseRadioGroup.getCheckedRadioButtonId());
645        if (status != Attendees.ATTENDEE_STATUS_NONE) {
646            mModel.mSelfAttendeeStatus = status;
647        }
648        return true;
649    }
650
651    // This is called if the user clicks on one of the buttons: "Save",
652    // "Discard", or "Delete". This is also called if the user clicks
653    // on the "remove reminder" button.
654    @Override
655    public void onClick(View view) {
656
657        // This must be a click on one of the "remove reminder" buttons
658        LinearLayout reminderItem = (LinearLayout) view.getParent();
659        LinearLayout parent = (LinearLayout) reminderItem.getParent();
660        parent.removeView(reminderItem);
661        mReminderItems.remove(reminderItem);
662        updateRemindersVisibility(mReminderItems.size());
663    }
664
665    // This is called if the user cancels the "No calendars" dialog.
666    // The "No calendars" dialog is shown if there are no syncable calendars.
667    @Override
668    public void onCancel(DialogInterface dialog) {
669        if (dialog == mLoadingCalendarsDialog) {
670            mLoadingCalendarsDialog = null;
671            mSaveAfterQueryComplete = false;
672        } else if (dialog == mNoCalendarsDialog) {
673            mDone.setDoneCode(Utils.DONE_REVERT);
674            mDone.run();
675            return;
676        }
677    }
678
679    // This is called if the user clicks on a dialog button.
680    @Override
681    public void onClick(DialogInterface dialog, int which) {
682        if (dialog == mNoCalendarsDialog) {
683            mDone.setDoneCode(Utils.DONE_REVERT);
684            mDone.run();
685            if (which == DialogInterface.BUTTON_POSITIVE) {
686                Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
687                final String[] array = {"com.android.calendar"};
688                nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
689                nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
690                mActivity.startActivity(nextIntent);
691            }
692        } else if (dialog == mTimezoneDialog) {
693            if (which >= 0 && which < mTimezoneAdapter.getCount()) {
694                setTimezone(which);
695                updateHomeTime();
696                dialog.dismiss();
697            }
698        }
699    }
700
701    // Goes through the UI elements and updates the model as necessary
702    private boolean fillModelFromUI() {
703        if (mModel == null) {
704            return false;
705        }
706        mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems,
707                mReminderMinuteValues, mReminderMethodValues);
708        mModel.mHasAlarm = mReminderItems.size() > 0;
709        mModel.mTitle = mTitleTextView.getText().toString();
710        mModel.mAllDay = mAllDayCheckBox.isChecked();
711        mModel.mLocation = mLocationTextView.getText().toString();
712        mModel.mDescription = mDescriptionTextView.getText().toString();
713        if (TextUtils.isEmpty(mModel.mLocation)) {
714            mModel.mLocation = null;
715        }
716        if (TextUtils.isEmpty(mModel.mDescription)) {
717            mModel.mDescription = null;
718        }
719
720        int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup
721                .getCheckedRadioButtonId());
722        if (status != Attendees.ATTENDEE_STATUS_NONE) {
723            mModel.mSelfAttendeeStatus = status;
724        }
725
726        if (mAttendeesView != null && mAttendeesView.getChildCount() > 0) {
727            final int size = mAttendeesView.getChildCount();
728            mModel.mAttendeesList.clear();
729            for (int i = 0; i < size; i++) {
730                final Attendee attendee = mAttendeesView.getItem(i);
731                if (attendee == null || mAttendeesView.isMarkAsRemoved(i)) {
732                    continue;
733                }
734                mModel.addAttendee(attendee);
735            }
736        }
737
738        // If this was a new event we need to fill in the Calendar information
739        if (mModel.mUri == null) {
740            mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId();
741            int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
742            if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
743                String defaultCalendar = mCalendarsCursor.getString(
744                        EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
745                Utils.setSharedPreference(
746                        mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar);
747                mModel.mOwnerAccount = defaultCalendar;
748                mModel.mOrganizer = defaultCalendar;
749                mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID);
750            }
751        }
752
753        if (mModel.mAllDay) {
754            // Reset start and end time, increment the monthDay by 1, and set
755            // the timezone to UTC, as required for all-day events.
756            mTimezone = Time.TIMEZONE_UTC;
757            mStartTime.hour = 0;
758            mStartTime.minute = 0;
759            mStartTime.second = 0;
760            mStartTime.timezone = mTimezone;
761            mModel.mStart = mStartTime.normalize(true);
762
763            mEndTime.hour = 0;
764            mEndTime.minute = 0;
765            mEndTime.second = 0;
766            mEndTime.timezone = mTimezone;
767            // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time
768            // should be Y + 1 (Oct.30).
769            final long normalizedEndTimeMillis =
770                    mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS;
771            if (normalizedEndTimeMillis < mModel.mStart) {
772                // mEnd should be midnight of the next day of mStart.
773                mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS;
774            } else {
775                mModel.mEnd = normalizedEndTimeMillis;
776            }
777        } else {
778            mStartTime.timezone = mTimezone;
779            mEndTime.timezone = mTimezone;
780            mModel.mStart = mStartTime.toMillis(true);
781            mModel.mEnd = mEndTime.toMillis(true);
782        }
783        mModel.mTimezone = mTimezone;
784        mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition();
785        mModel.mAvailability = mAvailabilitySpinner.getSelectedItemPosition() != 0;
786
787        int selection;
788        // If we're making an exception we don't want it to be a repeating
789        // event.
790        if (mModification == EditEventHelper.MODIFY_SELECTED) {
791            selection = EditEventHelper.DOES_NOT_REPEAT;
792        } else {
793            int position = mRepeatsSpinner.getSelectedItemPosition();
794            selection = mRecurrenceIndexes.get(position);
795        }
796
797        EditEventHelper.updateRecurrenceRule(
798                selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1);
799
800        // Save the timezone so we can display it as a standard option next time
801        if (!mModel.mAllDay) {
802            mTimezoneAdapter.saveRecentTimezone(mTimezone);
803        }
804        return true;
805    }
806
807    public EditEventView(Activity activity, View view, EditDoneRunnable done) {
808
809        mActivity = activity;
810        mView = view;
811        mDone = done;
812
813        DEFAULT_DOMAIN = activity.getResources().getString(R.string.google_email_domain);
814
815        // cache top level view elements
816        mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
817        mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
818
819        // cache all the widgets
820        mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner);
821        mTitleTextView = (TextView) view.findViewById(R.id.title);
822        mLocationTextView = (TextView) view.findViewById(R.id.location);
823        mDescriptionTextView = (TextView) view.findViewById(R.id.description);
824        mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label);
825        mStartDateButton = (Button) view.findViewById(R.id.start_date);
826        mEndDateButton = (Button) view.findViewById(R.id.end_date);
827        mWhenView = (TextView) mView.findViewById(R.id.when);
828        mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView);
829        mStartTimeButton = (Button) view.findViewById(R.id.start_time);
830        mEndTimeButton = (Button) view.findViewById(R.id.end_time);
831        mTimezoneButton = (Button) view.findViewById(R.id.timezone_button);
832        mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz);
833        mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz);
834        mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz);
835        mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz);
836        mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day);
837        mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats);
838        mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability);
839        mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility);
840        mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group);
841        mCalendarStaticGroup = view.findViewById(R.id.calendar_group);
842        mRemindersGroup = view.findViewById(R.id.reminders_row);
843        mResponseGroup = view.findViewById(R.id.response_row);
844        mOrganizerGroup = view.findViewById(R.id.organizer_row);
845        mAttendeesGroup = view.findViewById(R.id.attendees_row);
846        mAttendeesPane = view.findViewById(R.id.attendees_group);
847        mLocationGroup = view.findViewById(R.id.where_row);
848        mDescriptionGroup = view.findViewById(R.id.description_row);
849        mStartHomeGroup = view.findViewById(R.id.from_row_home_tz);
850        mEndHomeGroup = view.findViewById(R.id.to_row_home_tz);
851
852        mTitleTextView.setTag(mTitleTextView.getBackground());
853        mLocationTextView.setTag(mLocationTextView.getBackground());
854        mDescriptionTextView.setTag(mDescriptionTextView.getBackground());
855        mRepeatsSpinner.setTag(mRepeatsSpinner.getBackground());
856        mOriginalPadding[0] = mLocationTextView.getPaddingLeft();
857        mOriginalPadding[1] = mLocationTextView.getPaddingTop();
858        mOriginalPadding[2] = mLocationTextView.getPaddingRight();
859        mOriginalPadding[3] = mLocationTextView.getPaddingBottom();
860        mEditViewList.add(mTitleTextView);
861        mEditViewList.add(mLocationTextView);
862        mEditViewList.add(mDescriptionTextView);
863
864        mViewOnlyList.add(view.findViewById(R.id.when_row));
865        mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row));
866
867        mEditOnlyList.add(view.findViewById(R.id.all_day_row));
868        mEditOnlyList.add(view.findViewById(R.id.availability_row));
869        mEditOnlyList.add(view.findViewById(R.id.visibility_row));
870        mEditOnlyList.add(view.findViewById(R.id.from_row));
871        mEditOnlyList.add(view.findViewById(R.id.to_row));
872        mEditOnlyList.add(view.findViewById(R.id.timezone_button_row));
873        mEditOnlyList.add(view.findViewById(R.id.add_attendees_row));
874        mEditOnlyList.add(mStartHomeGroup);
875        mEditOnlyList.add(mEndHomeGroup);
876
877        mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value);
878        mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
879
880        mAddAttendeesButton = (ImageButton) view.findViewById(R.id.add_attendee_button);
881        mAddAttendeesListener = new AddAttendeeClickListener();
882        mAddAttendeesButton.setEnabled(false);
883        mAddAttendeesButton.setOnClickListener(mAddAttendeesListener);
884
885        mAttendeesView = (AttendeesView)view.findViewById(R.id.attendee_list);
886
887        mTimezone = Utils.getTimeZone(activity, null);
888        mStartTime = new Time(mTimezone);
889        mEndTime = new Time(mTimezone);
890        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
891
892        mColorChip = view.findViewById(R.id.color_chip);
893
894        // Display loading screen
895        setModel(null);
896    }
897
898
899    /**
900     * Loads an integer array asset into a list.
901     */
902    private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
903        int[] vals = r.getIntArray(resNum);
904        int size = vals.length;
905        ArrayList<Integer> list = new ArrayList<Integer>(size);
906
907        for (int i = 0; i < size; i++) {
908            list.add(vals[i]);
909        }
910
911        return list;
912    }
913
914    /**
915     * Loads a String array asset into a list.
916     */
917    private static ArrayList<String> loadStringArray(Resources r, int resNum) {
918        String[] labels = r.getStringArray(resNum);
919        ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
920        return list;
921    }
922
923    /**
924     * Prepares the reminder UI elements.
925     * <p>
926     * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as
927     * needed for the current set of reminders and calendar properties, and then creates UI
928     * elements.
929     */
930    private void prepareReminders() {
931        CalendarEventModel model = mModel;
932        Resources r = mActivity.getResources();
933
934        // Load the labels and corresponding numeric values for the minutes and methods lists
935        // from the assets.  If we're switching calendars, we need to clear and re-populate the
936        // lists (which may have elements added and removed based on calendar properties).  This
937        // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
938        // new event that aren't in the default set.
939        mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
940        mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
941        mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
942        mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
943
944        // Remove any reminder methods that aren't allowed for this calendar.  If this is
945        // a new event, mCalendarAllowedReminders may not be set the first time we're called.
946        if (mModel.mCalendarAllowedReminders != null) {
947            EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
948                    mModel.mCalendarAllowedReminders);
949        }
950
951        int numReminders = 0;
952        if (model.mHasAlarm) {
953            ArrayList<ReminderEntry> reminders = model.mReminders;
954            numReminders = reminders.size();
955            // Insert any minute values that aren't represented in the minutes list.
956            for (ReminderEntry re : reminders) {
957                EventViewUtils.addMinutesToList(
958                        mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
959            }
960
961            // Create a UI element for each reminder.  We display all of the reminders we get
962            // from the provider, even if the count exceeds the calendar maximum.  (Also, for
963            // a new event, we won't have a maxReminders value available.)
964            for (ReminderEntry re : reminders) {
965                EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
966                        mReminderMinuteValues, mReminderMinuteLabels,
967                        mReminderMethodValues, mReminderMethodLabels,
968                        re, Integer.MAX_VALUE);
969            }
970        }
971
972        updateRemindersVisibility(numReminders);
973    }
974
975    /**
976     * Fill in the view with the contents of the given event model. This allows
977     * an edit view to be initialized before the event has been loaded. Passing
978     * in null for the model will display a loading screen. A non-null model
979     * will fill in the view's fields with the data contained in the model.
980     *
981     * @param model The event model to pull the data from
982     */
983    public void setModel(CalendarEventModel model) {
984        mModel = model;
985
986        // Need to close the autocomplete adapter to prevent leaking cursors.
987        if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) {
988            ((EmailAddressAdapter)mAddressAdapter).close();
989            mAddressAdapter = null;
990        }
991
992        if (model == null) {
993            // Display loading screen
994            mLoadingMessage.setVisibility(View.VISIBLE);
995            mScrollView.setVisibility(View.GONE);
996            return;
997        }
998
999        boolean canModifyCalendar = EditEventHelper.canModifyCalendar(model);
1000        boolean canModifyEvent = EditEventHelper.canModifyEvent(model);
1001        boolean canRespond = EditEventHelper.canRespond(model);
1002
1003        long begin = model.mStart;
1004        long end = model.mEnd;
1005        mTimezone = model.mTimezone; // this will be UTC for all day events
1006
1007        // Set up the starting times
1008        if (begin > 0) {
1009            mStartTime.timezone = mTimezone;
1010            mStartTime.set(begin);
1011            mStartTime.normalize(true);
1012        }
1013        if (end > 0) {
1014            mEndTime.timezone = mTimezone;
1015            mEndTime.set(end);
1016            mEndTime.normalize(true);
1017        }
1018        String rrule = model.mRrule;
1019        if (!TextUtils.isEmpty(rrule)) {
1020            mEventRecurrence.parse(rrule);
1021        }
1022
1023        // If the user is allowed to change the attendees set up the view and
1024        // validator
1025        if (!model.mHasAttendeeData) {
1026            mView.findViewById(R.id.attendees_group).setVisibility(View.GONE);
1027        } else if (!canModifyEvent) {
1028            // Hide views used for adding attendees
1029            View v =  mView.findViewById(R.id.add_attendees_label);
1030            if (v != null) {
1031                v.setVisibility(View.GONE);
1032            }
1033            mView.findViewById(R.id.add_attendees_group).setVisibility(View.GONE);
1034            mAddAttendeesButton.setVisibility(View.GONE);
1035        } else {
1036            String domain = DEFAULT_DOMAIN;
1037            if (!TextUtils.isEmpty(model.mOwnerAccount)) {
1038                String ownerDomain = EditEventHelper.extractDomain(model.mOwnerAccount);
1039                if (!TextUtils.isEmpty(ownerDomain)) {
1040                    domain = ownerDomain;
1041                }
1042            }
1043            mEmailValidator = new Rfc822Validator(domain);
1044            mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees);
1045            mAttendeesList.addTextChangedListener(this);
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        if (model.mAllDay) {
1056            mAllDayCheckBox.setChecked(true);
1057            // put things back in local time for all day events
1058            mTimezone = TimeZone.getDefault().getID();
1059            mStartTime.timezone = mTimezone;
1060            mStartTime.normalize(true);
1061            mEndTime.timezone = mTimezone;
1062            mEndTime.normalize(true);
1063        } else {
1064            mAllDayCheckBox.setChecked(false);
1065        }
1066
1067        mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone);
1068        if (mTimezoneDialog != null) {
1069            mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter);
1070        }
1071
1072        SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
1073        String defaultReminderString = prefs.getString(
1074                GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
1075        mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
1076
1077        prepareReminders();
1078
1079        ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add);
1080        View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
1081            @Override
1082            public void onClick(View v) {
1083                addReminder();
1084            }
1085        };
1086        reminderAddButton.setOnClickListener(addReminderOnClickListener);
1087
1088        mTitleTextView.setText(model.mTitle);
1089        if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer)
1090                || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) {
1091            mView.findViewById(R.id.organizer_label).setVisibility(View.GONE);
1092            mView.findViewById(R.id.organizer).setVisibility(View.GONE);
1093            mOrganizerGroup.setVisibility(View.GONE);
1094        } else {
1095            ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName);
1096        }
1097        mLocationTextView.setText(model.mLocation);
1098        mDescriptionTextView.setText(model.mDescription);
1099        mAvailabilitySpinner.setSelection(model.mAvailability ? 1 : 0);
1100        mAccessLevelSpinner.setSelection(model.mAccessLevel);
1101
1102        View responseLabel = mView.findViewById(R.id.response_label);
1103        if (canRespond) {
1104            int buttonToCheck = EventInfoFragment
1105                    .findButtonIdForResponse(model.mSelfAttendeeStatus);
1106            mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
1107            mResponseRadioGroup.setVisibility(View.VISIBLE);
1108            responseLabel.setVisibility(View.VISIBLE);
1109        } else {
1110            responseLabel.setVisibility(View.GONE);
1111            mResponseRadioGroup.setVisibility(View.GONE);
1112        }
1113
1114        if (model.mUri != null) {
1115            // This is an existing event so hide the calendar spinner
1116            // since we can't change the calendar.
1117            View calendarGroup = mView.findViewById(R.id.calendar_selector_group);
1118            calendarGroup.setVisibility(View.GONE);
1119            TextView tv = (TextView) mView.findViewById(R.id.calendar_textview);
1120            tv.setText(model.mCalendarDisplayName);
1121            tv.setBackgroundColor(model.mCalendarColor);
1122        } else {
1123            View calendarGroup = mView.findViewById(R.id.calendar_group);
1124            calendarGroup.setVisibility(View.GONE);
1125            mCalendarsSpinner.setBackgroundColor(model.mCalendarColor);
1126            mCalendarSelectorGroup.setBackgroundColor(model.mCalendarColor);
1127        }
1128
1129        populateTimezone();
1130        populateWhen();
1131        populateRepeats();
1132        updateAttendees(model.mAttendeesList);
1133
1134        updateView();
1135        mScrollView.setVisibility(View.VISIBLE);
1136        mLoadingMessage.setVisibility(View.GONE);
1137        sendAccessibilityEvent();
1138    }
1139
1140    private void sendAccessibilityEvent() {
1141        AccessibilityManager am =
1142            (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE);
1143        if (!am.isEnabled() || mModel == null) {
1144            return;
1145        }
1146        StringBuilder b = new StringBuilder();
1147        addFieldsRecursive(b, mView);
1148        CharSequence msg = b.toString();
1149
1150        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1151        event.setClassName(getClass().getName());
1152        event.setPackageName(mActivity.getPackageName());
1153        event.getText().add(msg);
1154        event.setAddedCount(msg.length());
1155
1156        am.sendAccessibilityEvent(event);
1157    }
1158
1159    private void addFieldsRecursive(StringBuilder b, View v) {
1160        if (v == null || v.getVisibility() != View.VISIBLE) {
1161            return;
1162        }
1163        if (v instanceof TextView) {
1164            CharSequence tv = ((TextView) v).getText();
1165            if (!TextUtils.isEmpty(tv.toString().trim())) {
1166                b.append(tv + PERIOD_SPACE);
1167            }
1168        } else if (v instanceof RadioGroup) {
1169            RadioGroup rg = (RadioGroup) v;
1170            int id = rg.getCheckedRadioButtonId();
1171            if (id != View.NO_ID) {
1172                b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE);
1173            }
1174        } else if (v instanceof Spinner) {
1175            Spinner s = (Spinner) v;
1176            if (s.getSelectedItem() instanceof String) {
1177                String str = ((String) (s.getSelectedItem())).trim();
1178                if (!TextUtils.isEmpty(str)) {
1179                    b.append(str + PERIOD_SPACE);
1180                }
1181            }
1182        } else if (v instanceof ViewGroup) {
1183            ViewGroup vg = (ViewGroup) v;
1184            int children = vg.getChildCount();
1185            for (int i = 0; i < children; i++) {
1186                addFieldsRecursive(b, vg.getChildAt(i));
1187            }
1188        }
1189    }
1190
1191    /**
1192     * Creates a single line string for the time/duration
1193     */
1194    protected void setWhenString() {
1195        String when;
1196        int flags = DateUtils.FORMAT_SHOW_DATE;
1197        String tz = mTimezone;
1198        if (mModel.mAllDay) {
1199            flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
1200            tz = Time.TIMEZONE_UTC;
1201        } else {
1202            flags |= DateUtils.FORMAT_SHOW_TIME;
1203            if (DateFormat.is24HourFormat(mActivity)) {
1204                flags |= DateUtils.FORMAT_24HOUR;
1205            }
1206        }
1207        long startMillis = mStartTime.normalize(true);
1208        long endMillis = mEndTime.normalize(true);
1209        mSB.setLength(0);
1210        when = DateUtils
1211                .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString();
1212        mWhenView.setText(when);
1213    }
1214
1215    /**
1216     * Configures the Calendars spinner.  This is only done for new events, because only new
1217     * events allow you to select a calendar while editing an event.
1218     * <p>
1219     * We tuck a reference to a Cursor with calendar database data into the spinner, so that
1220     * we can easily extract calendar-specific values when the value changes (the spinner's
1221     * onItemSelected callback is configured).
1222     */
1223    public void setCalendarsCursor(Cursor cursor, boolean userVisible) {
1224        // If there are no syncable calendars, then we cannot allow
1225        // creating a new event.
1226        mCalendarsCursor = cursor;
1227        if (cursor == null || cursor.getCount() == 0) {
1228            // Cancel the "loading calendars" dialog if it exists
1229            if (mSaveAfterQueryComplete) {
1230                mLoadingCalendarsDialog.cancel();
1231            }
1232            if (!userVisible) {
1233                return;
1234            }
1235            // Create an error message for the user that, when clicked,
1236            // will exit this activity without saving the event.
1237            AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
1238            builder.setTitle(R.string.no_syncable_calendars).setIconAttribute(
1239                    android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found)
1240                    .setPositiveButton(R.string.add_account, this)
1241                    .setNegativeButton(android.R.string.no, this).setOnCancelListener(this);
1242            mNoCalendarsDialog = builder.show();
1243            return;
1244        }
1245
1246        int defaultCalendarPosition = findDefaultCalendarPosition(cursor);
1247
1248        // populate the calendars spinner
1249        CalendarsAdapter adapter = new CalendarsAdapter(mActivity, cursor);
1250        mCalendarsSpinner.setAdapter(adapter);
1251        mCalendarsSpinner.setSelection(defaultCalendarPosition);
1252        mCalendarsSpinner.setOnItemSelectedListener(this);
1253
1254        int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
1255        mColorChip.setBackgroundColor(cursor.getInt(colorColumn));
1256
1257
1258        // Find user domain and set it to the validator.
1259        // TODO: we may want to update this validator if the user actually picks
1260        // a different calendar. maybe not. depends on what we want for the
1261        // user experience. this may change when we add support for multiple
1262        // accounts, anyway.
1263        if (mModel != null && mModel.mHasAttendeeData
1264                && cursor.moveToPosition(defaultCalendarPosition)) {
1265            String ownEmail = cursor.getString(EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
1266            if (ownEmail != null) {
1267                String domain = EditEventHelper.extractDomain(ownEmail);
1268                if (domain != null) {
1269                    mEmailValidator = new Rfc822Validator(domain);
1270                    mAttendeesList.setValidator(mEmailValidator);
1271                }
1272            }
1273        }
1274        if (mSaveAfterQueryComplete) {
1275            mLoadingCalendarsDialog.cancel();
1276            if (prepareForSave() && fillModelFromUI()) {
1277                int exit = userVisible ? Utils.DONE_EXIT : 0;
1278                mDone.setDoneCode(Utils.DONE_SAVE | exit);
1279                mDone.run();
1280            } else if (userVisible) {
1281                mDone.setDoneCode(Utils.DONE_EXIT);
1282                mDone.run();
1283            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
1284                Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view");
1285            }
1286            return;
1287        }
1288    }
1289
1290    /**
1291     * Updates the view based on {@link #mModification} and {@link #mModel}
1292     */
1293    public void updateView() {
1294        if (mModel == null) {
1295            return;
1296        }
1297        if (EditEventHelper.canModifyEvent(mModel)) {
1298            setViewStates(mModification);
1299        } else {
1300            setViewStates(Utils.MODIFY_UNINITIALIZED);
1301        }
1302    }
1303
1304    private void setViewStates(int mode) {
1305        // Extra canModify check just in case
1306        if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) {
1307            setWhenString();
1308
1309            for (View v : mViewOnlyList) {
1310                v.setVisibility(View.VISIBLE);
1311            }
1312            for (View v : mEditOnlyList) {
1313                v.setVisibility(View.GONE);
1314            }
1315            for (View v : mEditViewList) {
1316                v.setEnabled(false);
1317                v.setBackgroundDrawable(null);
1318            }
1319            mCalendarSelectorGroup.setVisibility(View.GONE);
1320            mCalendarStaticGroup.setVisibility(View.VISIBLE);
1321            mRepeatsSpinner.setEnabled(false);
1322            mRepeatsSpinner.setBackgroundDrawable(null);
1323            if (EditEventHelper.canAddReminders(mModel)) {
1324                mRemindersGroup.setVisibility(View.VISIBLE);
1325            } else {
1326                mRemindersGroup.setVisibility(View.GONE);
1327            }
1328            setAttendeesEditable(false);
1329            if (mAttendeesView.getChildCount() == 0) {
1330                mAttendeesPane.setVisibility(View.GONE);
1331            } else {
1332                mAttendeesPane.setVisibility(View.VISIBLE);
1333            }
1334            if (mAllDayCheckBox.isChecked()) {
1335                mView.findViewById(R.id.timezone_textview_row).setVisibility(View.GONE);
1336            }
1337            if (TextUtils.isEmpty(mLocationTextView.getText())) {
1338                mLocationGroup.setVisibility(View.GONE);
1339            }
1340            if (TextUtils.isEmpty(mDescriptionTextView.getText())) {
1341                mDescriptionGroup.setVisibility(View.GONE);
1342            }
1343        } else {
1344            for (View v : mViewOnlyList) {
1345                v.setVisibility(View.GONE);
1346            }
1347            for (View v : mEditOnlyList) {
1348                v.setVisibility(View.VISIBLE);
1349            }
1350            for (View v : mEditViewList) {
1351                v.setEnabled(true);
1352                if (v.getTag() != null) {
1353                    v.setBackgroundDrawable((Drawable) v.getTag());
1354                }
1355            }
1356            if (mModel.mUri == null) {
1357                mCalendarSelectorGroup.setVisibility(View.VISIBLE);
1358                mCalendarStaticGroup.setVisibility(View.GONE);
1359            } else {
1360                mCalendarSelectorGroup.setVisibility(View.GONE);
1361                mCalendarStaticGroup.setVisibility(View.VISIBLE);
1362            }
1363            mRepeatsSpinner.setBackgroundDrawable((Drawable) mRepeatsSpinner.getTag());
1364            if (mModel.mOriginalEvent == null) {
1365                mRepeatsSpinner.setEnabled(true);
1366            } else {
1367                mRepeatsSpinner.setEnabled(false);
1368            }
1369            mRemindersGroup.setVisibility(View.VISIBLE);
1370            setAttendeesEditable(true);
1371            mAttendeesPane.setVisibility(View.VISIBLE);
1372
1373            mLocationGroup.setVisibility(View.VISIBLE);
1374            mDescriptionGroup.setVisibility(View.VISIBLE);
1375        }
1376    }
1377
1378    /**
1379     * Shows or hides the Guests view and sets the buttons for removing
1380     * attendees based on the value passed in.
1381     *
1382     * @param editable View.GONE or View.VISIBLE
1383     */
1384    protected void setAttendeesEditable(boolean editable) {
1385        int attCount = mAttendeesView.getChildCount();
1386        if (attCount > 0) {
1387            mResponseGroup.setVisibility(View.VISIBLE);
1388            mAttendeesGroup.setVisibility(View.VISIBLE);
1389        } else {
1390            mResponseGroup.setVisibility(View.GONE);
1391            mAttendeesGroup.setVisibility(View.GONE);
1392        }
1393        mAttendeesView.setEnabled(editable);
1394    }
1395
1396    public void setModification(int modifyWhich) {
1397        mModification = modifyWhich;
1398        updateView();
1399        updateHomeTime();
1400    }
1401
1402    // Find the calendar position in the cursor that matches calendar in
1403    // preference
1404    private int findDefaultCalendarPosition(Cursor calendarsCursor) {
1405        if (calendarsCursor.getCount() <= 0) {
1406            return -1;
1407        }
1408
1409        String defaultCalendar = Utils.getSharedPreference(
1410                mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, null);
1411
1412        if (defaultCalendar == null) {
1413            return 0;
1414        }
1415        int calendarsOwnerColumn = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
1416        int position = 0;
1417        calendarsCursor.moveToPosition(-1);
1418        while (calendarsCursor.moveToNext()) {
1419            if (defaultCalendar.equals(calendarsCursor.getString(calendarsOwnerColumn))) {
1420                return position;
1421            }
1422            position++;
1423        }
1424        return 0;
1425    }
1426
1427    private void updateAttendees(HashMap<String, Attendee> attendeesList) {
1428        mAttendeesView.setRfc822Validator(mEmailValidator);
1429        mAttendeesView.addAttendees(attendeesList);
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);
1453        } else {
1454            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1455                    mReminderMinuteValues, mReminderMinuteLabels,
1456                    mReminderMethodValues, mReminderMethodLabels,
1457                    ReminderEntry.valueOf(mDefaultReminderMinutes),
1458                    mModel.mCalendarMaxReminders);
1459        }
1460        updateRemindersVisibility(mReminderItems.size());
1461    }
1462
1463    // From com.google.android.gm.ComposeActivity
1464    private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) {
1465        RecipientEditTextView list = (RecipientEditTextView) mView.findViewById(res);
1466        if (ChipsUtil.supportsChipsUi()) {
1467            mAddressAdapter = new RecipientAdapter(mActivity);
1468            list.setAdapter((BaseRecipientAdapter) mAddressAdapter);
1469            Resources r = mActivity.getResources();
1470            Bitmap def = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
1471            list.setChipDimensions(
1472                    r.getDrawable(R.drawable.chip_background),
1473                    r.getDrawable(R.drawable.chip_background_selected),
1474                    r.getDrawable(R.drawable.chip_background_invalid),
1475                    r.getDrawable(R.drawable.chip_delete),
1476                    def,
1477                    R.string.more_string,
1478                    R.layout.chips_alternate_item,
1479                    r.getDimension(R.dimen.chip_height),
1480                    r.getDimension(R.dimen.chip_padding),
1481                    r.getDimension(R.dimen.chip_text_size));
1482        } else {
1483            mAddressAdapter = new EmailAddressAdapter(mActivity);
1484            list.setAdapter((EmailAddressAdapter)mAddressAdapter);
1485        }
1486        list.setTokenizer(new Rfc822Tokenizer());
1487        list.setValidator(mEmailValidator);
1488
1489        // NOTE: assumes no other filters are set
1490        list.setFilters(sRecipientFilters);
1491
1492        return list;
1493    }
1494
1495    /**
1496     * From com.google.android.gm.ComposeActivity Implements special address
1497     * cleanup rules: The first space key entry following an "@" symbol that is
1498     * followed by any combination of letters and symbols, including one+ dots
1499     * and zero commas, should insert an extra comma (followed by the space).
1500     */
1501    private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
1502
1503    private void setDate(TextView view, long millis) {
1504        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
1505                | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH
1506                | DateUtils.FORMAT_ABBREV_WEEKDAY;
1507
1508        // Unfortunately, DateUtils doesn't support a timezone other than the
1509        // default timezone provided by the system, so we have this ugly hack
1510        // here to trick it into formatting our time correctly. In order to
1511        // prevent all sorts of craziness, we synchronize on the TimeZone class
1512        // to prevent other threads from reading an incorrect timezone from
1513        // calls to TimeZone#getDefault()
1514        // TODO fix this if/when DateUtils allows for passing in a timezone
1515        String dateString;
1516        synchronized (TimeZone.class) {
1517            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1518            dateString = DateUtils.formatDateTime(mActivity, millis, flags);
1519            // setting the default back to null restores the correct behavior
1520            TimeZone.setDefault(null);
1521        }
1522        view.setText(dateString);
1523    }
1524
1525    private void setTime(TextView view, long millis) {
1526        int flags = DateUtils.FORMAT_SHOW_TIME;
1527        if (DateFormat.is24HourFormat(mActivity)) {
1528            flags |= DateUtils.FORMAT_24HOUR;
1529        }
1530
1531        // Unfortunately, DateUtils doesn't support a timezone other than the
1532        // default timezone provided by the system, so we have this ugly hack
1533        // here to trick it into formatting our time correctly. In order to
1534        // prevent all sorts of craziness, we synchronize on the TimeZone class
1535        // to prevent other threads from reading an incorrect timezone from
1536        // calls to TimeZone#getDefault()
1537        // TODO fix this if/when DateUtils allows for passing in a timezone
1538        String timeString;
1539        synchronized (TimeZone.class) {
1540            TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1541            timeString = DateUtils.formatDateTime(mActivity, millis, flags);
1542            TimeZone.setDefault(null);
1543        }
1544        view.setText(timeString);
1545    }
1546
1547    private void setTimezone(int i) {
1548        if (i < 0 || i >= mTimezoneAdapter.getCount()) {
1549            return; // do nothing
1550        }
1551        TimezoneRow timezone = mTimezoneAdapter.getItem(i);
1552        mTimezoneTextView.setText(timezone.toString());
1553        mTimezoneButton.setText(timezone.toString());
1554        mTimezone = timezone.mId;
1555        mStartTime.timezone = mTimezone;
1556        mStartTime.normalize(true);
1557        mEndTime.timezone = mTimezone;
1558        mEndTime.normalize(true);
1559        mTimezoneAdapter.setCurrentTimezone(mTimezone);
1560    }
1561
1562    // TextWatcher interface
1563    @Override
1564    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
1565    }
1566
1567    // TextWatcher interface
1568    @Override
1569    public void onTextChanged(CharSequence s, int start, int before, int count) {
1570    }
1571
1572    // TextWatcher interface
1573    @Override
1574    public void afterTextChanged(Editable s) {
1575        mAddAttendeesButton.setEnabled(s.length() > 0);
1576    }
1577
1578    /**
1579     * @param isChecked
1580     */
1581    protected void setAllDayViewsVisibility(boolean isChecked) {
1582        if (isChecked) {
1583            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1584                mEndTime.monthDay--;
1585                long endMillis = mEndTime.normalize(true);
1586
1587                // Do not allow an event to have an end time
1588                // before the
1589                // start time.
1590                if (mEndTime.before(mStartTime)) {
1591                    mEndTime.set(mStartTime);
1592                    endMillis = mEndTime.normalize(true);
1593                }
1594                setDate(mEndDateButton, endMillis);
1595                setTime(mEndTimeButton, endMillis);
1596            }
1597
1598            mStartTimeButton.setVisibility(View.GONE);
1599            mEndTimeButton.setVisibility(View.GONE);
1600            mTimezoneButton.setVisibility(View.GONE);
1601            mTimezoneLabel.setVisibility(View.GONE);
1602        } else {
1603            if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1604                mEndTime.monthDay++;
1605                long endMillis = mEndTime.normalize(true);
1606                setDate(mEndDateButton, endMillis);
1607                setTime(mEndTimeButton, endMillis);
1608            }
1609            mStartTimeButton.setVisibility(View.VISIBLE);
1610            mEndTimeButton.setVisibility(View.VISIBLE);
1611            mTimezoneButton.setVisibility(View.VISIBLE);
1612            mTimezoneLabel.setVisibility(View.VISIBLE);
1613        }
1614        updateHomeTime();
1615    }
1616
1617    @Override
1618    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1619        // This is only used for the Calendar spinner in new events, and only fires when the
1620        // calendar selection changes.
1621        Cursor c = (Cursor) parent.getItemAtPosition(position);
1622        if (c == null) {
1623            // TODO: can this happen? should we drop this check?
1624            Log.w(TAG, "Cursor not set on calendar item");
1625            return;
1626        }
1627
1628        int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
1629        mColorChip.setBackgroundColor(c.getInt(colorColumn));
1630        mModel.mCalendarColor = c.getInt(colorColumn);
1631        mCalendarsSpinner.setBackgroundColor(mModel.mCalendarColor);
1632        mCalendarSelectorGroup.setBackgroundColor(mModel.mCalendarColor);
1633
1634
1635        // Update the max/allowed reminders with the new calendar properties.
1636        int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS);
1637        mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn);
1638        int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS);
1639        mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn);
1640
1641        // Discard the current reminders and replace them with the model's default reminder set.
1642        // We could attempt to save & restore the reminders that have been added, but that's
1643        // probably more trouble than it's worth.
1644        mModel.mReminders.clear();
1645        mModel.mReminders.addAll(mModel.mDefaultReminders);
1646        mModel.mHasAlarm = mModel.mReminders.size() != 0;
1647
1648        // Update the UI elements.
1649        mReminderItems.clear();
1650        LinearLayout reminderLayout =
1651            (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container);
1652        reminderLayout.removeAllViews();
1653        prepareReminders();
1654    }
1655
1656    /**
1657     * Checks if the start and end times for this event should be displayed in
1658     * the Calendar app's time zone as well and formats and displays them.
1659     */
1660    private void updateHomeTime() {
1661        String tz = Utils.getTimeZone(mActivity, null);
1662        if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone)
1663                && mModification != EditEventHelper.MODIFY_UNINITIALIZED) {
1664            int flags = DateUtils.FORMAT_SHOW_TIME;
1665            boolean is24Format = DateFormat.is24HourFormat(mActivity);
1666            if (is24Format) {
1667                flags |= DateUtils.FORMAT_24HOUR;
1668            }
1669            long millisStart = mStartTime.toMillis(false);
1670            long millisEnd = mEndTime.toMillis(false);
1671
1672            boolean isDSTStart = mStartTime.isDst != 0;
1673            boolean isDSTEnd = mEndTime.isDst != 0;
1674
1675            // First update the start date and times
1676            String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1677                    isDSTStart, TimeZone.SHORT, Locale.getDefault());
1678            StringBuilder time = new StringBuilder();
1679
1680            mSB.setLength(0);
1681            time.append(DateUtils
1682                    .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz))
1683                    .append(" ").append(tzDisplay);
1684            mStartTimeHome.setText(time.toString());
1685
1686            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1687                    | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1688            mSB.setLength(0);
1689            mStartDateHome
1690                    .setText(DateUtils.formatDateRange(
1691                            mActivity, mF, millisStart, millisStart, flags, tz).toString());
1692
1693            // Make any adjustments needed for the end times
1694            if (isDSTEnd != isDSTStart) {
1695                tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1696                        isDSTEnd, TimeZone.SHORT, Locale.getDefault());
1697            }
1698            flags = DateUtils.FORMAT_SHOW_TIME;
1699            if (is24Format) {
1700                flags |= DateUtils.FORMAT_24HOUR;
1701            }
1702
1703            // Then update the end times
1704            time.setLength(0);
1705            mSB.setLength(0);
1706            time.append(DateUtils.formatDateRange(
1707                    mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay);
1708            mEndTimeHome.setText(time.toString());
1709
1710            flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1711                    | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1712            mSB.setLength(0);
1713            mEndDateHome.setText(DateUtils.formatDateRange(
1714                            mActivity, mF, millisEnd, millisEnd, flags, tz).toString());
1715
1716            mStartHomeGroup.setVisibility(View.VISIBLE);
1717            mEndHomeGroup.setVisibility(View.VISIBLE);
1718        } else {
1719            mStartHomeGroup.setVisibility(View.GONE);
1720            mEndHomeGroup.setVisibility(View.GONE);
1721        }
1722    }
1723
1724    @Override
1725    public void onNothingSelected(AdapterView<?> parent) {
1726        mColorChip.setBackgroundColor(0);
1727
1728    }
1729}
1730