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