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