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