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