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