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