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