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