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 
17 package com.android.calendar.event;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.DialogFragment;
22 import android.app.FragmentManager;
23 import android.app.ProgressDialog;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.res.Resources;
30 import android.database.Cursor;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.provider.CalendarContract;
34 import android.provider.CalendarContract.Attendees;
35 import android.provider.CalendarContract.Calendars;
36 import android.provider.CalendarContract.Events;
37 import android.provider.CalendarContract.Reminders;
38 import android.provider.Settings;
39 import android.text.InputFilter;
40 import android.text.TextUtils;
41 import android.text.format.DateFormat;
42 import android.text.format.DateUtils;
43 import android.text.format.Time;
44 import android.text.util.Rfc822Tokenizer;
45 import android.util.Log;
46 import android.view.KeyEvent;
47 import android.view.View;
48 import android.view.View.OnClickListener;
49 import android.view.ViewGroup;
50 import android.view.accessibility.AccessibilityEvent;
51 import android.view.accessibility.AccessibilityManager;
52 import android.view.inputmethod.EditorInfo;
53 import android.widget.AdapterView;
54 import android.widget.AdapterView.OnItemSelectedListener;
55 import android.widget.ArrayAdapter;
56 import android.widget.AutoCompleteTextView;
57 import android.widget.Button;
58 import android.widget.CheckBox;
59 import android.widget.CompoundButton;
60 import android.widget.LinearLayout;
61 import android.widget.MultiAutoCompleteTextView;
62 import android.widget.RadioButton;
63 import android.widget.RadioGroup;
64 import android.widget.ResourceCursorAdapter;
65 import android.widget.ScrollView;
66 import android.widget.Spinner;
67 import android.widget.TextView;
68 import android.widget.TextView.OnEditorActionListener;
69 
70 import com.android.calendar.CalendarEventModel;
71 import com.android.calendar.CalendarEventModel.Attendee;
72 import com.android.calendar.CalendarEventModel.ReminderEntry;
73 import com.android.calendar.EmailAddressAdapter;
74 import com.android.calendar.EventInfoFragment;
75 import com.android.calendar.EventRecurrenceFormatter;
76 import com.android.calendar.GeneralPreferences;
77 import com.android.calendar.R;
78 import com.android.calendar.RecipientAdapter;
79 import com.android.calendar.Utils;
80 import com.android.calendar.event.EditEventHelper.EditDoneRunnable;
81 import com.android.calendar.recurrencepicker.RecurrencePickerDialog;
82 import com.android.calendarcommon2.EventRecurrence;
83 import com.android.common.Rfc822InputFilter;
84 import com.android.common.Rfc822Validator;
85 import com.android.datetimepicker.date.DatePickerDialog;
86 import com.android.datetimepicker.date.DatePickerDialog.OnDateSetListener;
87 import com.android.datetimepicker.time.RadialPickerLayout;
88 import com.android.datetimepicker.time.TimePickerDialog;
89 import com.android.datetimepicker.time.TimePickerDialog.OnTimeSetListener;
90 import com.android.ex.chips.AccountSpecifier;
91 import com.android.ex.chips.BaseRecipientAdapter;
92 import com.android.ex.chips.ChipsUtil;
93 import com.android.ex.chips.RecipientEditTextView;
94 import com.android.timezonepicker.TimeZoneInfo;
95 import com.android.timezonepicker.TimeZonePickerDialog;
96 import com.android.timezonepicker.TimeZonePickerUtils;
97 
98 import java.util.ArrayList;
99 import java.util.Arrays;
100 import java.util.Formatter;
101 import java.util.HashMap;
102 import java.util.Locale;
103 import java.util.TimeZone;
104 
105 public class EditEventView implements View.OnClickListener, DialogInterface.OnCancelListener,
106         DialogInterface.OnClickListener, OnItemSelectedListener,
107         RecurrencePickerDialog.OnRecurrenceSetListener,
108         TimeZonePickerDialog.OnTimeZoneSetListener {
109 
110     private static final String TAG = "EditEvent";
111     private static final String GOOGLE_SECONDARY_CALENDAR = "calendar.google.com";
112     private static final String PERIOD_SPACE = ". ";
113 
114     private static final String FRAG_TAG_DATE_PICKER = "datePickerDialogFragment";
115     private static final String FRAG_TAG_TIME_PICKER = "timePickerDialogFragment";
116     private static final String FRAG_TAG_TIME_ZONE_PICKER = "timeZonePickerDialogFragment";
117     private static final String FRAG_TAG_RECUR_PICKER = "recurrencePickerDialogFragment";
118 
119     ArrayList<View> mEditOnlyList = new ArrayList<View>();
120     ArrayList<View> mEditViewList = new ArrayList<View>();
121     ArrayList<View> mViewOnlyList = new ArrayList<View>();
122     TextView mLoadingMessage;
123     ScrollView mScrollView;
124     Button mStartDateButton;
125     Button mEndDateButton;
126     Button mStartTimeButton;
127     Button mEndTimeButton;
128     Button mTimezoneButton;
129     View mColorPickerNewEvent;
130     View mColorPickerExistingEvent;
131     OnClickListener mChangeColorOnClickListener;
132     View mTimezoneRow;
133     TextView mStartTimeHome;
134     TextView mStartDateHome;
135     TextView mEndTimeHome;
136     TextView mEndDateHome;
137     CheckBox mAllDayCheckBox;
138     Spinner mCalendarsSpinner;
139     Button mRruleButton;
140     Spinner mAvailabilitySpinner;
141     Spinner mAccessLevelSpinner;
142     RadioGroup mResponseRadioGroup;
143     TextView mTitleTextView;
144     AutoCompleteTextView mLocationTextView;
145     EventLocationAdapter mLocationAdapter;
146     TextView mDescriptionTextView;
147     TextView mWhenView;
148     TextView mTimezoneTextView;
149     TextView mTimezoneLabel;
150     LinearLayout mRemindersContainer;
151     MultiAutoCompleteTextView mAttendeesList;
152     View mCalendarSelectorGroup;
153     View mCalendarSelectorWrapper;
154     View mCalendarStaticGroup;
155     View mLocationGroup;
156     View mDescriptionGroup;
157     View mRemindersGroup;
158     View mResponseGroup;
159     View mOrganizerGroup;
160     View mAttendeesGroup;
161     View mStartHomeGroup;
162     View mEndHomeGroup;
163 
164     private int[] mOriginalPadding = new int[4];
165 
166     public boolean mIsMultipane;
167     private ProgressDialog mLoadingCalendarsDialog;
168     private AlertDialog mNoCalendarsDialog;
169     private DialogFragment mTimezoneDialog;
170     private Activity mActivity;
171     private EditDoneRunnable mDone;
172     private View mView;
173     private CalendarEventModel mModel;
174     private Cursor mCalendarsCursor;
175     private AccountSpecifier mAddressAdapter;
176     private Rfc822Validator mEmailValidator;
177 
178     public boolean mTimeSelectedWasStartTime;
179     public boolean mDateSelectedWasStartDate;
180     private TimePickerDialog mStartTimePickerDialog;
181     private TimePickerDialog mEndTimePickerDialog;
182     private DatePickerDialog mDatePickerDialog;
183 
184     /**
185      * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
186      * with any additional values that were already associated with the event.
187      */
188     private ArrayList<Integer> mReminderMinuteValues;
189     private ArrayList<String> mReminderMinuteLabels;
190 
191     /**
192      * Contents of the "methods" spinner.  The "values" list specifies the method constant
193      * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
194      * aren't allowed by the Calendar will be removed.
195      */
196     private ArrayList<Integer> mReminderMethodValues;
197     private ArrayList<String> mReminderMethodLabels;
198 
199     /**
200      * Contents of the "availability" spinner. The "values" list specifies the
201      * type constant (e.g. {@link Events#AVAILABILITY_BUSY}) associated with the
202      * labels. Any types that aren't allowed by the Calendar will be removed.
203      */
204     private ArrayList<Integer> mAvailabilityValues;
205     private ArrayList<String> mAvailabilityLabels;
206     private ArrayList<String> mOriginalAvailabilityLabels;
207     private ArrayAdapter<String> mAvailabilityAdapter;
208     private boolean mAvailabilityExplicitlySet;
209     private boolean mAllDayChangingAvailability;
210     private int mAvailabilityCurrentlySelected;
211 
212     private int mDefaultReminderMinutes;
213 
214     private boolean mSaveAfterQueryComplete = false;
215 
216     private TimeZonePickerUtils mTzPickerUtils;
217     private Time mStartTime;
218     private Time mEndTime;
219     private String mTimezone;
220     private boolean mAllDay = false;
221     private int mModification = EditEventHelper.MODIFY_UNINITIALIZED;
222 
223     private EventRecurrence mEventRecurrence = new EventRecurrence();
224 
225     private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0);
226     private ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
227     private String mRrule;
228 
229     private static StringBuilder mSB = new StringBuilder(50);
230     private static Formatter mF = new Formatter(mSB, Locale.getDefault());
231 
232     /* This class is used to update the time buttons. */
233     private class TimeListener implements OnTimeSetListener {
234         private View mView;
235 
TimeListener(View view)236         public TimeListener(View view) {
237             mView = view;
238         }
239 
240         @Override
onTimeSet(RadialPickerLayout view, int hourOfDay, int minute)241         public void onTimeSet(RadialPickerLayout view, int hourOfDay, int minute) {
242             // Cache the member variables locally to avoid inner class overhead.
243             Time startTime = mStartTime;
244             Time endTime = mEndTime;
245 
246             // Cache the start and end millis so that we limit the number
247             // of calls to normalize() and toMillis(), which are fairly
248             // expensive.
249             long startMillis;
250             long endMillis;
251             if (mView == mStartTimeButton) {
252                 // The start time was changed.
253                 int hourDuration = endTime.hour - startTime.hour;
254                 int minuteDuration = endTime.minute - startTime.minute;
255 
256                 startTime.hour = hourOfDay;
257                 startTime.minute = minute;
258                 startMillis = startTime.normalize(true);
259 
260                 // Also update the end time to keep the duration constant.
261                 endTime.hour = hourOfDay + hourDuration;
262                 endTime.minute = minute + minuteDuration;
263 
264                 // Update tz in case the start time switched from/to DLS
265                 populateTimezone(startMillis);
266             } else {
267                 // The end time was changed.
268                 startMillis = startTime.toMillis(true);
269                 endTime.hour = hourOfDay;
270                 endTime.minute = minute;
271 
272                 // Move to the start time if the end time is before the start
273                 // time.
274                 if (endTime.before(startTime)) {
275                     endTime.monthDay = startTime.monthDay + 1;
276                 }
277                 // Call populateTimezone if we support end time zone as well
278             }
279 
280             endMillis = endTime.normalize(true);
281 
282             setDate(mEndDateButton, endMillis);
283             setTime(mStartTimeButton, startMillis);
284             setTime(mEndTimeButton, endMillis);
285             updateHomeTime();
286         }
287     }
288 
289     private class TimeClickListener implements View.OnClickListener {
290         private Time mTime;
291 
TimeClickListener(Time time)292         public TimeClickListener(Time time) {
293             mTime = time;
294         }
295 
296         @Override
onClick(View v)297         public void onClick(View v) {
298 
299             TimePickerDialog dialog;
300             if (v == mStartTimeButton) {
301                 mTimeSelectedWasStartTime = true;
302                 if (mStartTimePickerDialog == null) {
303                     mStartTimePickerDialog = TimePickerDialog.newInstance(new TimeListener(v),
304                             mTime.hour, mTime.minute, DateFormat.is24HourFormat(mActivity));
305                 } else {
306                     mStartTimePickerDialog.setStartTime(mTime.hour, mTime.minute);
307                 }
308                 dialog = mStartTimePickerDialog;
309             } else {
310                 mTimeSelectedWasStartTime = false;
311                 if (mEndTimePickerDialog == null) {
312                     mEndTimePickerDialog = TimePickerDialog.newInstance(new TimeListener(v),
313                             mTime.hour, mTime.minute, DateFormat.is24HourFormat(mActivity));
314                 } else {
315                     mEndTimePickerDialog.setStartTime(mTime.hour, mTime.minute);
316                 }
317                 dialog = mEndTimePickerDialog;
318 
319             }
320 
321             final FragmentManager fm = mActivity.getFragmentManager();
322             fm.executePendingTransactions();
323 
324             if (dialog != null && !dialog.isAdded()) {
325                 dialog.show(fm, FRAG_TAG_TIME_PICKER);
326             }
327         }
328     }
329 
330     private class DateListener implements OnDateSetListener {
331         View mView;
332 
DateListener(View view)333         public DateListener(View view) {
334             mView = view;
335         }
336 
337         @Override
onDateSet(DatePickerDialog view, int year, int month, int monthDay)338         public void onDateSet(DatePickerDialog view, int year, int month, int monthDay) {
339             Log.d(TAG, "onDateSet: " + year +  " " + month +  " " + monthDay);
340             // Cache the member variables locally to avoid inner class overhead.
341             Time startTime = mStartTime;
342             Time endTime = mEndTime;
343 
344             // Cache the start and end millis so that we limit the number
345             // of calls to normalize() and toMillis(), which are fairly
346             // expensive.
347             long startMillis;
348             long endMillis;
349             if (mView == mStartDateButton) {
350                 // The start date was changed.
351                 int yearDuration = endTime.year - startTime.year;
352                 int monthDuration = endTime.month - startTime.month;
353                 int monthDayDuration = endTime.monthDay - startTime.monthDay;
354 
355                 startTime.year = year;
356                 startTime.month = month;
357                 startTime.monthDay = monthDay;
358                 startMillis = startTime.normalize(true);
359 
360                 // Also update the end date to keep the duration constant.
361                 endTime.year = year + yearDuration;
362                 endTime.month = month + monthDuration;
363                 endTime.monthDay = monthDay + monthDayDuration;
364                 endMillis = endTime.normalize(true);
365 
366                 // If the start date has changed then update the repeats.
367                 populateRepeats();
368 
369                 // Update tz in case the start time switched from/to DLS
370                 populateTimezone(startMillis);
371             } else {
372                 // The end date was changed.
373                 startMillis = startTime.toMillis(true);
374                 endTime.year = year;
375                 endTime.month = month;
376                 endTime.monthDay = monthDay;
377                 endMillis = endTime.normalize(true);
378 
379                 // Do not allow an event to have an end time before the start
380                 // time.
381                 if (endTime.before(startTime)) {
382                     endTime.set(startTime);
383                     endMillis = startMillis;
384                 }
385                 // Call populateTimezone if we support end time zone as well
386             }
387 
388             setDate(mStartDateButton, startMillis);
389             setDate(mEndDateButton, endMillis);
390             setTime(mEndTimeButton, endMillis); // In case end time had to be
391             // reset
392             updateHomeTime();
393         }
394     }
395 
396     // Fills in the date and time fields
populateWhen()397     private void populateWhen() {
398         long startMillis = mStartTime.toMillis(false /* use isDst */);
399         long endMillis = mEndTime.toMillis(false /* use isDst */);
400         setDate(mStartDateButton, startMillis);
401         setDate(mEndDateButton, endMillis);
402 
403         setTime(mStartTimeButton, startMillis);
404         setTime(mEndTimeButton, endMillis);
405 
406         mStartDateButton.setOnClickListener(new DateClickListener(mStartTime));
407         mEndDateButton.setOnClickListener(new DateClickListener(mEndTime));
408 
409         mStartTimeButton.setOnClickListener(new TimeClickListener(mStartTime));
410         mEndTimeButton.setOnClickListener(new TimeClickListener(mEndTime));
411     }
412 
413     // Implements OnTimeZoneSetListener
414     @Override
onTimeZoneSet(TimeZoneInfo tzi)415     public void onTimeZoneSet(TimeZoneInfo tzi) {
416         setTimezone(tzi.mTzId);
417         updateHomeTime();
418     }
419 
setTimezone(String timeZone)420     private void setTimezone(String timeZone) {
421         mTimezone = timeZone;
422         mStartTime.timezone = mTimezone;
423         long timeMillis = mStartTime.normalize(true);
424         mEndTime.timezone = mTimezone;
425         mEndTime.normalize(true);
426 
427         populateTimezone(timeMillis);
428     }
429 
populateTimezone(long eventStartTime)430     private void populateTimezone(long eventStartTime) {
431         if (mTzPickerUtils == null) {
432             mTzPickerUtils = new TimeZonePickerUtils(mActivity);
433         }
434         CharSequence displayName =
435                 mTzPickerUtils.getGmtDisplayName(mActivity, mTimezone, eventStartTime, true);
436 
437         mTimezoneTextView.setText(displayName);
438         mTimezoneButton.setText(displayName);
439     }
440 
showTimezoneDialog()441     private void showTimezoneDialog() {
442         Bundle b = new Bundle();
443         b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, mStartTime.toMillis(false));
444         b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, mTimezone);
445 
446         FragmentManager fm = mActivity.getFragmentManager();
447         TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm
448                 .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
449         if (tzpd != null) {
450             tzpd.dismiss();
451         }
452         tzpd = new TimeZonePickerDialog();
453         tzpd.setArguments(b);
454         tzpd.setOnTimeZoneSetListener(EditEventView.this);
455         tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER);
456     }
457 
populateRepeats()458     private void populateRepeats() {
459         Resources r = mActivity.getResources();
460         String repeatString;
461         boolean enabled;
462         if (!TextUtils.isEmpty(mRrule)) {
463             repeatString = EventRecurrenceFormatter.getRepeatString(mActivity, r,
464                     mEventRecurrence, true);
465 
466             if (repeatString == null) {
467                 repeatString = r.getString(R.string.custom);
468                 Log.e(TAG, "Can't generate display string for " + mRrule);
469                 enabled = false;
470             } else {
471                 // TODO Should give option to clear/reset rrule
472                 enabled = RecurrencePickerDialog.canHandleRecurrenceRule(mEventRecurrence);
473                 if (!enabled) {
474                     Log.e(TAG, "UI can't handle " + mRrule);
475                 }
476             }
477         } else {
478             repeatString = r.getString(R.string.does_not_repeat);
479             enabled = true;
480         }
481 
482         mRruleButton.setText(repeatString);
483 
484         // Don't allow the user to make exceptions recurring events.
485         if (mModel.mOriginalSyncId != null) {
486             enabled = false;
487         }
488         mRruleButton.setOnClickListener(this);
489         mRruleButton.setEnabled(enabled);
490     }
491 
492     private class DateClickListener implements View.OnClickListener {
493         private Time mTime;
494 
DateClickListener(Time time)495         public DateClickListener(Time time) {
496             mTime = time;
497         }
498 
499         @Override
onClick(View v)500         public void onClick(View v) {
501             if (!mView.hasWindowFocus()) {
502                 // Don't do anything if the activity if paused. Since Activity doesn't
503                 // have a built in way to do this, we would have to implement one ourselves and
504                 // either cast our Activity to a specialized activity base class or implement some
505                 // generic interface that tells us if an activity is paused. hasWindowFocus() is
506                 // close enough if not quite perfect.
507                 return;
508             }
509             if (v == mStartDateButton) {
510                 mDateSelectedWasStartDate = true;
511             } else {
512                 mDateSelectedWasStartDate = false;
513             }
514 
515             final DateListener listener = new DateListener(v);
516             if (mDatePickerDialog != null) {
517                 mDatePickerDialog.dismiss();
518             }
519             mDatePickerDialog = DatePickerDialog.newInstance(listener,
520                     mTime.year, mTime.month, mTime.monthDay);
521             mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(mActivity));
522             mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX);
523             mDatePickerDialog.show(mActivity.getFragmentManager(), FRAG_TAG_DATE_PICKER);
524         }
525     }
526 
527     public static class CalendarsAdapter extends ResourceCursorAdapter {
CalendarsAdapter(Context context, int resourceId, Cursor c)528         public CalendarsAdapter(Context context, int resourceId, Cursor c) {
529             super(context, resourceId, c);
530             setDropDownViewResource(R.layout.calendars_dropdown_item);
531         }
532 
533         @Override
bindView(View view, Context context, Cursor cursor)534         public void bindView(View view, Context context, Cursor cursor) {
535             View colorBar = view.findViewById(R.id.color);
536             int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
537             int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME);
538             int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
539             if (colorBar != null) {
540                 colorBar.setBackgroundColor(Utils.getDisplayColorFromColor(cursor
541                         .getInt(colorColumn)));
542             }
543 
544             TextView name = (TextView) view.findViewById(R.id.calendar_name);
545             if (name != null) {
546                 String displayName = cursor.getString(nameColumn);
547                 name.setText(displayName);
548 
549                 TextView accountName = (TextView) view.findViewById(R.id.account_name);
550                 if (accountName != null) {
551                     accountName.setText(cursor.getString(ownerColumn));
552                     accountName.setVisibility(TextView.VISIBLE);
553                 }
554             }
555         }
556     }
557 
558     /**
559      * Does prep steps for saving a calendar event.
560      *
561      * This triggers a parse of the attendees list and checks if the event is
562      * ready to be saved. An event is ready to be saved so long as a model
563      * exists and has a calendar it can be associated with, either because it's
564      * an existing event or we've finished querying.
565      *
566      * @return false if there is no model or no calendar had been loaded yet,
567      * true otherwise.
568      */
prepareForSave()569     public boolean prepareForSave() {
570         if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
571             return false;
572         }
573         return fillModelFromUI();
574     }
575 
fillModelFromReadOnlyUi()576     public boolean fillModelFromReadOnlyUi() {
577         if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
578             return false;
579         }
580         mModel.mReminders = EventViewUtils.reminderItemsToReminders(
581                     mReminderItems, mReminderMinuteValues, mReminderMethodValues);
582         mModel.mReminders.addAll(mUnsupportedReminders);
583         mModel.normalizeReminders();
584         int status = EventInfoFragment.getResponseFromButtonId(
585                 mResponseRadioGroup.getCheckedRadioButtonId());
586         if (status != Attendees.ATTENDEE_STATUS_NONE) {
587             mModel.mSelfAttendeeStatus = status;
588         }
589         return true;
590     }
591 
592     // This is called if the user clicks on one of the buttons: "Save",
593     // "Discard", or "Delete". This is also called if the user clicks
594     // on the "remove reminder" button.
595     @Override
onClick(View view)596     public void onClick(View view) {
597         if (view == mRruleButton) {
598             Bundle b = new Bundle();
599             b.putLong(RecurrencePickerDialog.BUNDLE_START_TIME_MILLIS,
600                     mStartTime.toMillis(false));
601             b.putString(RecurrencePickerDialog.BUNDLE_TIME_ZONE, mStartTime.timezone);
602 
603             // TODO may be more efficient to serialize and pass in EventRecurrence
604             b.putString(RecurrencePickerDialog.BUNDLE_RRULE, mRrule);
605 
606             FragmentManager fm = mActivity.getFragmentManager();
607             RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm
608                     .findFragmentByTag(FRAG_TAG_RECUR_PICKER);
609             if (rpd != null) {
610                 rpd.dismiss();
611             }
612             rpd = new RecurrencePickerDialog();
613             rpd.setArguments(b);
614             rpd.setOnRecurrenceSetListener(EditEventView.this);
615             rpd.show(fm, FRAG_TAG_RECUR_PICKER);
616             return;
617         }
618 
619         // This must be a click on one of the "remove reminder" buttons
620         LinearLayout reminderItem = (LinearLayout) view.getParent();
621         LinearLayout parent = (LinearLayout) reminderItem.getParent();
622         parent.removeView(reminderItem);
623         mReminderItems.remove(reminderItem);
624         updateRemindersVisibility(mReminderItems.size());
625         EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
626     }
627 
628     @Override
onRecurrenceSet(String rrule)629     public void onRecurrenceSet(String rrule) {
630         Log.d(TAG, "Old rrule:" + mRrule);
631         Log.d(TAG, "New rrule:" + rrule);
632         mRrule = rrule;
633         if (mRrule != null) {
634             mEventRecurrence.parse(mRrule);
635         }
636         populateRepeats();
637     }
638 
639     // This is called if the user cancels the "No calendars" dialog.
640     // The "No calendars" dialog is shown if there are no syncable calendars.
641     @Override
onCancel(DialogInterface dialog)642     public void onCancel(DialogInterface dialog) {
643         if (dialog == mLoadingCalendarsDialog) {
644             mLoadingCalendarsDialog = null;
645             mSaveAfterQueryComplete = false;
646         } else if (dialog == mNoCalendarsDialog) {
647             mDone.setDoneCode(Utils.DONE_REVERT);
648             mDone.run();
649             return;
650         }
651     }
652 
653     // This is called if the user clicks on a dialog button.
654     @Override
onClick(DialogInterface dialog, int which)655     public void onClick(DialogInterface dialog, int which) {
656         if (dialog == mNoCalendarsDialog) {
657             mDone.setDoneCode(Utils.DONE_REVERT);
658             mDone.run();
659             if (which == DialogInterface.BUTTON_POSITIVE) {
660                 Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
661                 final String[] array = {"com.android.calendar"};
662                 nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array);
663                 nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
664                 mActivity.startActivity(nextIntent);
665             }
666         }
667     }
668 
669     // Goes through the UI elements and updates the model as necessary
fillModelFromUI()670     private boolean fillModelFromUI() {
671         if (mModel == null) {
672             return false;
673         }
674         mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems,
675                 mReminderMinuteValues, mReminderMethodValues);
676         mModel.mReminders.addAll(mUnsupportedReminders);
677         mModel.normalizeReminders();
678         mModel.mHasAlarm = mReminderItems.size() > 0;
679         mModel.mTitle = mTitleTextView.getText().toString();
680         mModel.mAllDay = mAllDayCheckBox.isChecked();
681         mModel.mLocation = mLocationTextView.getText().toString();
682         mModel.mDescription = mDescriptionTextView.getText().toString();
683         if (TextUtils.isEmpty(mModel.mLocation)) {
684             mModel.mLocation = null;
685         }
686         if (TextUtils.isEmpty(mModel.mDescription)) {
687             mModel.mDescription = null;
688         }
689 
690         int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup
691                 .getCheckedRadioButtonId());
692         if (status != Attendees.ATTENDEE_STATUS_NONE) {
693             mModel.mSelfAttendeeStatus = status;
694         }
695 
696         if (mAttendeesList != null) {
697             mEmailValidator.setRemoveInvalid(true);
698             mAttendeesList.performValidation();
699             mModel.mAttendeesList.clear();
700             mModel.addAttendees(mAttendeesList.getText().toString(), mEmailValidator);
701             mEmailValidator.setRemoveInvalid(false);
702         }
703 
704         // If this was a new event we need to fill in the Calendar information
705         if (mModel.mUri == null) {
706             mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId();
707             int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition();
708             if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) {
709                 String defaultCalendar = mCalendarsCursor.getString(
710                         EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT);
711                 Utils.setSharedPreference(
712                         mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar);
713                 mModel.mOwnerAccount = defaultCalendar;
714                 mModel.mOrganizer = defaultCalendar;
715                 mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID);
716             }
717         }
718 
719         if (mModel.mAllDay) {
720             // Reset start and end time, increment the monthDay by 1, and set
721             // the timezone to UTC, as required for all-day events.
722             mTimezone = Time.TIMEZONE_UTC;
723             mStartTime.hour = 0;
724             mStartTime.minute = 0;
725             mStartTime.second = 0;
726             mStartTime.timezone = mTimezone;
727             mModel.mStart = mStartTime.normalize(true);
728 
729             mEndTime.hour = 0;
730             mEndTime.minute = 0;
731             mEndTime.second = 0;
732             mEndTime.timezone = mTimezone;
733             // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time
734             // should be Y + 1 (Oct.30).
735             final long normalizedEndTimeMillis =
736                     mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS;
737             if (normalizedEndTimeMillis < mModel.mStart) {
738                 // mEnd should be midnight of the next day of mStart.
739                 mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS;
740             } else {
741                 mModel.mEnd = normalizedEndTimeMillis;
742             }
743         } else {
744             mStartTime.timezone = mTimezone;
745             mEndTime.timezone = mTimezone;
746             mModel.mStart = mStartTime.toMillis(true);
747             mModel.mEnd = mEndTime.toMillis(true);
748         }
749         mModel.mTimezone = mTimezone;
750         mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition();
751         // TODO set correct availability value
752         mModel.mAvailability = mAvailabilityValues.get(mAvailabilitySpinner
753                 .getSelectedItemPosition());
754 
755         // rrrule
756         // If we're making an exception we don't want it to be a repeating
757         // event.
758         if (mModification == EditEventHelper.MODIFY_SELECTED) {
759             mModel.mRrule = null;
760         } else {
761             mModel.mRrule = mRrule;
762         }
763 
764         return true;
765     }
766 
EditEventView(Activity activity, View view, EditDoneRunnable done, boolean timeSelectedWasStartTime, boolean dateSelectedWasStartDate)767     public EditEventView(Activity activity, View view, EditDoneRunnable done,
768             boolean timeSelectedWasStartTime, boolean dateSelectedWasStartDate) {
769 
770         mActivity = activity;
771         mView = view;
772         mDone = done;
773 
774         // cache top level view elements
775         mLoadingMessage = (TextView) view.findViewById(R.id.loading_message);
776         mScrollView = (ScrollView) view.findViewById(R.id.scroll_view);
777 
778         // cache all the widgets
779         mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner);
780         mTitleTextView = (TextView) view.findViewById(R.id.title);
781         mLocationTextView = (AutoCompleteTextView) view.findViewById(R.id.location);
782         mDescriptionTextView = (TextView) view.findViewById(R.id.description);
783         mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label);
784         mStartDateButton = (Button) view.findViewById(R.id.start_date);
785         mEndDateButton = (Button) view.findViewById(R.id.end_date);
786         mWhenView = (TextView) mView.findViewById(R.id.when);
787         mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView);
788         mStartTimeButton = (Button) view.findViewById(R.id.start_time);
789         mEndTimeButton = (Button) view.findViewById(R.id.end_time);
790         mTimezoneButton = (Button) view.findViewById(R.id.timezone_button);
791         mTimezoneButton.setOnClickListener(new View.OnClickListener() {
792             @Override
793             public void onClick(View v) {
794                 showTimezoneDialog();
795             }
796         });
797         mTimezoneRow = view.findViewById(R.id.timezone_button_row);
798         mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz);
799         mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz);
800         mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz);
801         mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz);
802         mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day);
803         mRruleButton = (Button) view.findViewById(R.id.rrule);
804         mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability);
805         mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility);
806         mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group);
807         mCalendarSelectorWrapper = view.findViewById(R.id.calendar_selector_wrapper);
808         mCalendarStaticGroup = view.findViewById(R.id.calendar_group);
809         mRemindersGroup = view.findViewById(R.id.reminders_row);
810         mResponseGroup = view.findViewById(R.id.response_row);
811         mOrganizerGroup = view.findViewById(R.id.organizer_row);
812         mAttendeesGroup = view.findViewById(R.id.add_attendees_row);
813         mLocationGroup = view.findViewById(R.id.where_row);
814         mDescriptionGroup = view.findViewById(R.id.description_row);
815         mStartHomeGroup = view.findViewById(R.id.from_row_home_tz);
816         mEndHomeGroup = view.findViewById(R.id.to_row_home_tz);
817         mAttendeesList = (MultiAutoCompleteTextView) view.findViewById(R.id.attendees);
818 
819         mColorPickerNewEvent = view.findViewById(R.id.change_color_new_event);
820         mColorPickerExistingEvent = view.findViewById(R.id.change_color_existing_event);
821 
822         mTitleTextView.setTag(mTitleTextView.getBackground());
823         mLocationTextView.setTag(mLocationTextView.getBackground());
824         mLocationAdapter = new EventLocationAdapter(activity);
825         mLocationTextView.setAdapter(mLocationAdapter);
826         mLocationTextView.setOnEditorActionListener(new OnEditorActionListener() {
827             @Override
828             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
829                 if (actionId == EditorInfo.IME_ACTION_DONE) {
830                     // Dismiss the suggestions dropdown.  Return false so the other
831                     // side effects still occur (soft keyboard going away, etc.).
832                     mLocationTextView.dismissDropDown();
833                 }
834                 return false;
835             }
836         });
837 
838         mAvailabilityExplicitlySet = false;
839         mAllDayChangingAvailability = false;
840         mAvailabilityCurrentlySelected = -1;
841         mAvailabilitySpinner.setOnItemSelectedListener(
842                 new OnItemSelectedListener() {
843             @Override
844             public void onItemSelected(AdapterView<?> parent,
845                     View view, int position, long id) {
846                 // The spinner's onItemSelected gets called while it is being
847                 // initialized to the first item, and when we explicitly set it
848                 // in the allDay checkbox toggling, so we need these checks to
849                 // find out when the spinner is actually being clicked.
850 
851                 // Set the initial selection.
852                 if (mAvailabilityCurrentlySelected == -1) {
853                     mAvailabilityCurrentlySelected = position;
854                 }
855 
856                 if (mAvailabilityCurrentlySelected != position &&
857                         !mAllDayChangingAvailability) {
858                     mAvailabilityExplicitlySet = true;
859                 } else {
860                     mAvailabilityCurrentlySelected = position;
861                     mAllDayChangingAvailability = false;
862                 }
863             }
864             @Override
865             public void onNothingSelected(AdapterView<?> arg0) { }
866         });
867 
868 
869         mDescriptionTextView.setTag(mDescriptionTextView.getBackground());
870         mAttendeesList.setTag(mAttendeesList.getBackground());
871         mOriginalPadding[0] = mLocationTextView.getPaddingLeft();
872         mOriginalPadding[1] = mLocationTextView.getPaddingTop();
873         mOriginalPadding[2] = mLocationTextView.getPaddingRight();
874         mOriginalPadding[3] = mLocationTextView.getPaddingBottom();
875         mEditViewList.add(mTitleTextView);
876         mEditViewList.add(mLocationTextView);
877         mEditViewList.add(mDescriptionTextView);
878         mEditViewList.add(mAttendeesList);
879 
880         mViewOnlyList.add(view.findViewById(R.id.when_row));
881         mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row));
882 
883         mEditOnlyList.add(view.findViewById(R.id.all_day_row));
884         mEditOnlyList.add(view.findViewById(R.id.availability_row));
885         mEditOnlyList.add(view.findViewById(R.id.visibility_row));
886         mEditOnlyList.add(view.findViewById(R.id.from_row));
887         mEditOnlyList.add(view.findViewById(R.id.to_row));
888         mEditOnlyList.add(mTimezoneRow);
889         mEditOnlyList.add(mStartHomeGroup);
890         mEditOnlyList.add(mEndHomeGroup);
891 
892         mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value);
893         mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container);
894 
895         mTimezone = Utils.getTimeZone(activity, null);
896         mIsMultipane = activity.getResources().getBoolean(R.bool.tablet_config);
897         mStartTime = new Time(mTimezone);
898         mEndTime = new Time(mTimezone);
899         mEmailValidator = new Rfc822Validator(null);
900         initMultiAutoCompleteTextView((RecipientEditTextView) mAttendeesList);
901 
902         // Display loading screen
903         setModel(null);
904 
905         FragmentManager fm = activity.getFragmentManager();
906         RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm
907                 .findFragmentByTag(FRAG_TAG_RECUR_PICKER);
908         if (rpd != null) {
909             rpd.setOnRecurrenceSetListener(this);
910         }
911         TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm
912                 .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
913         if (tzpd != null) {
914             tzpd.setOnTimeZoneSetListener(this);
915         }
916         TimePickerDialog tpd = (TimePickerDialog) fm.findFragmentByTag(FRAG_TAG_TIME_PICKER);
917         if (tpd != null) {
918             View v;
919             mTimeSelectedWasStartTime = timeSelectedWasStartTime;
920             if (timeSelectedWasStartTime) {
921                 v = mStartTimeButton;
922             } else {
923                 v = mEndTimeButton;
924             }
925             tpd.setOnTimeSetListener(new TimeListener(v));
926         }
927         mDatePickerDialog = (DatePickerDialog) fm.findFragmentByTag(FRAG_TAG_DATE_PICKER);
928         if (mDatePickerDialog != null) {
929             View v;
930             mDateSelectedWasStartDate = dateSelectedWasStartDate;
931             if (dateSelectedWasStartDate) {
932                 v = mStartDateButton;
933             } else {
934                 v = mEndDateButton;
935             }
936             mDatePickerDialog.setOnDateSetListener(new DateListener(v));
937         }
938     }
939 
940 
941     /**
942      * Loads an integer array asset into a list.
943      */
loadIntegerArray(Resources r, int resNum)944     private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
945         int[] vals = r.getIntArray(resNum);
946         int size = vals.length;
947         ArrayList<Integer> list = new ArrayList<Integer>(size);
948 
949         for (int i = 0; i < size; i++) {
950             list.add(vals[i]);
951         }
952 
953         return list;
954     }
955 
956     /**
957      * Loads a String array asset into a list.
958      */
loadStringArray(Resources r, int resNum)959     private static ArrayList<String> loadStringArray(Resources r, int resNum) {
960         String[] labels = r.getStringArray(resNum);
961         ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
962         return list;
963     }
964 
prepareAvailability()965     private void prepareAvailability() {
966         Resources r = mActivity.getResources();
967 
968         mAvailabilityValues = loadIntegerArray(r, R.array.availability_values);
969         mAvailabilityLabels = loadStringArray(r, R.array.availability);
970         // Copy the unadulterated availability labels for all-day toggling.
971         mOriginalAvailabilityLabels = new ArrayList<String>();
972         mOriginalAvailabilityLabels.addAll(mAvailabilityLabels);
973 
974         if (mModel.mCalendarAllowedAvailability != null) {
975             EventViewUtils.reduceMethodList(mAvailabilityValues, mAvailabilityLabels,
976                     mModel.mCalendarAllowedAvailability);
977         }
978 
979         mAvailabilityAdapter = new ArrayAdapter<String>(mActivity,
980                 android.R.layout.simple_spinner_item, mAvailabilityLabels);
981         mAvailabilityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
982         mAvailabilitySpinner.setAdapter(mAvailabilityAdapter);
983     }
984 
985     /**
986      * Prepares the reminder UI elements.
987      * <p>
988      * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as
989      * needed for the current set of reminders and calendar properties, and then creates UI
990      * elements.
991      */
prepareReminders()992     private void prepareReminders() {
993         CalendarEventModel model = mModel;
994         Resources r = mActivity.getResources();
995 
996         // Load the labels and corresponding numeric values for the minutes and methods lists
997         // from the assets.  If we're switching calendars, we need to clear and re-populate the
998         // lists (which may have elements added and removed based on calendar properties).  This
999         // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
1000         // new event that aren't in the default set.
1001         mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
1002         mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
1003         mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
1004         mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
1005 
1006         // Remove any reminder methods that aren't allowed for this calendar.  If this is
1007         // a new event, mCalendarAllowedReminders may not be set the first time we're called.
1008         if (mModel.mCalendarAllowedReminders != null) {
1009             EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
1010                     mModel.mCalendarAllowedReminders);
1011         }
1012 
1013         int numReminders = 0;
1014         if (model.mHasAlarm) {
1015             ArrayList<ReminderEntry> reminders = model.mReminders;
1016             numReminders = reminders.size();
1017             // Insert any minute values that aren't represented in the minutes list.
1018             for (ReminderEntry re : reminders) {
1019                 if (mReminderMethodValues.contains(re.getMethod())) {
1020                     EventViewUtils.addMinutesToList(mActivity, mReminderMinuteValues,
1021                             mReminderMinuteLabels, re.getMinutes());
1022                 }
1023             }
1024 
1025             // Create a UI element for each reminder.  We display all of the reminders we get
1026             // from the provider, even if the count exceeds the calendar maximum.  (Also, for
1027             // a new event, we won't have a maxReminders value available.)
1028             mUnsupportedReminders.clear();
1029             for (ReminderEntry re : reminders) {
1030                 if (mReminderMethodValues.contains(re.getMethod())
1031                         || re.getMethod() == Reminders.METHOD_DEFAULT) {
1032                     EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1033                             mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
1034                             mReminderMethodLabels, re, Integer.MAX_VALUE, null);
1035                 } else {
1036                     // TODO figure out a way to display unsupported reminders
1037                     mUnsupportedReminders.add(re);
1038                 }
1039             }
1040         }
1041 
1042         updateRemindersVisibility(numReminders);
1043         EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
1044     }
1045 
1046     /**
1047      * Fill in the view with the contents of the given event model. This allows
1048      * an edit view to be initialized before the event has been loaded. Passing
1049      * in null for the model will display a loading screen. A non-null model
1050      * will fill in the view's fields with the data contained in the model.
1051      *
1052      * @param model The event model to pull the data from
1053      */
setModel(CalendarEventModel model)1054     public void setModel(CalendarEventModel model) {
1055         mModel = model;
1056 
1057         // Need to close the autocomplete adapter to prevent leaking cursors.
1058         if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) {
1059             ((EmailAddressAdapter)mAddressAdapter).close();
1060             mAddressAdapter = null;
1061         }
1062 
1063         if (model == null) {
1064             // Display loading screen
1065             mLoadingMessage.setVisibility(View.VISIBLE);
1066             mScrollView.setVisibility(View.GONE);
1067             return;
1068         }
1069 
1070         boolean canRespond = EditEventHelper.canRespond(model);
1071 
1072         long begin = model.mStart;
1073         long end = model.mEnd;
1074         mTimezone = model.mTimezone; // this will be UTC for all day events
1075 
1076         // Set up the starting times
1077         if (begin > 0) {
1078             mStartTime.timezone = mTimezone;
1079             mStartTime.set(begin);
1080             mStartTime.normalize(true);
1081         }
1082         if (end > 0) {
1083             mEndTime.timezone = mTimezone;
1084             mEndTime.set(end);
1085             mEndTime.normalize(true);
1086         }
1087 
1088         mRrule = model.mRrule;
1089         if (!TextUtils.isEmpty(mRrule)) {
1090             mEventRecurrence.parse(mRrule);
1091         }
1092 
1093         if (mEventRecurrence.startDate == null) {
1094             mEventRecurrence.startDate = mStartTime;
1095         }
1096 
1097         // If the user is allowed to change the attendees set up the view and
1098         // validator
1099         if (!model.mHasAttendeeData) {
1100             mAttendeesGroup.setVisibility(View.GONE);
1101         }
1102 
1103         mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
1104             @Override
1105             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
1106                 setAllDayViewsVisibility(isChecked);
1107             }
1108         });
1109 
1110         boolean prevAllDay = mAllDayCheckBox.isChecked();
1111         mAllDay = false; // default to false. Let setAllDayViewsVisibility update it as needed
1112         if (model.mAllDay) {
1113             mAllDayCheckBox.setChecked(true);
1114             // put things back in local time for all day events
1115             mTimezone = Utils.getTimeZone(mActivity, null);
1116             mStartTime.timezone = mTimezone;
1117             mEndTime.timezone = mTimezone;
1118             mEndTime.normalize(true);
1119         } else {
1120             mAllDayCheckBox.setChecked(false);
1121         }
1122         // On a rotation we need to update the views but onCheckedChanged
1123         // doesn't get called
1124         if (prevAllDay == mAllDayCheckBox.isChecked()) {
1125             setAllDayViewsVisibility(prevAllDay);
1126         }
1127 
1128         populateTimezone(mStartTime.normalize(true));
1129 
1130         SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
1131         String defaultReminderString = prefs.getString(
1132                 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
1133         mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
1134 
1135         prepareReminders();
1136         prepareAvailability();
1137 
1138         View reminderAddButton = mView.findViewById(R.id.reminder_add);
1139         View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
1140             @Override
1141             public void onClick(View v) {
1142                 addReminder();
1143             }
1144         };
1145         reminderAddButton.setOnClickListener(addReminderOnClickListener);
1146 
1147         if (!mIsMultipane) {
1148             mView.findViewById(R.id.is_all_day_label).setOnClickListener(
1149                     new View.OnClickListener() {
1150                         @Override
1151                         public void onClick(View v) {
1152                             mAllDayCheckBox.setChecked(!mAllDayCheckBox.isChecked());
1153                         }
1154                     });
1155         }
1156 
1157         if (model.mTitle != null) {
1158             mTitleTextView.setTextKeepState(model.mTitle);
1159         }
1160 
1161         if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer)
1162                 || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) {
1163             mView.findViewById(R.id.organizer_label).setVisibility(View.GONE);
1164             mView.findViewById(R.id.organizer).setVisibility(View.GONE);
1165             mOrganizerGroup.setVisibility(View.GONE);
1166         } else {
1167             ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName);
1168         }
1169 
1170         if (model.mLocation != null) {
1171             mLocationTextView.setTextKeepState(model.mLocation);
1172         }
1173 
1174         if (model.mDescription != null) {
1175             mDescriptionTextView.setTextKeepState(model.mDescription);
1176         }
1177 
1178         int availIndex = mAvailabilityValues.indexOf(model.mAvailability);
1179         if (availIndex != -1) {
1180             mAvailabilitySpinner.setSelection(availIndex);
1181         }
1182         mAccessLevelSpinner.setSelection(model.mAccessLevel);
1183 
1184         View responseLabel = mView.findViewById(R.id.response_label);
1185         if (canRespond) {
1186             int buttonToCheck = EventInfoFragment
1187                     .findButtonIdForResponse(model.mSelfAttendeeStatus);
1188             mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
1189             mResponseRadioGroup.setVisibility(View.VISIBLE);
1190             responseLabel.setVisibility(View.VISIBLE);
1191         } else {
1192             responseLabel.setVisibility(View.GONE);
1193             mResponseRadioGroup.setVisibility(View.GONE);
1194             mResponseGroup.setVisibility(View.GONE);
1195         }
1196 
1197         if (model.mUri != null) {
1198             // This is an existing event so hide the calendar spinner
1199             // since we can't change the calendar.
1200             View calendarGroup = mView.findViewById(R.id.calendar_selector_group);
1201             calendarGroup.setVisibility(View.GONE);
1202             TextView tv = (TextView) mView.findViewById(R.id.calendar_textview);
1203             tv.setText(model.mCalendarDisplayName);
1204             tv = (TextView) mView.findViewById(R.id.calendar_textview_secondary);
1205             if (tv != null) {
1206                 tv.setText(model.mOwnerAccount);
1207             }
1208         } else {
1209             View calendarGroup = mView.findViewById(R.id.calendar_group);
1210             calendarGroup.setVisibility(View.GONE);
1211         }
1212         if (model.isEventColorInitialized()) {
1213             updateHeadlineColor(model, model.getEventColor());
1214         }
1215 
1216         populateWhen();
1217         populateRepeats();
1218         updateAttendees(model.mAttendeesList);
1219 
1220         updateView();
1221         mScrollView.setVisibility(View.VISIBLE);
1222         mLoadingMessage.setVisibility(View.GONE);
1223         sendAccessibilityEvent();
1224     }
1225 
updateHeadlineColor(CalendarEventModel model, int displayColor)1226     public void updateHeadlineColor(CalendarEventModel model, int displayColor) {
1227         if (model.mUri != null) {
1228             if (mIsMultipane) {
1229                 mView.findViewById(R.id.calendar_textview_with_colorpicker)
1230                     .setBackgroundColor(displayColor);
1231             } else {
1232                 mView.findViewById(R.id.calendar_group).setBackgroundColor(displayColor);
1233             }
1234         } else {
1235             setSpinnerBackgroundColor(displayColor);
1236         }
1237     }
1238 
setSpinnerBackgroundColor(int displayColor)1239     private void setSpinnerBackgroundColor(int displayColor) {
1240         if (mIsMultipane) {
1241             mCalendarSelectorWrapper.setBackgroundColor(displayColor);
1242         } else {
1243             mCalendarSelectorGroup.setBackgroundColor(displayColor);
1244         }
1245     }
1246 
sendAccessibilityEvent()1247     private void sendAccessibilityEvent() {
1248         AccessibilityManager am =
1249             (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE);
1250         if (!am.isEnabled() || mModel == null) {
1251             return;
1252         }
1253         StringBuilder b = new StringBuilder();
1254         addFieldsRecursive(b, mView);
1255         CharSequence msg = b.toString();
1256 
1257         AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1258         event.setClassName(getClass().getName());
1259         event.setPackageName(mActivity.getPackageName());
1260         event.getText().add(msg);
1261         event.setAddedCount(msg.length());
1262 
1263         am.sendAccessibilityEvent(event);
1264     }
1265 
addFieldsRecursive(StringBuilder b, View v)1266     private void addFieldsRecursive(StringBuilder b, View v) {
1267         if (v == null || v.getVisibility() != View.VISIBLE) {
1268             return;
1269         }
1270         if (v instanceof TextView) {
1271             CharSequence tv = ((TextView) v).getText();
1272             if (!TextUtils.isEmpty(tv.toString().trim())) {
1273                 b.append(tv + PERIOD_SPACE);
1274             }
1275         } else if (v instanceof RadioGroup) {
1276             RadioGroup rg = (RadioGroup) v;
1277             int id = rg.getCheckedRadioButtonId();
1278             if (id != View.NO_ID) {
1279                 b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE);
1280             }
1281         } else if (v instanceof Spinner) {
1282             Spinner s = (Spinner) v;
1283             if (s.getSelectedItem() instanceof String) {
1284                 String str = ((String) (s.getSelectedItem())).trim();
1285                 if (!TextUtils.isEmpty(str)) {
1286                     b.append(str + PERIOD_SPACE);
1287                 }
1288             }
1289         } else if (v instanceof ViewGroup) {
1290             ViewGroup vg = (ViewGroup) v;
1291             int children = vg.getChildCount();
1292             for (int i = 0; i < children; i++) {
1293                 addFieldsRecursive(b, vg.getChildAt(i));
1294             }
1295         }
1296     }
1297 
1298     /**
1299      * Creates a single line string for the time/duration
1300      */
setWhenString()1301     protected void setWhenString() {
1302         String when;
1303         int flags = DateUtils.FORMAT_SHOW_DATE;
1304         String tz = mTimezone;
1305         if (mModel.mAllDay) {
1306             flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
1307             tz = Time.TIMEZONE_UTC;
1308         } else {
1309             flags |= DateUtils.FORMAT_SHOW_TIME;
1310             if (DateFormat.is24HourFormat(mActivity)) {
1311                 flags |= DateUtils.FORMAT_24HOUR;
1312             }
1313         }
1314         long startMillis = mStartTime.normalize(true);
1315         long endMillis = mEndTime.normalize(true);
1316         mSB.setLength(0);
1317         when = DateUtils
1318                 .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString();
1319         mWhenView.setText(when);
1320     }
1321 
1322     /**
1323      * Configures the Calendars spinner.  This is only done for new events, because only new
1324      * events allow you to select a calendar while editing an event.
1325      * <p>
1326      * We tuck a reference to a Cursor with calendar database data into the spinner, so that
1327      * we can easily extract calendar-specific values when the value changes (the spinner's
1328      * onItemSelected callback is configured).
1329      */
setCalendarsCursor(Cursor cursor, boolean userVisible, long selectedCalendarId)1330     public void setCalendarsCursor(Cursor cursor, boolean userVisible, long selectedCalendarId) {
1331         // If there are no syncable calendars, then we cannot allow
1332         // creating a new event.
1333         mCalendarsCursor = cursor;
1334         if (cursor == null || cursor.getCount() == 0) {
1335             // Cancel the "loading calendars" dialog if it exists
1336             if (mSaveAfterQueryComplete) {
1337                 mLoadingCalendarsDialog.cancel();
1338             }
1339             if (!userVisible) {
1340                 return;
1341             }
1342             // Create an error message for the user that, when clicked,
1343             // will exit this activity without saving the event.
1344             AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
1345             builder.setTitle(R.string.no_syncable_calendars).setIconAttribute(
1346                     android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found)
1347                     .setPositiveButton(R.string.add_account, this)
1348                     .setNegativeButton(android.R.string.no, this).setOnCancelListener(this);
1349             mNoCalendarsDialog = builder.show();
1350             return;
1351         }
1352 
1353         int selection;
1354         if (selectedCalendarId != -1) {
1355             selection = findSelectedCalendarPosition(cursor, selectedCalendarId);
1356         } else {
1357             selection = findDefaultCalendarPosition(cursor);
1358         }
1359 
1360         // populate the calendars spinner
1361         CalendarsAdapter adapter = new CalendarsAdapter(mActivity,
1362             R.layout.calendars_spinner_item, cursor);
1363         mCalendarsSpinner.setAdapter(adapter);
1364         mCalendarsSpinner.setOnItemSelectedListener(this);
1365         mCalendarsSpinner.setSelection(selection);
1366 
1367         if (mSaveAfterQueryComplete) {
1368             mLoadingCalendarsDialog.cancel();
1369             if (prepareForSave() && fillModelFromUI()) {
1370                 int exit = userVisible ? Utils.DONE_EXIT : 0;
1371                 mDone.setDoneCode(Utils.DONE_SAVE | exit);
1372                 mDone.run();
1373             } else if (userVisible) {
1374                 mDone.setDoneCode(Utils.DONE_EXIT);
1375                 mDone.run();
1376             } else if (Log.isLoggable(TAG, Log.DEBUG)) {
1377                 Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view");
1378             }
1379             return;
1380         }
1381     }
1382 
1383     /**
1384      * Updates the view based on {@link #mModification} and {@link #mModel}
1385      */
updateView()1386     public void updateView() {
1387         if (mModel == null) {
1388             return;
1389         }
1390         if (EditEventHelper.canModifyEvent(mModel)) {
1391             setViewStates(mModification);
1392         } else {
1393             setViewStates(Utils.MODIFY_UNINITIALIZED);
1394         }
1395     }
1396 
setViewStates(int mode)1397     private void setViewStates(int mode) {
1398         // Extra canModify check just in case
1399         if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) {
1400             setWhenString();
1401 
1402             for (View v : mViewOnlyList) {
1403                 v.setVisibility(View.VISIBLE);
1404             }
1405             for (View v : mEditOnlyList) {
1406                 v.setVisibility(View.GONE);
1407             }
1408             for (View v : mEditViewList) {
1409                 v.setEnabled(false);
1410                 v.setBackgroundDrawable(null);
1411             }
1412             mCalendarSelectorGroup.setVisibility(View.GONE);
1413             mCalendarStaticGroup.setVisibility(View.VISIBLE);
1414             mRruleButton.setEnabled(false);
1415             if (EditEventHelper.canAddReminders(mModel)) {
1416                 mRemindersGroup.setVisibility(View.VISIBLE);
1417             } else {
1418                 mRemindersGroup.setVisibility(View.GONE);
1419             }
1420             if (TextUtils.isEmpty(mLocationTextView.getText())) {
1421                 mLocationGroup.setVisibility(View.GONE);
1422             }
1423             if (TextUtils.isEmpty(mDescriptionTextView.getText())) {
1424                 mDescriptionGroup.setVisibility(View.GONE);
1425             }
1426         } else {
1427             for (View v : mViewOnlyList) {
1428                 v.setVisibility(View.GONE);
1429             }
1430             for (View v : mEditOnlyList) {
1431                 v.setVisibility(View.VISIBLE);
1432             }
1433             for (View v : mEditViewList) {
1434                 v.setEnabled(true);
1435                 if (v.getTag() != null) {
1436                     v.setBackgroundDrawable((Drawable) v.getTag());
1437                     v.setPadding(mOriginalPadding[0], mOriginalPadding[1], mOriginalPadding[2],
1438                             mOriginalPadding[3]);
1439                 }
1440             }
1441             if (mModel.mUri == null) {
1442                 mCalendarSelectorGroup.setVisibility(View.VISIBLE);
1443                 mCalendarStaticGroup.setVisibility(View.GONE);
1444             } else {
1445                 mCalendarSelectorGroup.setVisibility(View.GONE);
1446                 mCalendarStaticGroup.setVisibility(View.VISIBLE);
1447             }
1448             if (mModel.mOriginalSyncId == null) {
1449                 mRruleButton.setEnabled(true);
1450             } else {
1451                 mRruleButton.setEnabled(false);
1452                 mRruleButton.setBackgroundDrawable(null);
1453             }
1454             mRemindersGroup.setVisibility(View.VISIBLE);
1455 
1456             mLocationGroup.setVisibility(View.VISIBLE);
1457             mDescriptionGroup.setVisibility(View.VISIBLE);
1458         }
1459         setAllDayViewsVisibility(mAllDayCheckBox.isChecked());
1460     }
1461 
setModification(int modifyWhich)1462     public void setModification(int modifyWhich) {
1463         mModification = modifyWhich;
1464         updateView();
1465         updateHomeTime();
1466     }
1467 
findSelectedCalendarPosition(Cursor calendarsCursor, long calendarId)1468     private int findSelectedCalendarPosition(Cursor calendarsCursor, long calendarId) {
1469         if (calendarsCursor.getCount() <= 0) {
1470             return -1;
1471         }
1472         int calendarIdColumn = calendarsCursor.getColumnIndexOrThrow(Calendars._ID);
1473         int position = 0;
1474         calendarsCursor.moveToPosition(-1);
1475         while (calendarsCursor.moveToNext()) {
1476             if (calendarsCursor.getLong(calendarIdColumn) == calendarId) {
1477                 return position;
1478             }
1479             position++;
1480         }
1481         return 0;
1482     }
1483 
1484     // Find the calendar position in the cursor that matches calendar in
1485     // preference
findDefaultCalendarPosition(Cursor calendarsCursor)1486     private int findDefaultCalendarPosition(Cursor calendarsCursor) {
1487         if (calendarsCursor.getCount() <= 0) {
1488             return -1;
1489         }
1490 
1491         String defaultCalendar = Utils.getSharedPreference(
1492                 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, (String) null);
1493 
1494         int calendarsOwnerIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT);
1495         int accountNameIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME);
1496         int accountTypeIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE);
1497         int position = 0;
1498         calendarsCursor.moveToPosition(-1);
1499         while (calendarsCursor.moveToNext()) {
1500             String calendarOwner = calendarsCursor.getString(calendarsOwnerIndex);
1501             if (defaultCalendar == null) {
1502                 // There is no stored default upon the first time running.  Use a primary
1503                 // calendar in this case.
1504                 if (calendarOwner != null &&
1505                         calendarOwner.equals(calendarsCursor.getString(accountNameIndex)) &&
1506                         !CalendarContract.ACCOUNT_TYPE_LOCAL.equals(
1507                                 calendarsCursor.getString(accountTypeIndex))) {
1508                     return position;
1509                 }
1510             } else if (defaultCalendar.equals(calendarOwner)) {
1511                 // Found the default calendar.
1512                 return position;
1513             }
1514             position++;
1515         }
1516         return 0;
1517     }
1518 
updateAttendees(HashMap<String, Attendee> attendeesList)1519     private void updateAttendees(HashMap<String, Attendee> attendeesList) {
1520         if (attendeesList == null || attendeesList.isEmpty()) {
1521             return;
1522         }
1523         mAttendeesList.setText(null);
1524         for (Attendee attendee : attendeesList.values()) {
1525 
1526             // TODO: Please remove separator when Calendar uses the chips MR2 project
1527 
1528             // Adding a comma separator between email addresses to prevent a chips MR1.1 bug
1529             // in which email addresses are concatenated together with no separator.
1530             mAttendeesList.append(attendee.mEmail + ", ");
1531         }
1532     }
1533 
updateRemindersVisibility(int numReminders)1534     private void updateRemindersVisibility(int numReminders) {
1535         if (numReminders == 0) {
1536             mRemindersContainer.setVisibility(View.GONE);
1537         } else {
1538             mRemindersContainer.setVisibility(View.VISIBLE);
1539         }
1540     }
1541 
1542     /**
1543      * Add a new reminder when the user hits the "add reminder" button.  We use the default
1544      * reminder time and method.
1545      */
addReminder()1546     private void addReminder() {
1547         // TODO: when adding a new reminder, make it different from the
1548         // last one in the list (if any).
1549         if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
1550             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1551                     mReminderMinuteValues, mReminderMinuteLabels,
1552                     mReminderMethodValues, mReminderMethodLabels,
1553                     ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME),
1554                     mModel.mCalendarMaxReminders, null);
1555         } else {
1556             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems,
1557                     mReminderMinuteValues, mReminderMinuteLabels,
1558                     mReminderMethodValues, mReminderMethodLabels,
1559                     ReminderEntry.valueOf(mDefaultReminderMinutes),
1560                     mModel.mCalendarMaxReminders, null);
1561         }
1562         updateRemindersVisibility(mReminderItems.size());
1563         EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders);
1564     }
1565 
1566     // From com.google.android.gm.ComposeActivity
initMultiAutoCompleteTextView(RecipientEditTextView list)1567     private MultiAutoCompleteTextView initMultiAutoCompleteTextView(RecipientEditTextView list) {
1568         if (ChipsUtil.supportsChipsUi()) {
1569             mAddressAdapter = new RecipientAdapter(mActivity);
1570             list.setAdapter((BaseRecipientAdapter) mAddressAdapter);
1571             list.setOnFocusListShrinkRecipients(false);
1572         } else {
1573             mAddressAdapter = new EmailAddressAdapter(mActivity);
1574             list.setAdapter((EmailAddressAdapter)mAddressAdapter);
1575         }
1576         list.setTokenizer(new Rfc822Tokenizer());
1577         list.setValidator(mEmailValidator);
1578 
1579         // NOTE: assumes no other filters are set
1580         list.setFilters(sRecipientFilters);
1581 
1582         return list;
1583     }
1584 
1585     /**
1586      * From com.google.android.gm.ComposeActivity Implements special address
1587      * cleanup rules: The first space key entry following an "@" symbol that is
1588      * followed by any combination of letters and symbols, including one+ dots
1589      * and zero commas, should insert an extra comma (followed by the space).
1590      */
1591     private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() };
1592 
setDate(TextView view, long millis)1593     private void setDate(TextView view, long millis) {
1594         int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
1595                 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH
1596                 | DateUtils.FORMAT_ABBREV_WEEKDAY;
1597 
1598         // Unfortunately, DateUtils doesn't support a timezone other than the
1599         // default timezone provided by the system, so we have this ugly hack
1600         // here to trick it into formatting our time correctly. In order to
1601         // prevent all sorts of craziness, we synchronize on the TimeZone class
1602         // to prevent other threads from reading an incorrect timezone from
1603         // calls to TimeZone#getDefault()
1604         // TODO fix this if/when DateUtils allows for passing in a timezone
1605         String dateString;
1606         synchronized (TimeZone.class) {
1607             TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1608             dateString = DateUtils.formatDateTime(mActivity, millis, flags);
1609             // setting the default back to null restores the correct behavior
1610             TimeZone.setDefault(null);
1611         }
1612         view.setText(dateString);
1613     }
1614 
setTime(TextView view, long millis)1615     private void setTime(TextView view, long millis) {
1616         int flags = DateUtils.FORMAT_SHOW_TIME;
1617         flags |= DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
1618         if (DateFormat.is24HourFormat(mActivity)) {
1619             flags |= DateUtils.FORMAT_24HOUR;
1620         }
1621 
1622         // Unfortunately, DateUtils doesn't support a timezone other than the
1623         // default timezone provided by the system, so we have this ugly hack
1624         // here to trick it into formatting our time correctly. In order to
1625         // prevent all sorts of craziness, we synchronize on the TimeZone class
1626         // to prevent other threads from reading an incorrect timezone from
1627         // calls to TimeZone#getDefault()
1628         // TODO fix this if/when DateUtils allows for passing in a timezone
1629         String timeString;
1630         synchronized (TimeZone.class) {
1631             TimeZone.setDefault(TimeZone.getTimeZone(mTimezone));
1632             timeString = DateUtils.formatDateTime(mActivity, millis, flags);
1633             TimeZone.setDefault(null);
1634         }
1635         view.setText(timeString);
1636     }
1637 
1638     /**
1639      * @param isChecked
1640      */
setAllDayViewsVisibility(boolean isChecked)1641     protected void setAllDayViewsVisibility(boolean isChecked) {
1642         if (isChecked) {
1643             if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1644                 if (mAllDay != isChecked) {
1645                     mEndTime.monthDay--;
1646                 }
1647 
1648                 long endMillis = mEndTime.normalize(true);
1649 
1650                 // Do not allow an event to have an end time
1651                 // before the
1652                 // start time.
1653                 if (mEndTime.before(mStartTime)) {
1654                     mEndTime.set(mStartTime);
1655                     endMillis = mEndTime.normalize(true);
1656                 }
1657                 setDate(mEndDateButton, endMillis);
1658                 setTime(mEndTimeButton, endMillis);
1659             }
1660 
1661             mStartTimeButton.setVisibility(View.GONE);
1662             mEndTimeButton.setVisibility(View.GONE);
1663             mTimezoneRow.setVisibility(View.GONE);
1664         } else {
1665             if (mEndTime.hour == 0 && mEndTime.minute == 0) {
1666                 if (mAllDay != isChecked) {
1667                     mEndTime.monthDay++;
1668                 }
1669 
1670                 long endMillis = mEndTime.normalize(true);
1671                 setDate(mEndDateButton, endMillis);
1672                 setTime(mEndTimeButton, endMillis);
1673             }
1674             mStartTimeButton.setVisibility(View.VISIBLE);
1675             mEndTimeButton.setVisibility(View.VISIBLE);
1676             mTimezoneRow.setVisibility(View.VISIBLE);
1677         }
1678 
1679         // If this is a new event, and if availability has not yet been
1680         // explicitly set, toggle busy/available as the inverse of all day.
1681         if (mModel.mUri == null && !mAvailabilityExplicitlySet) {
1682             // Values are from R.arrays.availability_values.
1683             // 0 = busy
1684             // 1 = available
1685             int newAvailabilityValue = isChecked? 1 : 0;
1686             if (mAvailabilityAdapter != null && mAvailabilityValues != null
1687                     && mAvailabilityValues.contains(newAvailabilityValue)) {
1688                 // We'll need to let the spinner's listener know that we're
1689                 // explicitly toggling it.
1690                 mAllDayChangingAvailability = true;
1691 
1692                 String newAvailabilityLabel = mOriginalAvailabilityLabels.get(newAvailabilityValue);
1693                 int newAvailabilityPos = mAvailabilityAdapter.getPosition(newAvailabilityLabel);
1694                 mAvailabilitySpinner.setSelection(newAvailabilityPos);
1695             }
1696         }
1697 
1698         mAllDay = isChecked;
1699         updateHomeTime();
1700     }
1701 
setColorPickerButtonStates(int[] colorArray)1702     public void setColorPickerButtonStates(int[] colorArray) {
1703         setColorPickerButtonStates(colorArray != null && colorArray.length > 0);
1704     }
1705 
setColorPickerButtonStates(boolean showColorPalette)1706     public void setColorPickerButtonStates(boolean showColorPalette) {
1707         if (showColorPalette) {
1708             mColorPickerNewEvent.setVisibility(View.VISIBLE);
1709             mColorPickerExistingEvent.setVisibility(View.VISIBLE);
1710         } else {
1711             mColorPickerNewEvent.setVisibility(View.INVISIBLE);
1712             mColorPickerExistingEvent.setVisibility(View.GONE);
1713         }
1714     }
1715 
isColorPaletteVisible()1716     public boolean isColorPaletteVisible() {
1717         return mColorPickerNewEvent.getVisibility() == View.VISIBLE ||
1718                 mColorPickerExistingEvent.getVisibility() == View.VISIBLE;
1719     }
1720 
1721     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)1722     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1723         // This is only used for the Calendar spinner in new events, and only fires when the
1724         // calendar selection changes or on screen rotation
1725         Cursor c = (Cursor) parent.getItemAtPosition(position);
1726         if (c == null) {
1727             // TODO: can this happen? should we drop this check?
1728             Log.w(TAG, "Cursor not set on calendar item");
1729             return;
1730         }
1731 
1732         // Do nothing if the selection didn't change so that reminders will not get lost
1733         int idColumn = c.getColumnIndexOrThrow(Calendars._ID);
1734         long calendarId = c.getLong(idColumn);
1735         int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR);
1736         int color = c.getInt(colorColumn);
1737         int displayColor = Utils.getDisplayColorFromColor(color);
1738 
1739         // Prevents resetting of data (reminders, etc.) on orientation change.
1740         if (calendarId == mModel.mCalendarId && mModel.isCalendarColorInitialized() &&
1741                 displayColor == mModel.getCalendarColor()) {
1742             return;
1743         }
1744 
1745         setSpinnerBackgroundColor(displayColor);
1746 
1747         mModel.mCalendarId = calendarId;
1748         mModel.setCalendarColor(displayColor);
1749         mModel.mCalendarAccountName = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_NAME);
1750         mModel.mCalendarAccountType = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_TYPE);
1751         mModel.setEventColor(mModel.getCalendarColor());
1752 
1753         setColorPickerButtonStates(mModel.getCalendarEventColors());
1754 
1755         // Update the max/allowed reminders with the new calendar properties.
1756         int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS);
1757         mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn);
1758         int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS);
1759         mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn);
1760         int allowedAttendeeTypesColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_ATTENDEE_TYPES);
1761         mModel.mCalendarAllowedAttendeeTypes = c.getString(allowedAttendeeTypesColumn);
1762         int allowedAvailabilityColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_AVAILABILITY);
1763         mModel.mCalendarAllowedAvailability = c.getString(allowedAvailabilityColumn);
1764 
1765         // Discard the current reminders and replace them with the model's default reminder set.
1766         // We could attempt to save & restore the reminders that have been added, but that's
1767         // probably more trouble than it's worth.
1768         mModel.mReminders.clear();
1769         mModel.mReminders.addAll(mModel.mDefaultReminders);
1770         mModel.mHasAlarm = mModel.mReminders.size() != 0;
1771 
1772         // Update the UI elements.
1773         mReminderItems.clear();
1774         LinearLayout reminderLayout =
1775             (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container);
1776         reminderLayout.removeAllViews();
1777         prepareReminders();
1778         prepareAvailability();
1779     }
1780 
1781     /**
1782      * Checks if the start and end times for this event should be displayed in
1783      * the Calendar app's time zone as well and formats and displays them.
1784      */
updateHomeTime()1785     private void updateHomeTime() {
1786         String tz = Utils.getTimeZone(mActivity, null);
1787         if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone)
1788                 && mModification != EditEventHelper.MODIFY_UNINITIALIZED) {
1789             int flags = DateUtils.FORMAT_SHOW_TIME;
1790             boolean is24Format = DateFormat.is24HourFormat(mActivity);
1791             if (is24Format) {
1792                 flags |= DateUtils.FORMAT_24HOUR;
1793             }
1794             long millisStart = mStartTime.toMillis(false);
1795             long millisEnd = mEndTime.toMillis(false);
1796 
1797             boolean isDSTStart = mStartTime.isDst != 0;
1798             boolean isDSTEnd = mEndTime.isDst != 0;
1799 
1800             // First update the start date and times
1801             String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1802                     isDSTStart, TimeZone.SHORT, Locale.getDefault());
1803             StringBuilder time = new StringBuilder();
1804 
1805             mSB.setLength(0);
1806             time.append(DateUtils
1807                     .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz))
1808                     .append(" ").append(tzDisplay);
1809             mStartTimeHome.setText(time.toString());
1810 
1811             flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1812                     | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1813             mSB.setLength(0);
1814             mStartDateHome
1815                     .setText(DateUtils.formatDateRange(
1816                             mActivity, mF, millisStart, millisStart, flags, tz).toString());
1817 
1818             // Make any adjustments needed for the end times
1819             if (isDSTEnd != isDSTStart) {
1820                 tzDisplay = TimeZone.getTimeZone(tz).getDisplayName(
1821                         isDSTEnd, TimeZone.SHORT, Locale.getDefault());
1822             }
1823             flags = DateUtils.FORMAT_SHOW_TIME;
1824             if (is24Format) {
1825                 flags |= DateUtils.FORMAT_24HOUR;
1826             }
1827 
1828             // Then update the end times
1829             time.setLength(0);
1830             mSB.setLength(0);
1831             time.append(DateUtils.formatDateRange(
1832                     mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay);
1833             mEndTimeHome.setText(time.toString());
1834 
1835             flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE
1836                     | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY;
1837             mSB.setLength(0);
1838             mEndDateHome.setText(DateUtils.formatDateRange(
1839                             mActivity, mF, millisEnd, millisEnd, flags, tz).toString());
1840 
1841             mStartHomeGroup.setVisibility(View.VISIBLE);
1842             mEndHomeGroup.setVisibility(View.VISIBLE);
1843         } else {
1844             mStartHomeGroup.setVisibility(View.GONE);
1845             mEndHomeGroup.setVisibility(View.GONE);
1846         }
1847     }
1848 
1849     @Override
onNothingSelected(AdapterView<?> parent)1850     public void onNothingSelected(AdapterView<?> parent) {
1851     }
1852 }
1853