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