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 502 if (v == mStartDateButton) { 503 mDateSelectedWasStartDate = true; 504 } else { 505 mDateSelectedWasStartDate = false; 506 } 507 508 final DateListener listener = new DateListener(v); 509 if (mDatePickerDialog != null) { 510 mDatePickerDialog.dismiss(); 511 } 512 mDatePickerDialog = DatePickerDialog.newInstance(listener, 513 mTime.year, mTime.month, mTime.monthDay); 514 mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(mActivity)); 515 mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX); 516 mDatePickerDialog.show(mActivity.getFragmentManager(), FRAG_TAG_DATE_PICKER); 517 } 518 } 519 520 public static class CalendarsAdapter extends ResourceCursorAdapter { 521 public CalendarsAdapter(Context context, int resourceId, Cursor c) { 522 super(context, resourceId, c); 523 setDropDownViewResource(R.layout.calendars_dropdown_item); 524 } 525 526 @Override 527 public void bindView(View view, Context context, Cursor cursor) { 528 View colorBar = view.findViewById(R.id.color); 529 int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); 530 int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); 531 int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); 532 if (colorBar != null) { 533 colorBar.setBackgroundColor(Utils.getDisplayColorFromColor(cursor 534 .getInt(colorColumn))); 535 } 536 537 TextView name = (TextView) view.findViewById(R.id.calendar_name); 538 if (name != null) { 539 String displayName = cursor.getString(nameColumn); 540 name.setText(displayName); 541 542 TextView accountName = (TextView) view.findViewById(R.id.account_name); 543 if (accountName != null) { 544 accountName.setText(cursor.getString(ownerColumn)); 545 accountName.setVisibility(TextView.VISIBLE); 546 } 547 } 548 } 549 } 550 551 /** 552 * Does prep steps for saving a calendar event. 553 * 554 * This triggers a parse of the attendees list and checks if the event is 555 * ready to be saved. An event is ready to be saved so long as a model 556 * exists and has a calendar it can be associated with, either because it's 557 * an existing event or we've finished querying. 558 * 559 * @return false if there is no model or no calendar had been loaded yet, 560 * true otherwise. 561 */ 562 public boolean prepareForSave() { 563 if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { 564 return false; 565 } 566 return fillModelFromUI(); 567 } 568 569 public boolean fillModelFromReadOnlyUi() { 570 if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { 571 return false; 572 } 573 mModel.mReminders = EventViewUtils.reminderItemsToReminders( 574 mReminderItems, mReminderMinuteValues, mReminderMethodValues); 575 mModel.mReminders.addAll(mUnsupportedReminders); 576 mModel.normalizeReminders(); 577 int status = EventInfoFragment.getResponseFromButtonId( 578 mResponseRadioGroup.getCheckedRadioButtonId()); 579 if (status != Attendees.ATTENDEE_STATUS_NONE) { 580 mModel.mSelfAttendeeStatus = status; 581 } 582 return true; 583 } 584 585 // This is called if the user clicks on one of the buttons: "Save", 586 // "Discard", or "Delete". This is also called if the user clicks 587 // on the "remove reminder" button. 588 @Override 589 public void onClick(View view) { 590 if (view == mRruleButton) { 591 Bundle b = new Bundle(); 592 b.putLong(RecurrencePickerDialog.BUNDLE_START_TIME_MILLIS, 593 mStartTime.toMillis(false)); 594 b.putString(RecurrencePickerDialog.BUNDLE_TIME_ZONE, mStartTime.timezone); 595 596 // TODO may be more efficient to serialize and pass in EventRecurrence 597 b.putString(RecurrencePickerDialog.BUNDLE_RRULE, mRrule); 598 599 FragmentManager fm = mActivity.getFragmentManager(); 600 RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm 601 .findFragmentByTag(FRAG_TAG_RECUR_PICKER); 602 if (rpd != null) { 603 rpd.dismiss(); 604 } 605 rpd = new RecurrencePickerDialog(); 606 rpd.setArguments(b); 607 rpd.setOnRecurrenceSetListener(EditEventView.this); 608 rpd.show(fm, FRAG_TAG_RECUR_PICKER); 609 return; 610 } 611 612 // This must be a click on one of the "remove reminder" buttons 613 LinearLayout reminderItem = (LinearLayout) view.getParent(); 614 LinearLayout parent = (LinearLayout) reminderItem.getParent(); 615 parent.removeView(reminderItem); 616 mReminderItems.remove(reminderItem); 617 updateRemindersVisibility(mReminderItems.size()); 618 EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); 619 } 620 621 @Override 622 public void onRecurrenceSet(String rrule) { 623 Log.d(TAG, "Old rrule:" + mRrule); 624 Log.d(TAG, "New rrule:" + rrule); 625 mRrule = rrule; 626 if (mRrule != null) { 627 mEventRecurrence.parse(mRrule); 628 } 629 populateRepeats(); 630 } 631 632 // This is called if the user cancels the "No calendars" dialog. 633 // The "No calendars" dialog is shown if there are no syncable calendars. 634 @Override 635 public void onCancel(DialogInterface dialog) { 636 if (dialog == mLoadingCalendarsDialog) { 637 mLoadingCalendarsDialog = null; 638 mSaveAfterQueryComplete = false; 639 } else if (dialog == mNoCalendarsDialog) { 640 mDone.setDoneCode(Utils.DONE_REVERT); 641 mDone.run(); 642 return; 643 } 644 } 645 646 // This is called if the user clicks on a dialog button. 647 @Override 648 public void onClick(DialogInterface dialog, int which) { 649 if (dialog == mNoCalendarsDialog) { 650 mDone.setDoneCode(Utils.DONE_REVERT); 651 mDone.run(); 652 if (which == DialogInterface.BUTTON_POSITIVE) { 653 Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); 654 final String[] array = {"com.android.calendar"}; 655 nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); 656 nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); 657 mActivity.startActivity(nextIntent); 658 } 659 } 660 } 661 662 // Goes through the UI elements and updates the model as necessary 663 private boolean fillModelFromUI() { 664 if (mModel == null) { 665 return false; 666 } 667 mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems, 668 mReminderMinuteValues, mReminderMethodValues); 669 mModel.mReminders.addAll(mUnsupportedReminders); 670 mModel.normalizeReminders(); 671 mModel.mHasAlarm = mReminderItems.size() > 0; 672 mModel.mTitle = mTitleTextView.getText().toString(); 673 mModel.mAllDay = mAllDayCheckBox.isChecked(); 674 mModel.mLocation = mLocationTextView.getText().toString(); 675 mModel.mDescription = mDescriptionTextView.getText().toString(); 676 if (TextUtils.isEmpty(mModel.mLocation)) { 677 mModel.mLocation = null; 678 } 679 if (TextUtils.isEmpty(mModel.mDescription)) { 680 mModel.mDescription = null; 681 } 682 683 int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup 684 .getCheckedRadioButtonId()); 685 if (status != Attendees.ATTENDEE_STATUS_NONE) { 686 mModel.mSelfAttendeeStatus = status; 687 } 688 689 if (mAttendeesList != null) { 690 mEmailValidator.setRemoveInvalid(true); 691 mAttendeesList.performValidation(); 692 mModel.mAttendeesList.clear(); 693 mModel.addAttendees(mAttendeesList.getText().toString(), mEmailValidator); 694 mEmailValidator.setRemoveInvalid(false); 695 } 696 697 // If this was a new event we need to fill in the Calendar information 698 if (mModel.mUri == null) { 699 mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId(); 700 int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition(); 701 if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) { 702 String defaultCalendar = mCalendarsCursor.getString( 703 EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); 704 Utils.setSharedPreference( 705 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar); 706 mModel.mOwnerAccount = defaultCalendar; 707 mModel.mOrganizer = defaultCalendar; 708 mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID); 709 } 710 } 711 712 if (mModel.mAllDay) { 713 // Reset start and end time, increment the monthDay by 1, and set 714 // the timezone to UTC, as required for all-day events. 715 mTimezone = Time.TIMEZONE_UTC; 716 mStartTime.hour = 0; 717 mStartTime.minute = 0; 718 mStartTime.second = 0; 719 mStartTime.timezone = mTimezone; 720 mModel.mStart = mStartTime.normalize(true); 721 722 mEndTime.hour = 0; 723 mEndTime.minute = 0; 724 mEndTime.second = 0; 725 mEndTime.timezone = mTimezone; 726 // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time 727 // should be Y + 1 (Oct.30). 728 final long normalizedEndTimeMillis = 729 mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS; 730 if (normalizedEndTimeMillis < mModel.mStart) { 731 // mEnd should be midnight of the next day of mStart. 732 mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS; 733 } else { 734 mModel.mEnd = normalizedEndTimeMillis; 735 } 736 } else { 737 mStartTime.timezone = mTimezone; 738 mEndTime.timezone = mTimezone; 739 mModel.mStart = mStartTime.toMillis(true); 740 mModel.mEnd = mEndTime.toMillis(true); 741 } 742 mModel.mTimezone = mTimezone; 743 mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition(); 744 // TODO set correct availability value 745 mModel.mAvailability = mAvailabilityValues.get(mAvailabilitySpinner 746 .getSelectedItemPosition()); 747 748 // rrrule 749 // If we're making an exception we don't want it to be a repeating 750 // event. 751 if (mModification == EditEventHelper.MODIFY_SELECTED) { 752 mModel.mRrule = null; 753 } else { 754 mModel.mRrule = mRrule; 755 } 756 757 return true; 758 } 759 760 public EditEventView(Activity activity, View view, EditDoneRunnable done, 761 boolean timeSelectedWasStartTime, boolean dateSelectedWasStartDate) { 762 763 mActivity = activity; 764 mView = view; 765 mDone = done; 766 767 // cache top level view elements 768 mLoadingMessage = (TextView) view.findViewById(R.id.loading_message); 769 mScrollView = (ScrollView) view.findViewById(R.id.scroll_view); 770 771 // cache all the widgets 772 mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner); 773 mTitleTextView = (TextView) view.findViewById(R.id.title); 774 mLocationTextView = (AutoCompleteTextView) view.findViewById(R.id.location); 775 mDescriptionTextView = (TextView) view.findViewById(R.id.description); 776 mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label); 777 mStartDateButton = (Button) view.findViewById(R.id.start_date); 778 mEndDateButton = (Button) view.findViewById(R.id.end_date); 779 mWhenView = (TextView) mView.findViewById(R.id.when); 780 mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView); 781 mStartTimeButton = (Button) view.findViewById(R.id.start_time); 782 mEndTimeButton = (Button) view.findViewById(R.id.end_time); 783 mTimezoneButton = (Button) view.findViewById(R.id.timezone_button); 784 mTimezoneButton.setOnClickListener(new View.OnClickListener() { 785 @Override 786 public void onClick(View v) { 787 showTimezoneDialog(); 788 } 789 }); 790 mTimezoneRow = view.findViewById(R.id.timezone_button_row); 791 mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz); 792 mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz); 793 mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz); 794 mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz); 795 mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day); 796 mRruleButton = (Button) view.findViewById(R.id.rrule); 797 mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability); 798 mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility); 799 mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group); 800 mCalendarSelectorWrapper = view.findViewById(R.id.calendar_selector_wrapper); 801 mCalendarStaticGroup = view.findViewById(R.id.calendar_group); 802 mRemindersGroup = view.findViewById(R.id.reminders_row); 803 mResponseGroup = view.findViewById(R.id.response_row); 804 mOrganizerGroup = view.findViewById(R.id.organizer_row); 805 mAttendeesGroup = view.findViewById(R.id.add_attendees_row); 806 mLocationGroup = view.findViewById(R.id.where_row); 807 mDescriptionGroup = view.findViewById(R.id.description_row); 808 mStartHomeGroup = view.findViewById(R.id.from_row_home_tz); 809 mEndHomeGroup = view.findViewById(R.id.to_row_home_tz); 810 mAttendeesList = (MultiAutoCompleteTextView) view.findViewById(R.id.attendees); 811 812 mColorPickerNewEvent = view.findViewById(R.id.change_color_new_event); 813 mColorPickerExistingEvent = view.findViewById(R.id.change_color_existing_event); 814 815 mTitleTextView.setTag(mTitleTextView.getBackground()); 816 mLocationTextView.setTag(mLocationTextView.getBackground()); 817 mLocationAdapter = new EventLocationAdapter(activity); 818 mLocationTextView.setAdapter(mLocationAdapter); 819 mLocationTextView.setOnEditorActionListener(new OnEditorActionListener() { 820 @Override 821 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 822 if (actionId == EditorInfo.IME_ACTION_DONE) { 823 // Dismiss the suggestions dropdown. Return false so the other 824 // side effects still occur (soft keyboard going away, etc.). 825 mLocationTextView.dismissDropDown(); 826 } 827 return false; 828 } 829 }); 830 831 mAvailabilityExplicitlySet = false; 832 mAllDayChangingAvailability = false; 833 mAvailabilityCurrentlySelected = -1; 834 mAvailabilitySpinner.setOnItemSelectedListener( 835 new OnItemSelectedListener() { 836 @Override 837 public void onItemSelected(AdapterView<?> parent, 838 View view, int position, long id) { 839 // The spinner's onItemSelected gets called while it is being 840 // initialized to the first item, and when we explicitly set it 841 // in the allDay checkbox toggling, so we need these checks to 842 // find out when the spinner is actually being clicked. 843 844 // Set the initial selection. 845 if (mAvailabilityCurrentlySelected == -1) { 846 mAvailabilityCurrentlySelected = position; 847 } 848 849 if (mAvailabilityCurrentlySelected != position && 850 !mAllDayChangingAvailability) { 851 mAvailabilityExplicitlySet = true; 852 } else { 853 mAvailabilityCurrentlySelected = position; 854 mAllDayChangingAvailability = false; 855 } 856 } 857 @Override 858 public void onNothingSelected(AdapterView<?> arg0) { } 859 }); 860 861 862 mDescriptionTextView.setTag(mDescriptionTextView.getBackground()); 863 mAttendeesList.setTag(mAttendeesList.getBackground()); 864 mOriginalPadding[0] = mLocationTextView.getPaddingLeft(); 865 mOriginalPadding[1] = mLocationTextView.getPaddingTop(); 866 mOriginalPadding[2] = mLocationTextView.getPaddingRight(); 867 mOriginalPadding[3] = mLocationTextView.getPaddingBottom(); 868 mEditViewList.add(mTitleTextView); 869 mEditViewList.add(mLocationTextView); 870 mEditViewList.add(mDescriptionTextView); 871 mEditViewList.add(mAttendeesList); 872 873 mViewOnlyList.add(view.findViewById(R.id.when_row)); 874 mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row)); 875 876 mEditOnlyList.add(view.findViewById(R.id.all_day_row)); 877 mEditOnlyList.add(view.findViewById(R.id.availability_row)); 878 mEditOnlyList.add(view.findViewById(R.id.visibility_row)); 879 mEditOnlyList.add(view.findViewById(R.id.from_row)); 880 mEditOnlyList.add(view.findViewById(R.id.to_row)); 881 mEditOnlyList.add(mTimezoneRow); 882 mEditOnlyList.add(mStartHomeGroup); 883 mEditOnlyList.add(mEndHomeGroup); 884 885 mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value); 886 mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container); 887 888 mTimezone = Utils.getTimeZone(activity, null); 889 mIsMultipane = activity.getResources().getBoolean(R.bool.tablet_config); 890 mStartTime = new Time(mTimezone); 891 mEndTime = new Time(mTimezone); 892 mEmailValidator = new Rfc822Validator(null); 893 initMultiAutoCompleteTextView((RecipientEditTextView) mAttendeesList); 894 895 // Display loading screen 896 setModel(null); 897 898 FragmentManager fm = activity.getFragmentManager(); 899 RecurrencePickerDialog rpd = (RecurrencePickerDialog) fm 900 .findFragmentByTag(FRAG_TAG_RECUR_PICKER); 901 if (rpd != null) { 902 rpd.setOnRecurrenceSetListener(this); 903 } 904 TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm 905 .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER); 906 if (tzpd != null) { 907 tzpd.setOnTimeZoneSetListener(this); 908 } 909 TimePickerDialog tpd = (TimePickerDialog) fm.findFragmentByTag(FRAG_TAG_TIME_PICKER); 910 if (tpd != null) { 911 View v; 912 mTimeSelectedWasStartTime = timeSelectedWasStartTime; 913 if (timeSelectedWasStartTime) { 914 v = mStartTimeButton; 915 } else { 916 v = mEndTimeButton; 917 } 918 tpd.setOnTimeSetListener(new TimeListener(v)); 919 } 920 mDatePickerDialog = (DatePickerDialog) fm.findFragmentByTag(FRAG_TAG_DATE_PICKER); 921 if (mDatePickerDialog != null) { 922 View v; 923 mDateSelectedWasStartDate = dateSelectedWasStartDate; 924 if (dateSelectedWasStartDate) { 925 v = mStartDateButton; 926 } else { 927 v = mEndDateButton; 928 } 929 mDatePickerDialog.setOnDateSetListener(new DateListener(v)); 930 } 931 } 932 933 934 /** 935 * Loads an integer array asset into a list. 936 */ 937 private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) { 938 int[] vals = r.getIntArray(resNum); 939 int size = vals.length; 940 ArrayList<Integer> list = new ArrayList<Integer>(size); 941 942 for (int i = 0; i < size; i++) { 943 list.add(vals[i]); 944 } 945 946 return list; 947 } 948 949 /** 950 * Loads a String array asset into a list. 951 */ 952 private static ArrayList<String> loadStringArray(Resources r, int resNum) { 953 String[] labels = r.getStringArray(resNum); 954 ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels)); 955 return list; 956 } 957 958 private void prepareAvailability() { 959 Resources r = mActivity.getResources(); 960 961 mAvailabilityValues = loadIntegerArray(r, R.array.availability_values); 962 mAvailabilityLabels = loadStringArray(r, R.array.availability); 963 // Copy the unadulterated availability labels for all-day toggling. 964 mOriginalAvailabilityLabels = new ArrayList<String>(); 965 mOriginalAvailabilityLabels.addAll(mAvailabilityLabels); 966 967 if (mModel.mCalendarAllowedAvailability != null) { 968 EventViewUtils.reduceMethodList(mAvailabilityValues, mAvailabilityLabels, 969 mModel.mCalendarAllowedAvailability); 970 } 971 972 mAvailabilityAdapter = new ArrayAdapter<String>(mActivity, 973 android.R.layout.simple_spinner_item, mAvailabilityLabels); 974 mAvailabilityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 975 mAvailabilitySpinner.setAdapter(mAvailabilityAdapter); 976 } 977 978 /** 979 * Prepares the reminder UI elements. 980 * <p> 981 * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as 982 * needed for the current set of reminders and calendar properties, and then creates UI 983 * elements. 984 */ 985 private void prepareReminders() { 986 CalendarEventModel model = mModel; 987 Resources r = mActivity.getResources(); 988 989 // Load the labels and corresponding numeric values for the minutes and methods lists 990 // from the assets. If we're switching calendars, we need to clear and re-populate the 991 // lists (which may have elements added and removed based on calendar properties). This 992 // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a 993 // new event that aren't in the default set. 994 mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values); 995 mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels); 996 mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values); 997 mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels); 998 999 // Remove any reminder methods that aren't allowed for this calendar. If this is 1000 // a new event, mCalendarAllowedReminders may not be set the first time we're called. 1001 if (mModel.mCalendarAllowedReminders != null) { 1002 EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels, 1003 mModel.mCalendarAllowedReminders); 1004 } 1005 1006 int numReminders = 0; 1007 if (model.mHasAlarm) { 1008 ArrayList<ReminderEntry> reminders = model.mReminders; 1009 numReminders = reminders.size(); 1010 // Insert any minute values that aren't represented in the minutes list. 1011 for (ReminderEntry re : reminders) { 1012 if (mReminderMethodValues.contains(re.getMethod())) { 1013 EventViewUtils.addMinutesToList(mActivity, mReminderMinuteValues, 1014 mReminderMinuteLabels, re.getMinutes()); 1015 } 1016 } 1017 1018 // Create a UI element for each reminder. We display all of the reminders we get 1019 // from the provider, even if the count exceeds the calendar maximum. (Also, for 1020 // a new event, we won't have a maxReminders value available.) 1021 mUnsupportedReminders.clear(); 1022 for (ReminderEntry re : reminders) { 1023 if (mReminderMethodValues.contains(re.getMethod()) 1024 || re.getMethod() == Reminders.METHOD_DEFAULT) { 1025 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1026 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, 1027 mReminderMethodLabels, re, Integer.MAX_VALUE, null); 1028 } else { 1029 // TODO figure out a way to display unsupported reminders 1030 mUnsupportedReminders.add(re); 1031 } 1032 } 1033 } 1034 1035 updateRemindersVisibility(numReminders); 1036 EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); 1037 } 1038 1039 /** 1040 * Fill in the view with the contents of the given event model. This allows 1041 * an edit view to be initialized before the event has been loaded. Passing 1042 * in null for the model will display a loading screen. A non-null model 1043 * will fill in the view's fields with the data contained in the model. 1044 * 1045 * @param model The event model to pull the data from 1046 */ 1047 public void setModel(CalendarEventModel model) { 1048 mModel = model; 1049 1050 // Need to close the autocomplete adapter to prevent leaking cursors. 1051 if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) { 1052 ((EmailAddressAdapter)mAddressAdapter).close(); 1053 mAddressAdapter = null; 1054 } 1055 1056 if (model == null) { 1057 // Display loading screen 1058 mLoadingMessage.setVisibility(View.VISIBLE); 1059 mScrollView.setVisibility(View.GONE); 1060 return; 1061 } 1062 1063 boolean canRespond = EditEventHelper.canRespond(model); 1064 1065 long begin = model.mStart; 1066 long end = model.mEnd; 1067 mTimezone = model.mTimezone; // this will be UTC for all day events 1068 1069 // Set up the starting times 1070 if (begin > 0) { 1071 mStartTime.timezone = mTimezone; 1072 mStartTime.set(begin); 1073 mStartTime.normalize(true); 1074 } 1075 if (end > 0) { 1076 mEndTime.timezone = mTimezone; 1077 mEndTime.set(end); 1078 mEndTime.normalize(true); 1079 } 1080 1081 mRrule = model.mRrule; 1082 if (!TextUtils.isEmpty(mRrule)) { 1083 mEventRecurrence.parse(mRrule); 1084 } 1085 1086 if (mEventRecurrence.startDate == null) { 1087 mEventRecurrence.startDate = mStartTime; 1088 } 1089 1090 // If the user is allowed to change the attendees set up the view and 1091 // validator 1092 if (!model.mHasAttendeeData) { 1093 mAttendeesGroup.setVisibility(View.GONE); 1094 } 1095 1096 mAllDayCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 1097 @Override 1098 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 1099 setAllDayViewsVisibility(isChecked); 1100 } 1101 }); 1102 1103 boolean prevAllDay = mAllDayCheckBox.isChecked(); 1104 mAllDay = false; // default to false. Let setAllDayViewsVisibility update it as needed 1105 if (model.mAllDay) { 1106 mAllDayCheckBox.setChecked(true); 1107 // put things back in local time for all day events 1108 mTimezone = Utils.getTimeZone(mActivity, null); 1109 mStartTime.timezone = mTimezone; 1110 mEndTime.timezone = mTimezone; 1111 mEndTime.normalize(true); 1112 } else { 1113 mAllDayCheckBox.setChecked(false); 1114 } 1115 // On a rotation we need to update the views but onCheckedChanged 1116 // doesn't get called 1117 if (prevAllDay == mAllDayCheckBox.isChecked()) { 1118 setAllDayViewsVisibility(prevAllDay); 1119 } 1120 1121 populateTimezone(mStartTime.normalize(true)); 1122 1123 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); 1124 String defaultReminderString = prefs.getString( 1125 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); 1126 mDefaultReminderMinutes = Integer.parseInt(defaultReminderString); 1127 1128 prepareReminders(); 1129 prepareAvailability(); 1130 1131 View reminderAddButton = mView.findViewById(R.id.reminder_add); 1132 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { 1133 @Override 1134 public void onClick(View v) { 1135 addReminder(); 1136 } 1137 }; 1138 reminderAddButton.setOnClickListener(addReminderOnClickListener); 1139 1140 if (!mIsMultipane) { 1141 mView.findViewById(R.id.is_all_day_label).setOnClickListener( 1142 new View.OnClickListener() { 1143 @Override 1144 public void onClick(View v) { 1145 mAllDayCheckBox.setChecked(!mAllDayCheckBox.isChecked()); 1146 } 1147 }); 1148 } 1149 1150 if (model.mTitle != null) { 1151 mTitleTextView.setTextKeepState(model.mTitle); 1152 } 1153 1154 if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer) 1155 || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) { 1156 mView.findViewById(R.id.organizer_label).setVisibility(View.GONE); 1157 mView.findViewById(R.id.organizer).setVisibility(View.GONE); 1158 mOrganizerGroup.setVisibility(View.GONE); 1159 } else { 1160 ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName); 1161 } 1162 1163 if (model.mLocation != null) { 1164 mLocationTextView.setTextKeepState(model.mLocation); 1165 } 1166 1167 if (model.mDescription != null) { 1168 mDescriptionTextView.setTextKeepState(model.mDescription); 1169 } 1170 1171 int availIndex = mAvailabilityValues.indexOf(model.mAvailability); 1172 if (availIndex != -1) { 1173 mAvailabilitySpinner.setSelection(availIndex); 1174 } 1175 mAccessLevelSpinner.setSelection(model.mAccessLevel); 1176 1177 View responseLabel = mView.findViewById(R.id.response_label); 1178 if (canRespond) { 1179 int buttonToCheck = EventInfoFragment 1180 .findButtonIdForResponse(model.mSelfAttendeeStatus); 1181 mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons 1182 mResponseRadioGroup.setVisibility(View.VISIBLE); 1183 responseLabel.setVisibility(View.VISIBLE); 1184 } else { 1185 responseLabel.setVisibility(View.GONE); 1186 mResponseRadioGroup.setVisibility(View.GONE); 1187 mResponseGroup.setVisibility(View.GONE); 1188 } 1189 1190 if (model.mUri != null) { 1191 // This is an existing event so hide the calendar spinner 1192 // since we can't change the calendar. 1193 View calendarGroup = mView.findViewById(R.id.calendar_selector_group); 1194 calendarGroup.setVisibility(View.GONE); 1195 TextView tv = (TextView) mView.findViewById(R.id.calendar_textview); 1196 tv.setText(model.mCalendarDisplayName); 1197 tv = (TextView) mView.findViewById(R.id.calendar_textview_secondary); 1198 if (tv != null) { 1199 tv.setText(model.mOwnerAccount); 1200 } 1201 } else { 1202 View calendarGroup = mView.findViewById(R.id.calendar_group); 1203 calendarGroup.setVisibility(View.GONE); 1204 } 1205 if (model.isEventColorInitialized()) { 1206 updateHeadlineColor(model, model.getEventColor()); 1207 } 1208 1209 populateWhen(); 1210 populateRepeats(); 1211 updateAttendees(model.mAttendeesList); 1212 1213 updateView(); 1214 mScrollView.setVisibility(View.VISIBLE); 1215 mLoadingMessage.setVisibility(View.GONE); 1216 sendAccessibilityEvent(); 1217 } 1218 1219 public void updateHeadlineColor(CalendarEventModel model, int displayColor) { 1220 if (model.mUri != null) { 1221 if (mIsMultipane) { 1222 mView.findViewById(R.id.calendar_textview_with_colorpicker) 1223 .setBackgroundColor(displayColor); 1224 } else { 1225 mView.findViewById(R.id.calendar_group).setBackgroundColor(displayColor); 1226 } 1227 } else { 1228 setSpinnerBackgroundColor(displayColor); 1229 } 1230 } 1231 1232 private void setSpinnerBackgroundColor(int displayColor) { 1233 if (mIsMultipane) { 1234 mCalendarSelectorWrapper.setBackgroundColor(displayColor); 1235 } else { 1236 mCalendarSelectorGroup.setBackgroundColor(displayColor); 1237 } 1238 } 1239 1240 private void sendAccessibilityEvent() { 1241 AccessibilityManager am = 1242 (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE); 1243 if (!am.isEnabled() || mModel == null) { 1244 return; 1245 } 1246 StringBuilder b = new StringBuilder(); 1247 addFieldsRecursive(b, mView); 1248 CharSequence msg = b.toString(); 1249 1250 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1251 event.setClassName(getClass().getName()); 1252 event.setPackageName(mActivity.getPackageName()); 1253 event.getText().add(msg); 1254 event.setAddedCount(msg.length()); 1255 1256 am.sendAccessibilityEvent(event); 1257 } 1258 1259 private void addFieldsRecursive(StringBuilder b, View v) { 1260 if (v == null || v.getVisibility() != View.VISIBLE) { 1261 return; 1262 } 1263 if (v instanceof TextView) { 1264 CharSequence tv = ((TextView) v).getText(); 1265 if (!TextUtils.isEmpty(tv.toString().trim())) { 1266 b.append(tv + PERIOD_SPACE); 1267 } 1268 } else if (v instanceof RadioGroup) { 1269 RadioGroup rg = (RadioGroup) v; 1270 int id = rg.getCheckedRadioButtonId(); 1271 if (id != View.NO_ID) { 1272 b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE); 1273 } 1274 } else if (v instanceof Spinner) { 1275 Spinner s = (Spinner) v; 1276 if (s.getSelectedItem() instanceof String) { 1277 String str = ((String) (s.getSelectedItem())).trim(); 1278 if (!TextUtils.isEmpty(str)) { 1279 b.append(str + PERIOD_SPACE); 1280 } 1281 } 1282 } else if (v instanceof ViewGroup) { 1283 ViewGroup vg = (ViewGroup) v; 1284 int children = vg.getChildCount(); 1285 for (int i = 0; i < children; i++) { 1286 addFieldsRecursive(b, vg.getChildAt(i)); 1287 } 1288 } 1289 } 1290 1291 /** 1292 * Creates a single line string for the time/duration 1293 */ 1294 protected void setWhenString() { 1295 String when; 1296 int flags = DateUtils.FORMAT_SHOW_DATE; 1297 String tz = mTimezone; 1298 if (mModel.mAllDay) { 1299 flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 1300 tz = Time.TIMEZONE_UTC; 1301 } else { 1302 flags |= DateUtils.FORMAT_SHOW_TIME; 1303 if (DateFormat.is24HourFormat(mActivity)) { 1304 flags |= DateUtils.FORMAT_24HOUR; 1305 } 1306 } 1307 long startMillis = mStartTime.normalize(true); 1308 long endMillis = mEndTime.normalize(true); 1309 mSB.setLength(0); 1310 when = DateUtils 1311 .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString(); 1312 mWhenView.setText(when); 1313 } 1314 1315 /** 1316 * Configures the Calendars spinner. This is only done for new events, because only new 1317 * events allow you to select a calendar while editing an event. 1318 * <p> 1319 * We tuck a reference to a Cursor with calendar database data into the spinner, so that 1320 * we can easily extract calendar-specific values when the value changes (the spinner's 1321 * onItemSelected callback is configured). 1322 */ 1323 public void setCalendarsCursor(Cursor cursor, boolean userVisible, long selectedCalendarId) { 1324 // If there are no syncable calendars, then we cannot allow 1325 // creating a new event. 1326 mCalendarsCursor = cursor; 1327 if (cursor == null || cursor.getCount() == 0) { 1328 // Cancel the "loading calendars" dialog if it exists 1329 if (mSaveAfterQueryComplete) { 1330 mLoadingCalendarsDialog.cancel(); 1331 } 1332 if (!userVisible) { 1333 return; 1334 } 1335 // Create an error message for the user that, when clicked, 1336 // will exit this activity without saving the event. 1337 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 1338 builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( 1339 android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) 1340 .setPositiveButton(R.string.add_account, this) 1341 .setNegativeButton(android.R.string.no, this).setOnCancelListener(this); 1342 mNoCalendarsDialog = builder.show(); 1343 return; 1344 } 1345 1346 int selection; 1347 if (selectedCalendarId != -1) { 1348 selection = findSelectedCalendarPosition(cursor, selectedCalendarId); 1349 } else { 1350 selection = findDefaultCalendarPosition(cursor); 1351 } 1352 1353 // populate the calendars spinner 1354 CalendarsAdapter adapter = new CalendarsAdapter(mActivity, 1355 R.layout.calendars_spinner_item, cursor); 1356 mCalendarsSpinner.setAdapter(adapter); 1357 mCalendarsSpinner.setOnItemSelectedListener(this); 1358 mCalendarsSpinner.setSelection(selection); 1359 1360 if (mSaveAfterQueryComplete) { 1361 mLoadingCalendarsDialog.cancel(); 1362 if (prepareForSave() && fillModelFromUI()) { 1363 int exit = userVisible ? Utils.DONE_EXIT : 0; 1364 mDone.setDoneCode(Utils.DONE_SAVE | exit); 1365 mDone.run(); 1366 } else if (userVisible) { 1367 mDone.setDoneCode(Utils.DONE_EXIT); 1368 mDone.run(); 1369 } else if (Log.isLoggable(TAG, Log.DEBUG)) { 1370 Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view"); 1371 } 1372 return; 1373 } 1374 } 1375 1376 /** 1377 * Updates the view based on {@link #mModification} and {@link #mModel} 1378 */ 1379 public void updateView() { 1380 if (mModel == null) { 1381 return; 1382 } 1383 if (EditEventHelper.canModifyEvent(mModel)) { 1384 setViewStates(mModification); 1385 } else { 1386 setViewStates(Utils.MODIFY_UNINITIALIZED); 1387 } 1388 } 1389 1390 private void setViewStates(int mode) { 1391 // Extra canModify check just in case 1392 if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) { 1393 setWhenString(); 1394 1395 for (View v : mViewOnlyList) { 1396 v.setVisibility(View.VISIBLE); 1397 } 1398 for (View v : mEditOnlyList) { 1399 v.setVisibility(View.GONE); 1400 } 1401 for (View v : mEditViewList) { 1402 v.setEnabled(false); 1403 v.setBackgroundDrawable(null); 1404 } 1405 mCalendarSelectorGroup.setVisibility(View.GONE); 1406 mCalendarStaticGroup.setVisibility(View.VISIBLE); 1407 mRruleButton.setEnabled(false); 1408 if (EditEventHelper.canAddReminders(mModel)) { 1409 mRemindersGroup.setVisibility(View.VISIBLE); 1410 } else { 1411 mRemindersGroup.setVisibility(View.GONE); 1412 } 1413 if (TextUtils.isEmpty(mLocationTextView.getText())) { 1414 mLocationGroup.setVisibility(View.GONE); 1415 } 1416 if (TextUtils.isEmpty(mDescriptionTextView.getText())) { 1417 mDescriptionGroup.setVisibility(View.GONE); 1418 } 1419 } else { 1420 for (View v : mViewOnlyList) { 1421 v.setVisibility(View.GONE); 1422 } 1423 for (View v : mEditOnlyList) { 1424 v.setVisibility(View.VISIBLE); 1425 } 1426 for (View v : mEditViewList) { 1427 v.setEnabled(true); 1428 if (v.getTag() != null) { 1429 v.setBackgroundDrawable((Drawable) v.getTag()); 1430 v.setPadding(mOriginalPadding[0], mOriginalPadding[1], mOriginalPadding[2], 1431 mOriginalPadding[3]); 1432 } 1433 } 1434 if (mModel.mUri == null) { 1435 mCalendarSelectorGroup.setVisibility(View.VISIBLE); 1436 mCalendarStaticGroup.setVisibility(View.GONE); 1437 } else { 1438 mCalendarSelectorGroup.setVisibility(View.GONE); 1439 mCalendarStaticGroup.setVisibility(View.VISIBLE); 1440 } 1441 if (mModel.mOriginalSyncId == null) { 1442 mRruleButton.setEnabled(true); 1443 } else { 1444 mRruleButton.setEnabled(false); 1445 mRruleButton.setBackgroundDrawable(null); 1446 } 1447 mRemindersGroup.setVisibility(View.VISIBLE); 1448 1449 mLocationGroup.setVisibility(View.VISIBLE); 1450 mDescriptionGroup.setVisibility(View.VISIBLE); 1451 } 1452 setAllDayViewsVisibility(mAllDayCheckBox.isChecked()); 1453 } 1454 1455 public void setModification(int modifyWhich) { 1456 mModification = modifyWhich; 1457 updateView(); 1458 updateHomeTime(); 1459 } 1460 1461 private int findSelectedCalendarPosition(Cursor calendarsCursor, long calendarId) { 1462 if (calendarsCursor.getCount() <= 0) { 1463 return -1; 1464 } 1465 int calendarIdColumn = calendarsCursor.getColumnIndexOrThrow(Calendars._ID); 1466 int position = 0; 1467 calendarsCursor.moveToPosition(-1); 1468 while (calendarsCursor.moveToNext()) { 1469 if (calendarsCursor.getLong(calendarIdColumn) == calendarId) { 1470 return position; 1471 } 1472 position++; 1473 } 1474 return 0; 1475 } 1476 1477 // Find the calendar position in the cursor that matches calendar in 1478 // preference 1479 private int findDefaultCalendarPosition(Cursor calendarsCursor) { 1480 if (calendarsCursor.getCount() <= 0) { 1481 return -1; 1482 } 1483 1484 String defaultCalendar = Utils.getSharedPreference( 1485 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, (String) null); 1486 1487 int calendarsOwnerIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); 1488 int accountNameIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_NAME); 1489 int accountTypeIndex = calendarsCursor.getColumnIndexOrThrow(Calendars.ACCOUNT_TYPE); 1490 int position = 0; 1491 calendarsCursor.moveToPosition(-1); 1492 while (calendarsCursor.moveToNext()) { 1493 String calendarOwner = calendarsCursor.getString(calendarsOwnerIndex); 1494 if (defaultCalendar == null) { 1495 // There is no stored default upon the first time running. Use a primary 1496 // calendar in this case. 1497 if (calendarOwner != null && 1498 calendarOwner.equals(calendarsCursor.getString(accountNameIndex)) && 1499 !CalendarContract.ACCOUNT_TYPE_LOCAL.equals( 1500 calendarsCursor.getString(accountTypeIndex))) { 1501 return position; 1502 } 1503 } else if (defaultCalendar.equals(calendarOwner)) { 1504 // Found the default calendar. 1505 return position; 1506 } 1507 position++; 1508 } 1509 return 0; 1510 } 1511 1512 private void updateAttendees(HashMap<String, Attendee> attendeesList) { 1513 if (attendeesList == null || attendeesList.isEmpty()) { 1514 return; 1515 } 1516 mAttendeesList.setText(null); 1517 for (Attendee attendee : attendeesList.values()) { 1518 1519 // TODO: Please remove separator when Calendar uses the chips MR2 project 1520 1521 // Adding a comma separator between email addresses to prevent a chips MR1.1 bug 1522 // in which email addresses are concatenated together with no separator. 1523 mAttendeesList.append(attendee.mEmail + ", "); 1524 } 1525 } 1526 1527 private void updateRemindersVisibility(int numReminders) { 1528 if (numReminders == 0) { 1529 mRemindersContainer.setVisibility(View.GONE); 1530 } else { 1531 mRemindersContainer.setVisibility(View.VISIBLE); 1532 } 1533 } 1534 1535 /** 1536 * Add a new reminder when the user hits the "add reminder" button. We use the default 1537 * reminder time and method. 1538 */ 1539 private void addReminder() { 1540 // TODO: when adding a new reminder, make it different from the 1541 // last one in the list (if any). 1542 if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) { 1543 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1544 mReminderMinuteValues, mReminderMinuteLabels, 1545 mReminderMethodValues, mReminderMethodLabels, 1546 ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), 1547 mModel.mCalendarMaxReminders, null); 1548 } else { 1549 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1550 mReminderMinuteValues, mReminderMinuteLabels, 1551 mReminderMethodValues, mReminderMethodLabels, 1552 ReminderEntry.valueOf(mDefaultReminderMinutes), 1553 mModel.mCalendarMaxReminders, null); 1554 } 1555 updateRemindersVisibility(mReminderItems.size()); 1556 EventViewUtils.updateAddReminderButton(mView, mReminderItems, mModel.mCalendarMaxReminders); 1557 } 1558 1559 // From com.google.android.gm.ComposeActivity 1560 private MultiAutoCompleteTextView initMultiAutoCompleteTextView(RecipientEditTextView list) { 1561 if (ChipsUtil.supportsChipsUi()) { 1562 mAddressAdapter = new RecipientAdapter(mActivity); 1563 list.setAdapter((BaseRecipientAdapter) mAddressAdapter); 1564 list.setOnFocusListShrinkRecipients(false); 1565 } else { 1566 mAddressAdapter = new EmailAddressAdapter(mActivity); 1567 list.setAdapter((EmailAddressAdapter)mAddressAdapter); 1568 } 1569 list.setTokenizer(new Rfc822Tokenizer()); 1570 list.setValidator(mEmailValidator); 1571 1572 // NOTE: assumes no other filters are set 1573 list.setFilters(sRecipientFilters); 1574 1575 return list; 1576 } 1577 1578 /** 1579 * From com.google.android.gm.ComposeActivity Implements special address 1580 * cleanup rules: The first space key entry following an "@" symbol that is 1581 * followed by any combination of letters and symbols, including one+ dots 1582 * and zero commas, should insert an extra comma (followed by the space). 1583 */ 1584 private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() }; 1585 1586 private void setDate(TextView view, long millis) { 1587 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR 1588 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH 1589 | DateUtils.FORMAT_ABBREV_WEEKDAY; 1590 1591 // Unfortunately, DateUtils doesn't support a timezone other than the 1592 // default timezone provided by the system, so we have this ugly hack 1593 // here to trick it into formatting our time correctly. In order to 1594 // prevent all sorts of craziness, we synchronize on the TimeZone class 1595 // to prevent other threads from reading an incorrect timezone from 1596 // calls to TimeZone#getDefault() 1597 // TODO fix this if/when DateUtils allows for passing in a timezone 1598 String dateString; 1599 synchronized (TimeZone.class) { 1600 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1601 dateString = DateUtils.formatDateTime(mActivity, millis, flags); 1602 // setting the default back to null restores the correct behavior 1603 TimeZone.setDefault(null); 1604 } 1605 view.setText(dateString); 1606 } 1607 1608 private void setTime(TextView view, long millis) { 1609 int flags = DateUtils.FORMAT_SHOW_TIME; 1610 if (DateFormat.is24HourFormat(mActivity)) { 1611 flags |= DateUtils.FORMAT_24HOUR; 1612 } 1613 1614 // Unfortunately, DateUtils doesn't support a timezone other than the 1615 // default timezone provided by the system, so we have this ugly hack 1616 // here to trick it into formatting our time correctly. In order to 1617 // prevent all sorts of craziness, we synchronize on the TimeZone class 1618 // to prevent other threads from reading an incorrect timezone from 1619 // calls to TimeZone#getDefault() 1620 // TODO fix this if/when DateUtils allows for passing in a timezone 1621 String timeString; 1622 synchronized (TimeZone.class) { 1623 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1624 timeString = DateUtils.formatDateTime(mActivity, millis, flags); 1625 TimeZone.setDefault(null); 1626 } 1627 view.setText(timeString); 1628 } 1629 1630 /** 1631 * @param isChecked 1632 */ 1633 protected void setAllDayViewsVisibility(boolean isChecked) { 1634 if (isChecked) { 1635 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 1636 if (mAllDay != isChecked) { 1637 mEndTime.monthDay--; 1638 } 1639 1640 long endMillis = mEndTime.normalize(true); 1641 1642 // Do not allow an event to have an end time 1643 // before the 1644 // start time. 1645 if (mEndTime.before(mStartTime)) { 1646 mEndTime.set(mStartTime); 1647 endMillis = mEndTime.normalize(true); 1648 } 1649 setDate(mEndDateButton, endMillis); 1650 setTime(mEndTimeButton, endMillis); 1651 } 1652 1653 mStartTimeButton.setVisibility(View.GONE); 1654 mEndTimeButton.setVisibility(View.GONE); 1655 mTimezoneRow.setVisibility(View.GONE); 1656 } else { 1657 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 1658 if (mAllDay != isChecked) { 1659 mEndTime.monthDay++; 1660 } 1661 1662 long endMillis = mEndTime.normalize(true); 1663 setDate(mEndDateButton, endMillis); 1664 setTime(mEndTimeButton, endMillis); 1665 } 1666 mStartTimeButton.setVisibility(View.VISIBLE); 1667 mEndTimeButton.setVisibility(View.VISIBLE); 1668 mTimezoneRow.setVisibility(View.VISIBLE); 1669 } 1670 1671 // If this is a new event, and if availability has not yet been 1672 // explicitly set, toggle busy/available as the inverse of all day. 1673 if (mModel.mUri == null && !mAvailabilityExplicitlySet) { 1674 // Values are from R.arrays.availability_values. 1675 // 0 = busy 1676 // 1 = available 1677 int newAvailabilityValue = isChecked? 1 : 0; 1678 if (mAvailabilityAdapter != null && mAvailabilityValues != null 1679 && mAvailabilityValues.contains(newAvailabilityValue)) { 1680 // We'll need to let the spinner's listener know that we're 1681 // explicitly toggling it. 1682 mAllDayChangingAvailability = true; 1683 1684 String newAvailabilityLabel = mOriginalAvailabilityLabels.get(newAvailabilityValue); 1685 int newAvailabilityPos = mAvailabilityAdapter.getPosition(newAvailabilityLabel); 1686 mAvailabilitySpinner.setSelection(newAvailabilityPos); 1687 } 1688 } 1689 1690 mAllDay = isChecked; 1691 updateHomeTime(); 1692 } 1693 1694 public void setColorPickerButtonStates(int[] colorArray) { 1695 setColorPickerButtonStates(colorArray != null && colorArray.length > 0); 1696 } 1697 1698 public void setColorPickerButtonStates(boolean showColorPalette) { 1699 if (showColorPalette) { 1700 mColorPickerNewEvent.setVisibility(View.VISIBLE); 1701 mColorPickerExistingEvent.setVisibility(View.VISIBLE); 1702 } else { 1703 mColorPickerNewEvent.setVisibility(View.INVISIBLE); 1704 mColorPickerExistingEvent.setVisibility(View.GONE); 1705 } 1706 } 1707 1708 public boolean isColorPaletteVisible() { 1709 return mColorPickerNewEvent.getVisibility() == View.VISIBLE || 1710 mColorPickerExistingEvent.getVisibility() == View.VISIBLE; 1711 } 1712 1713 @Override 1714 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 1715 // This is only used for the Calendar spinner in new events, and only fires when the 1716 // calendar selection changes or on screen rotation 1717 Cursor c = (Cursor) parent.getItemAtPosition(position); 1718 if (c == null) { 1719 // TODO: can this happen? should we drop this check? 1720 Log.w(TAG, "Cursor not set on calendar item"); 1721 return; 1722 } 1723 1724 // Do nothing if the selection didn't change so that reminders will not get lost 1725 int idColumn = c.getColumnIndexOrThrow(Calendars._ID); 1726 long calendarId = c.getLong(idColumn); 1727 int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); 1728 int color = c.getInt(colorColumn); 1729 int displayColor = Utils.getDisplayColorFromColor(color); 1730 1731 // Prevents resetting of data (reminders, etc.) on orientation change. 1732 if (calendarId == mModel.mCalendarId && mModel.isCalendarColorInitialized() && 1733 displayColor == mModel.getCalendarColor()) { 1734 return; 1735 } 1736 1737 setSpinnerBackgroundColor(displayColor); 1738 1739 mModel.mCalendarId = calendarId; 1740 mModel.setCalendarColor(displayColor); 1741 mModel.mCalendarAccountName = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_NAME); 1742 mModel.mCalendarAccountType = c.getString(EditEventHelper.CALENDARS_INDEX_ACCOUNT_TYPE); 1743 mModel.setEventColor(mModel.getCalendarColor()); 1744 1745 setColorPickerButtonStates(mModel.getCalendarEventColors()); 1746 1747 // Update the max/allowed reminders with the new calendar properties. 1748 int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS); 1749 mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn); 1750 int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS); 1751 mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn); 1752 int allowedAttendeeTypesColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_ATTENDEE_TYPES); 1753 mModel.mCalendarAllowedAttendeeTypes = c.getString(allowedAttendeeTypesColumn); 1754 int allowedAvailabilityColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_AVAILABILITY); 1755 mModel.mCalendarAllowedAvailability = c.getString(allowedAvailabilityColumn); 1756 1757 // Discard the current reminders and replace them with the model's default reminder set. 1758 // We could attempt to save & restore the reminders that have been added, but that's 1759 // probably more trouble than it's worth. 1760 mModel.mReminders.clear(); 1761 mModel.mReminders.addAll(mModel.mDefaultReminders); 1762 mModel.mHasAlarm = mModel.mReminders.size() != 0; 1763 1764 // Update the UI elements. 1765 mReminderItems.clear(); 1766 LinearLayout reminderLayout = 1767 (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container); 1768 reminderLayout.removeAllViews(); 1769 prepareReminders(); 1770 prepareAvailability(); 1771 } 1772 1773 /** 1774 * Checks if the start and end times for this event should be displayed in 1775 * the Calendar app's time zone as well and formats and displays them. 1776 */ 1777 private void updateHomeTime() { 1778 String tz = Utils.getTimeZone(mActivity, null); 1779 if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone) 1780 && mModification != EditEventHelper.MODIFY_UNINITIALIZED) { 1781 int flags = DateUtils.FORMAT_SHOW_TIME; 1782 boolean is24Format = DateFormat.is24HourFormat(mActivity); 1783 if (is24Format) { 1784 flags |= DateUtils.FORMAT_24HOUR; 1785 } 1786 long millisStart = mStartTime.toMillis(false); 1787 long millisEnd = mEndTime.toMillis(false); 1788 1789 boolean isDSTStart = mStartTime.isDst != 0; 1790 boolean isDSTEnd = mEndTime.isDst != 0; 1791 1792 // First update the start date and times 1793 String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( 1794 isDSTStart, TimeZone.SHORT, Locale.getDefault()); 1795 StringBuilder time = new StringBuilder(); 1796 1797 mSB.setLength(0); 1798 time.append(DateUtils 1799 .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz)) 1800 .append(" ").append(tzDisplay); 1801 mStartTimeHome.setText(time.toString()); 1802 1803 flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE 1804 | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; 1805 mSB.setLength(0); 1806 mStartDateHome 1807 .setText(DateUtils.formatDateRange( 1808 mActivity, mF, millisStart, millisStart, flags, tz).toString()); 1809 1810 // Make any adjustments needed for the end times 1811 if (isDSTEnd != isDSTStart) { 1812 tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( 1813 isDSTEnd, TimeZone.SHORT, Locale.getDefault()); 1814 } 1815 flags = DateUtils.FORMAT_SHOW_TIME; 1816 if (is24Format) { 1817 flags |= DateUtils.FORMAT_24HOUR; 1818 } 1819 1820 // Then update the end times 1821 time.setLength(0); 1822 mSB.setLength(0); 1823 time.append(DateUtils.formatDateRange( 1824 mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay); 1825 mEndTimeHome.setText(time.toString()); 1826 1827 flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE 1828 | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; 1829 mSB.setLength(0); 1830 mEndDateHome.setText(DateUtils.formatDateRange( 1831 mActivity, mF, millisEnd, millisEnd, flags, tz).toString()); 1832 1833 mStartHomeGroup.setVisibility(View.VISIBLE); 1834 mEndHomeGroup.setVisibility(View.VISIBLE); 1835 } else { 1836 mStartHomeGroup.setVisibility(View.GONE); 1837 mEndHomeGroup.setVisibility(View.GONE); 1838 } 1839 } 1840 1841 @Override 1842 public void onNothingSelected(AdapterView<?> parent) { 1843 } 1844 } 1845