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