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