Home | History | Annotate | Download | only in event
      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 
    236         public TimeListener(View view) {
    237             mView = view;
    238         }
    239 
    240         @Override
    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 
    292         public TimeClickListener(Time time) {
    293             mTime = time;
    294         }
    295 
    296         @Override
    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 
    333         public DateListener(View view) {
    334             mView = view;
    335         }
    336 
    337         @Override
    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
    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
    415     public void onTimeZoneSet(TimeZoneInfo tzi) {
    416         setTimezone(tzi.mTzId);
    417         updateHomeTime();
    418     }
    419 
    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 
    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 
    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 
    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 
    495         public DateClickListener(Time time) {
    496             mTime = time;
    497         }
    498 
    499         @Override
    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 {
    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
    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      */
    569     public boolean prepareForSave() {
    570         if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) {
    571             return false;
    572         }
    573         return fillModelFromUI();
    574     }
    575 
    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
    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
    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
    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
    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
    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 
    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      */
    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      */
    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 
    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      */
    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      */
   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 
   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 
   1239     private void setSpinnerBackgroundColor(int displayColor) {
   1240         if (mIsMultipane) {
   1241             mCalendarSelectorWrapper.setBackgroundColor(displayColor);
   1242         } else {
   1243             mCalendarSelectorGroup.setBackgroundColor(displayColor);
   1244         }
   1245     }
   1246 
   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 
   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      */
   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      */
   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      */
   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 
   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 
   1462     public void setModification(int modifyWhich) {
   1463         mModification = modifyWhich;
   1464         updateView();
   1465         updateHomeTime();
   1466     }
   1467 
   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
   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 
   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 
   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      */
   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
   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 
   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 
   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      */
   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 
   1702     public void setColorPickerButtonStates(int[] colorArray) {
   1703         setColorPickerButtonStates(colorArray != null && colorArray.length > 0);
   1704     }
   1705 
   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 
   1716     public boolean isColorPaletteVisible() {
   1717         return mColorPickerNewEvent.getVisibility() == View.VISIBLE ||
   1718                 mColorPickerExistingEvent.getVisibility() == View.VISIBLE;
   1719     }
   1720 
   1721     @Override
   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      */
   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
   1850     public void onNothingSelected(AdapterView<?> parent) {
   1851     }
   1852 }
   1853