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