EditEventView.java revision 18cabd263b91d7d09f3b93b0bf6c78533bbc2c6a
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.common.Rfc822InputFilter; 32import com.android.common.Rfc822Validator; 33import com.android.ex.chips.AccountSpecifier; 34import com.android.ex.chips.BaseRecipientAdapter; 35import com.android.ex.chips.ChipsUtil; 36import com.android.ex.chips.RecipientEditTextView; 37 38import android.app.Activity; 39import android.app.AlertDialog; 40import android.app.DatePickerDialog; 41import android.app.DatePickerDialog.OnDateSetListener; 42import android.app.ProgressDialog; 43import android.app.Service; 44import android.app.TimePickerDialog; 45import android.app.TimePickerDialog.OnTimeSetListener; 46import android.content.Context; 47import android.content.DialogInterface; 48import android.content.Intent; 49import android.content.SharedPreferences; 50import android.content.res.Resources; 51import android.database.Cursor; 52import android.graphics.Bitmap; 53import android.graphics.BitmapFactory; 54import android.graphics.drawable.Drawable; 55import android.pim.EventRecurrence; 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 return false; 541 } else if (mEventRecurrence.bydayCount == 0 542 && mEventRecurrence.bymonthdayCount == 1) { 543 return false; 544 } 545 break; 546 case EventRecurrence.YEARLY: 547 return false; 548 } 549 550 return true; 551 } 552 553 private boolean isWeekdayEvent() { 554 if (mStartTime.weekDay != Time.SUNDAY && mStartTime.weekDay != Time.SATURDAY) { 555 return true; 556 } 557 return false; 558 } 559 560 private class DateClickListener implements View.OnClickListener { 561 private Time mTime; 562 563 public DateClickListener(Time time) { 564 mTime = time; 565 } 566 567 public void onClick(View v) { 568 DatePickerDialog dpd = new DatePickerDialog( 569 mActivity, new DateListener(v), mTime.year, mTime.month, mTime.monthDay); 570 CalendarView cv = dpd.getDatePicker().getCalendarView(); 571 cv.setShowWeekNumber(Utils.getShowWeekNumber(mActivity)); 572 int startOfWeek = Utils.getFirstDayOfWeek(mActivity); 573 // Utils returns Time days while CalendarView wants Calendar days 574 if (startOfWeek == Time.SATURDAY) { 575 startOfWeek = Calendar.SATURDAY; 576 } else if (startOfWeek == Time.SUNDAY) { 577 startOfWeek = Calendar.SUNDAY; 578 } else { 579 startOfWeek = Calendar.MONDAY; 580 } 581 cv.setFirstDayOfWeek(startOfWeek); 582 dpd.show(); 583 } 584 } 585 586 static private class CalendarsAdapter extends ResourceCursorAdapter { 587 public CalendarsAdapter(Context context, Cursor c) { 588 super(context, R.layout.calendars_item, c); 589 setDropDownViewResource(R.layout.calendars_dropdown_item); 590 } 591 592 @Override 593 public void bindView(View view, Context context, Cursor cursor) { 594 View colorBar = view.findViewById(R.id.color); 595 int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); 596 int nameColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_DISPLAY_NAME); 597 int ownerColumn = cursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); 598 if (colorBar != null) { 599 colorBar.setBackgroundColor(cursor.getInt(colorColumn)); 600 } 601 602 TextView name = (TextView) view.findViewById(R.id.calendar_name); 603 if (name != null) { 604 String displayName = cursor.getString(nameColumn); 605 name.setText(displayName); 606 607 TextView accountName = (TextView) view.findViewById(R.id.account_name); 608 if (accountName != null) { 609 Resources res = context.getResources(); 610 accountName.setText(cursor.getString(ownerColumn)); 611 accountName.setVisibility(TextView.VISIBLE); 612 accountName.setTextColor(res.getColor(R.color.calendar_owner_text_color)); 613 } 614 } 615 } 616 } 617 618 /** 619 * Does prep steps for saving a calendar event. 620 * 621 * This triggers a parse of the attendees list and checks if the event is 622 * ready to be saved. An event is ready to be saved so long as a model 623 * exists and has a calendar it can be associated with, either because it's 624 * an existing event or we've finished querying. 625 * 626 * @return false if there is no model or no calendar had been loaded yet, 627 * true otherwise. 628 */ 629 public boolean prepareForSave() { 630 if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { 631 return false; 632 } 633 mAddAttendeesListener.onClick(null); 634 return fillModelFromUI(); 635 } 636 637 public boolean fillModelFromReadOnlyUi() { 638 if (mModel == null || (mCalendarsCursor == null && mModel.mUri == null)) { 639 return false; 640 } 641 mModel.mReminders = EventViewUtils.reminderItemsToReminders( 642 mReminderItems, mReminderMinuteValues, mReminderMethodValues); 643 int status = EventInfoFragment.getResponseFromButtonId( 644 mResponseRadioGroup.getCheckedRadioButtonId()); 645 if (status != Attendees.ATTENDEE_STATUS_NONE) { 646 mModel.mSelfAttendeeStatus = status; 647 } 648 return true; 649 } 650 651 // This is called if the user clicks on one of the buttons: "Save", 652 // "Discard", or "Delete". This is also called if the user clicks 653 // on the "remove reminder" button. 654 @Override 655 public void onClick(View view) { 656 657 // This must be a click on one of the "remove reminder" buttons 658 LinearLayout reminderItem = (LinearLayout) view.getParent(); 659 LinearLayout parent = (LinearLayout) reminderItem.getParent(); 660 parent.removeView(reminderItem); 661 mReminderItems.remove(reminderItem); 662 updateRemindersVisibility(mReminderItems.size()); 663 } 664 665 // This is called if the user cancels the "No calendars" dialog. 666 // The "No calendars" dialog is shown if there are no syncable calendars. 667 @Override 668 public void onCancel(DialogInterface dialog) { 669 if (dialog == mLoadingCalendarsDialog) { 670 mLoadingCalendarsDialog = null; 671 mSaveAfterQueryComplete = false; 672 } else if (dialog == mNoCalendarsDialog) { 673 mDone.setDoneCode(Utils.DONE_REVERT); 674 mDone.run(); 675 return; 676 } 677 } 678 679 // This is called if the user clicks on a dialog button. 680 @Override 681 public void onClick(DialogInterface dialog, int which) { 682 if (dialog == mNoCalendarsDialog) { 683 mDone.setDoneCode(Utils.DONE_REVERT); 684 mDone.run(); 685 if (which == DialogInterface.BUTTON_POSITIVE) { 686 Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); 687 final String[] array = {"com.android.calendar"}; 688 nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); 689 nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 690 mActivity.startActivity(nextIntent); 691 } 692 } else if (dialog == mTimezoneDialog) { 693 if (which >= 0 && which < mTimezoneAdapter.getCount()) { 694 setTimezone(which); 695 updateHomeTime(); 696 dialog.dismiss(); 697 } 698 } 699 } 700 701 // Goes through the UI elements and updates the model as necessary 702 private boolean fillModelFromUI() { 703 if (mModel == null) { 704 return false; 705 } 706 mModel.mReminders = EventViewUtils.reminderItemsToReminders(mReminderItems, 707 mReminderMinuteValues, mReminderMethodValues); 708 mModel.mHasAlarm = mReminderItems.size() > 0; 709 mModel.mTitle = mTitleTextView.getText().toString(); 710 mModel.mAllDay = mAllDayCheckBox.isChecked(); 711 mModel.mLocation = mLocationTextView.getText().toString(); 712 mModel.mDescription = mDescriptionTextView.getText().toString(); 713 if (TextUtils.isEmpty(mModel.mLocation)) { 714 mModel.mLocation = null; 715 } 716 if (TextUtils.isEmpty(mModel.mDescription)) { 717 mModel.mDescription = null; 718 } 719 720 int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup 721 .getCheckedRadioButtonId()); 722 if (status != Attendees.ATTENDEE_STATUS_NONE) { 723 mModel.mSelfAttendeeStatus = status; 724 } 725 726 if (mAttendeesView != null && mAttendeesView.getChildCount() > 0) { 727 final int size = mAttendeesView.getChildCount(); 728 mModel.mAttendeesList.clear(); 729 for (int i = 0; i < size; i++) { 730 final Attendee attendee = mAttendeesView.getItem(i); 731 if (attendee == null || mAttendeesView.isMarkAsRemoved(i)) { 732 continue; 733 } 734 mModel.addAttendee(attendee); 735 } 736 } 737 738 // If this was a new event we need to fill in the Calendar information 739 if (mModel.mUri == null) { 740 mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId(); 741 int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition(); 742 if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) { 743 String defaultCalendar = mCalendarsCursor.getString( 744 EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); 745 Utils.setSharedPreference( 746 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar); 747 mModel.mOwnerAccount = defaultCalendar; 748 mModel.mOrganizer = defaultCalendar; 749 mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID); 750 } 751 } 752 753 if (mModel.mAllDay) { 754 // Reset start and end time, increment the monthDay by 1, and set 755 // the timezone to UTC, as required for all-day events. 756 mTimezone = Time.TIMEZONE_UTC; 757 mStartTime.hour = 0; 758 mStartTime.minute = 0; 759 mStartTime.second = 0; 760 mStartTime.timezone = mTimezone; 761 mModel.mStart = mStartTime.normalize(true); 762 763 mEndTime.hour = 0; 764 mEndTime.minute = 0; 765 mEndTime.second = 0; 766 mEndTime.timezone = mTimezone; 767 // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time 768 // should be Y + 1 (Oct.30). 769 final long normalizedEndTimeMillis = 770 mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS; 771 if (normalizedEndTimeMillis < mModel.mStart) { 772 // mEnd should be midnight of the next day of mStart. 773 mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS; 774 } else { 775 mModel.mEnd = normalizedEndTimeMillis; 776 } 777 } else { 778 mStartTime.timezone = mTimezone; 779 mEndTime.timezone = mTimezone; 780 mModel.mStart = mStartTime.toMillis(true); 781 mModel.mEnd = mEndTime.toMillis(true); 782 } 783 mModel.mTimezone = mTimezone; 784 mModel.mAccessLevel = mAccessLevelSpinner.getSelectedItemPosition(); 785 mModel.mAvailability = mAvailabilitySpinner.getSelectedItemPosition() != 0; 786 787 int selection; 788 // If we're making an exception we don't want it to be a repeating 789 // event. 790 if (mModification == EditEventHelper.MODIFY_SELECTED) { 791 selection = EditEventHelper.DOES_NOT_REPEAT; 792 } else { 793 int position = mRepeatsSpinner.getSelectedItemPosition(); 794 selection = mRecurrenceIndexes.get(position); 795 } 796 797 EditEventHelper.updateRecurrenceRule( 798 selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1); 799 800 // Save the timezone so we can display it as a standard option next time 801 if (!mModel.mAllDay) { 802 mTimezoneAdapter.saveRecentTimezone(mTimezone); 803 } 804 return true; 805 } 806 807 public EditEventView(Activity activity, View view, EditDoneRunnable done) { 808 809 mActivity = activity; 810 mView = view; 811 mDone = done; 812 813 DEFAULT_DOMAIN = activity.getResources().getString(R.string.google_email_domain); 814 815 // cache top level view elements 816 mLoadingMessage = (TextView) view.findViewById(R.id.loading_message); 817 mScrollView = (ScrollView) view.findViewById(R.id.scroll_view); 818 819 // cache all the widgets 820 mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars_spinner); 821 mTitleTextView = (TextView) view.findViewById(R.id.title); 822 mLocationTextView = (TextView) view.findViewById(R.id.location); 823 mDescriptionTextView = (TextView) view.findViewById(R.id.description); 824 mTimezoneLabel = (TextView) view.findViewById(R.id.timezone_label); 825 mStartDateButton = (Button) view.findViewById(R.id.start_date); 826 mEndDateButton = (Button) view.findViewById(R.id.end_date); 827 mWhenView = (TextView) mView.findViewById(R.id.when); 828 mTimezoneTextView = (TextView) mView.findViewById(R.id.timezone_textView); 829 mStartTimeButton = (Button) view.findViewById(R.id.start_time); 830 mEndTimeButton = (Button) view.findViewById(R.id.end_time); 831 mTimezoneButton = (Button) view.findViewById(R.id.timezone_button); 832 mStartTimeHome = (TextView) view.findViewById(R.id.start_time_home_tz); 833 mStartDateHome = (TextView) view.findViewById(R.id.start_date_home_tz); 834 mEndTimeHome = (TextView) view.findViewById(R.id.end_time_home_tz); 835 mEndDateHome = (TextView) view.findViewById(R.id.end_date_home_tz); 836 mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day); 837 mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats); 838 mAvailabilitySpinner = (Spinner) view.findViewById(R.id.availability); 839 mAccessLevelSpinner = (Spinner) view.findViewById(R.id.visibility); 840 mCalendarSelectorGroup = view.findViewById(R.id.calendar_selector_group); 841 mCalendarStaticGroup = view.findViewById(R.id.calendar_group); 842 mRemindersGroup = view.findViewById(R.id.reminders_row); 843 mResponseGroup = view.findViewById(R.id.response_row); 844 mOrganizerGroup = view.findViewById(R.id.organizer_row); 845 mAttendeesGroup = view.findViewById(R.id.attendees_row); 846 mAttendeesPane = view.findViewById(R.id.attendees_group); 847 mLocationGroup = view.findViewById(R.id.where_row); 848 mDescriptionGroup = view.findViewById(R.id.description_row); 849 mStartHomeGroup = view.findViewById(R.id.from_row_home_tz); 850 mEndHomeGroup = view.findViewById(R.id.to_row_home_tz); 851 852 mTitleTextView.setTag(mTitleTextView.getBackground()); 853 mLocationTextView.setTag(mLocationTextView.getBackground()); 854 mDescriptionTextView.setTag(mDescriptionTextView.getBackground()); 855 mRepeatsSpinner.setTag(mRepeatsSpinner.getBackground()); 856 mOriginalPadding[0] = mLocationTextView.getPaddingLeft(); 857 mOriginalPadding[1] = mLocationTextView.getPaddingTop(); 858 mOriginalPadding[2] = mLocationTextView.getPaddingRight(); 859 mOriginalPadding[3] = mLocationTextView.getPaddingBottom(); 860 mEditViewList.add(mTitleTextView); 861 mEditViewList.add(mLocationTextView); 862 mEditViewList.add(mDescriptionTextView); 863 864 mViewOnlyList.add(view.findViewById(R.id.when_row)); 865 mViewOnlyList.add(view.findViewById(R.id.timezone_textview_row)); 866 867 mEditOnlyList.add(view.findViewById(R.id.all_day_row)); 868 mEditOnlyList.add(view.findViewById(R.id.availability_row)); 869 mEditOnlyList.add(view.findViewById(R.id.visibility_row)); 870 mEditOnlyList.add(view.findViewById(R.id.from_row)); 871 mEditOnlyList.add(view.findViewById(R.id.to_row)); 872 mEditOnlyList.add(view.findViewById(R.id.timezone_button_row)); 873 mEditOnlyList.add(view.findViewById(R.id.add_attendees_row)); 874 mEditOnlyList.add(mStartHomeGroup); 875 mEditOnlyList.add(mEndHomeGroup); 876 877 mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value); 878 mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container); 879 880 mAddAttendeesButton = (ImageButton) view.findViewById(R.id.add_attendee_button); 881 mAddAttendeesListener = new AddAttendeeClickListener(); 882 mAddAttendeesButton.setEnabled(false); 883 mAddAttendeesButton.setOnClickListener(mAddAttendeesListener); 884 885 mAttendeesView = (AttendeesView)view.findViewById(R.id.attendee_list); 886 887 mTimezone = Utils.getTimeZone(activity, null); 888 mStartTime = new Time(mTimezone); 889 mEndTime = new Time(mTimezone); 890 mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone); 891 892 mColorChip = view.findViewById(R.id.color_chip); 893 894 // Display loading screen 895 setModel(null); 896 } 897 898 899 /** 900 * Loads an integer array asset into a list. 901 */ 902 private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) { 903 int[] vals = r.getIntArray(resNum); 904 int size = vals.length; 905 ArrayList<Integer> list = new ArrayList<Integer>(size); 906 907 for (int i = 0; i < size; i++) { 908 list.add(vals[i]); 909 } 910 911 return list; 912 } 913 914 /** 915 * Loads a String array asset into a list. 916 */ 917 private static ArrayList<String> loadStringArray(Resources r, int resNum) { 918 String[] labels = r.getStringArray(resNum); 919 ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels)); 920 return list; 921 } 922 923 /** 924 * Prepares the reminder UI elements. 925 * <p> 926 * (Re-)loads the minutes / methods lists from the XML assets, adds/removes items as 927 * needed for the current set of reminders and calendar properties, and then creates UI 928 * elements. 929 */ 930 private void prepareReminders() { 931 CalendarEventModel model = mModel; 932 Resources r = mActivity.getResources(); 933 934 // Load the labels and corresponding numeric values for the minutes and methods lists 935 // from the assets. If we're switching calendars, we need to clear and re-populate the 936 // lists (which may have elements added and removed based on calendar properties). This 937 // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a 938 // new event that aren't in the default set. 939 mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values); 940 mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels); 941 mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values); 942 mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels); 943 944 // Remove any reminder methods that aren't allowed for this calendar. If this is 945 // a new event, mCalendarAllowedReminders may not be set the first time we're called. 946 if (mModel.mCalendarAllowedReminders != null) { 947 EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels, 948 mModel.mCalendarAllowedReminders); 949 } 950 951 int numReminders = 0; 952 if (model.mHasAlarm) { 953 ArrayList<ReminderEntry> reminders = model.mReminders; 954 numReminders = reminders.size(); 955 // Insert any minute values that aren't represented in the minutes list. 956 for (ReminderEntry re : reminders) { 957 EventViewUtils.addMinutesToList( 958 mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes()); 959 } 960 961 // Create a UI element for each reminder. We display all of the reminders we get 962 // from the provider, even if the count exceeds the calendar maximum. (Also, for 963 // a new event, we won't have a maxReminders value available.) 964 for (ReminderEntry re : reminders) { 965 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 966 mReminderMinuteValues, mReminderMinuteLabels, 967 mReminderMethodValues, mReminderMethodLabels, 968 re, Integer.MAX_VALUE); 969 } 970 } 971 972 updateRemindersVisibility(numReminders); 973 } 974 975 /** 976 * Fill in the view with the contents of the given event model. This allows 977 * an edit view to be initialized before the event has been loaded. Passing 978 * in null for the model will display a loading screen. A non-null model 979 * will fill in the view's fields with the data contained in the model. 980 * 981 * @param model The event model to pull the data from 982 */ 983 public void setModel(CalendarEventModel model) { 984 mModel = model; 985 986 // Need to close the autocomplete adapter to prevent leaking cursors. 987 if (mAddressAdapter != null && mAddressAdapter instanceof EmailAddressAdapter) { 988 ((EmailAddressAdapter)mAddressAdapter).close(); 989 mAddressAdapter = null; 990 } 991 992 if (model == null) { 993 // Display loading screen 994 mLoadingMessage.setVisibility(View.VISIBLE); 995 mScrollView.setVisibility(View.GONE); 996 return; 997 } 998 999 boolean canModifyCalendar = EditEventHelper.canModifyCalendar(model); 1000 boolean canModifyEvent = EditEventHelper.canModifyEvent(model); 1001 boolean canRespond = EditEventHelper.canRespond(model); 1002 1003 long begin = model.mStart; 1004 long end = model.mEnd; 1005 mTimezone = model.mTimezone; // this will be UTC for all day events 1006 1007 // Set up the starting times 1008 if (begin > 0) { 1009 mStartTime.timezone = mTimezone; 1010 mStartTime.set(begin); 1011 mStartTime.normalize(true); 1012 } 1013 if (end > 0) { 1014 mEndTime.timezone = mTimezone; 1015 mEndTime.set(end); 1016 mEndTime.normalize(true); 1017 } 1018 String rrule = model.mRrule; 1019 if (!TextUtils.isEmpty(rrule)) { 1020 mEventRecurrence.parse(rrule); 1021 } 1022 1023 // If the user is allowed to change the attendees set up the view and 1024 // validator 1025 if (!model.mHasAttendeeData) { 1026 mView.findViewById(R.id.attendees_group).setVisibility(View.GONE); 1027 } else if (!canModifyEvent) { 1028 // Hide views used for adding attendees 1029 View v = mView.findViewById(R.id.add_attendees_label); 1030 if (v != null) { 1031 v.setVisibility(View.GONE); 1032 } 1033 mView.findViewById(R.id.add_attendees_group).setVisibility(View.GONE); 1034 mAddAttendeesButton.setVisibility(View.GONE); 1035 } else { 1036 String domain = DEFAULT_DOMAIN; 1037 if (!TextUtils.isEmpty(model.mOwnerAccount)) { 1038 String ownerDomain = EditEventHelper.extractDomain(model.mOwnerAccount); 1039 if (!TextUtils.isEmpty(ownerDomain)) { 1040 domain = ownerDomain; 1041 } 1042 } 1043 mEmailValidator = new Rfc822Validator(domain); 1044 mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees); 1045 mAttendeesList.addTextChangedListener(this); 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 if (model.mAllDay) { 1056 mAllDayCheckBox.setChecked(true); 1057 // put things back in local time for all day events 1058 mTimezone = TimeZone.getDefault().getID(); 1059 mStartTime.timezone = mTimezone; 1060 mStartTime.normalize(true); 1061 mEndTime.timezone = mTimezone; 1062 mEndTime.normalize(true); 1063 } else { 1064 mAllDayCheckBox.setChecked(false); 1065 } 1066 1067 mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone); 1068 if (mTimezoneDialog != null) { 1069 mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter); 1070 } 1071 1072 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); 1073 String defaultReminderString = prefs.getString( 1074 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); 1075 mDefaultReminderMinutes = Integer.parseInt(defaultReminderString); 1076 1077 prepareReminders(); 1078 1079 ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add); 1080 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { 1081 @Override 1082 public void onClick(View v) { 1083 addReminder(); 1084 } 1085 }; 1086 reminderAddButton.setOnClickListener(addReminderOnClickListener); 1087 1088 mTitleTextView.setText(model.mTitle); 1089 if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer) 1090 || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) { 1091 mView.findViewById(R.id.organizer_label).setVisibility(View.GONE); 1092 mView.findViewById(R.id.organizer).setVisibility(View.GONE); 1093 mOrganizerGroup.setVisibility(View.GONE); 1094 } else { 1095 ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName); 1096 } 1097 mLocationTextView.setText(model.mLocation); 1098 mDescriptionTextView.setText(model.mDescription); 1099 mAvailabilitySpinner.setSelection(model.mAvailability ? 1 : 0); 1100 mAccessLevelSpinner.setSelection(model.mAccessLevel); 1101 1102 View responseLabel = mView.findViewById(R.id.response_label); 1103 if (canRespond) { 1104 int buttonToCheck = EventInfoFragment 1105 .findButtonIdForResponse(model.mSelfAttendeeStatus); 1106 mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons 1107 mResponseRadioGroup.setVisibility(View.VISIBLE); 1108 responseLabel.setVisibility(View.VISIBLE); 1109 } else { 1110 responseLabel.setVisibility(View.GONE); 1111 mResponseRadioGroup.setVisibility(View.GONE); 1112 } 1113 1114 if (model.mUri != null) { 1115 // This is an existing event so hide the calendar spinner 1116 // since we can't change the calendar. 1117 View calendarGroup = mView.findViewById(R.id.calendar_selector_group); 1118 calendarGroup.setVisibility(View.GONE); 1119 TextView tv = (TextView) mView.findViewById(R.id.calendar_textview); 1120 tv.setText(model.mCalendarDisplayName); 1121 tv.setBackgroundColor(model.mCalendarColor); 1122 } else { 1123 View calendarGroup = mView.findViewById(R.id.calendar_group); 1124 calendarGroup.setVisibility(View.GONE); 1125 mCalendarsSpinner.setBackgroundColor(model.mCalendarColor); 1126 mCalendarSelectorGroup.setBackgroundColor(model.mCalendarColor); 1127 } 1128 1129 populateTimezone(); 1130 populateWhen(); 1131 populateRepeats(); 1132 updateAttendees(model.mAttendeesList); 1133 1134 updateView(); 1135 mScrollView.setVisibility(View.VISIBLE); 1136 mLoadingMessage.setVisibility(View.GONE); 1137 sendAccessibilityEvent(); 1138 } 1139 1140 private void sendAccessibilityEvent() { 1141 AccessibilityManager am = 1142 (AccessibilityManager) mActivity.getSystemService(Service.ACCESSIBILITY_SERVICE); 1143 if (!am.isEnabled() || mModel == null) { 1144 return; 1145 } 1146 StringBuilder b = new StringBuilder(); 1147 addFieldsRecursive(b, mView); 1148 CharSequence msg = b.toString(); 1149 1150 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1151 event.setClassName(getClass().getName()); 1152 event.setPackageName(mActivity.getPackageName()); 1153 event.getText().add(msg); 1154 event.setAddedCount(msg.length()); 1155 1156 am.sendAccessibilityEvent(event); 1157 } 1158 1159 private void addFieldsRecursive(StringBuilder b, View v) { 1160 if (v == null || v.getVisibility() != View.VISIBLE) { 1161 return; 1162 } 1163 if (v instanceof TextView) { 1164 CharSequence tv = ((TextView) v).getText(); 1165 if (!TextUtils.isEmpty(tv.toString().trim())) { 1166 b.append(tv + PERIOD_SPACE); 1167 } 1168 } else if (v instanceof RadioGroup) { 1169 RadioGroup rg = (RadioGroup) v; 1170 int id = rg.getCheckedRadioButtonId(); 1171 if (id != View.NO_ID) { 1172 b.append(((RadioButton) (v.findViewById(id))).getText() + PERIOD_SPACE); 1173 } 1174 } else if (v instanceof Spinner) { 1175 Spinner s = (Spinner) v; 1176 if (s.getSelectedItem() instanceof String) { 1177 String str = ((String) (s.getSelectedItem())).trim(); 1178 if (!TextUtils.isEmpty(str)) { 1179 b.append(str + PERIOD_SPACE); 1180 } 1181 } 1182 } else if (v instanceof ViewGroup) { 1183 ViewGroup vg = (ViewGroup) v; 1184 int children = vg.getChildCount(); 1185 for (int i = 0; i < children; i++) { 1186 addFieldsRecursive(b, vg.getChildAt(i)); 1187 } 1188 } 1189 } 1190 1191 /** 1192 * Creates a single line string for the time/duration 1193 */ 1194 protected void setWhenString() { 1195 String when; 1196 int flags = DateUtils.FORMAT_SHOW_DATE; 1197 String tz = mTimezone; 1198 if (mModel.mAllDay) { 1199 flags |= DateUtils.FORMAT_SHOW_WEEKDAY; 1200 tz = Time.TIMEZONE_UTC; 1201 } else { 1202 flags |= DateUtils.FORMAT_SHOW_TIME; 1203 if (DateFormat.is24HourFormat(mActivity)) { 1204 flags |= DateUtils.FORMAT_24HOUR; 1205 } 1206 } 1207 long startMillis = mStartTime.normalize(true); 1208 long endMillis = mEndTime.normalize(true); 1209 mSB.setLength(0); 1210 when = DateUtils 1211 .formatDateRange(mActivity, mF, startMillis, endMillis, flags, tz).toString(); 1212 mWhenView.setText(when); 1213 } 1214 1215 /** 1216 * Configures the Calendars spinner. This is only done for new events, because only new 1217 * events allow you to select a calendar while editing an event. 1218 * <p> 1219 * We tuck a reference to a Cursor with calendar database data into the spinner, so that 1220 * we can easily extract calendar-specific values when the value changes (the spinner's 1221 * onItemSelected callback is configured). 1222 */ 1223 public void setCalendarsCursor(Cursor cursor, boolean userVisible) { 1224 // If there are no syncable calendars, then we cannot allow 1225 // creating a new event. 1226 mCalendarsCursor = cursor; 1227 if (cursor == null || cursor.getCount() == 0) { 1228 // Cancel the "loading calendars" dialog if it exists 1229 if (mSaveAfterQueryComplete) { 1230 mLoadingCalendarsDialog.cancel(); 1231 } 1232 if (!userVisible) { 1233 return; 1234 } 1235 // Create an error message for the user that, when clicked, 1236 // will exit this activity without saving the event. 1237 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 1238 builder.setTitle(R.string.no_syncable_calendars).setIconAttribute( 1239 android.R.attr.alertDialogIcon).setMessage(R.string.no_calendars_found) 1240 .setPositiveButton(R.string.add_account, this) 1241 .setNegativeButton(android.R.string.no, this).setOnCancelListener(this); 1242 mNoCalendarsDialog = builder.show(); 1243 return; 1244 } 1245 1246 int defaultCalendarPosition = findDefaultCalendarPosition(cursor); 1247 1248 // populate the calendars spinner 1249 CalendarsAdapter adapter = new CalendarsAdapter(mActivity, cursor); 1250 mCalendarsSpinner.setAdapter(adapter); 1251 mCalendarsSpinner.setSelection(defaultCalendarPosition); 1252 mCalendarsSpinner.setOnItemSelectedListener(this); 1253 1254 int colorColumn = cursor.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); 1255 mColorChip.setBackgroundColor(cursor.getInt(colorColumn)); 1256 1257 1258 // Find user domain and set it to the validator. 1259 // TODO: we may want to update this validator if the user actually picks 1260 // a different calendar. maybe not. depends on what we want for the 1261 // user experience. this may change when we add support for multiple 1262 // accounts, anyway. 1263 if (mModel != null && mModel.mHasAttendeeData 1264 && cursor.moveToPosition(defaultCalendarPosition)) { 1265 String ownEmail = cursor.getString(EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); 1266 if (ownEmail != null) { 1267 String domain = EditEventHelper.extractDomain(ownEmail); 1268 if (domain != null) { 1269 mEmailValidator = new Rfc822Validator(domain); 1270 mAttendeesList.setValidator(mEmailValidator); 1271 } 1272 } 1273 } 1274 if (mSaveAfterQueryComplete) { 1275 mLoadingCalendarsDialog.cancel(); 1276 if (prepareForSave() && fillModelFromUI()) { 1277 int exit = userVisible ? Utils.DONE_EXIT : 0; 1278 mDone.setDoneCode(Utils.DONE_SAVE | exit); 1279 mDone.run(); 1280 } else if (userVisible) { 1281 mDone.setDoneCode(Utils.DONE_EXIT); 1282 mDone.run(); 1283 } else if (Log.isLoggable(TAG, Log.DEBUG)) { 1284 Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view"); 1285 } 1286 return; 1287 } 1288 } 1289 1290 /** 1291 * Updates the view based on {@link #mModification} and {@link #mModel} 1292 */ 1293 public void updateView() { 1294 if (mModel == null) { 1295 return; 1296 } 1297 if (EditEventHelper.canModifyEvent(mModel)) { 1298 setViewStates(mModification); 1299 } else { 1300 setViewStates(Utils.MODIFY_UNINITIALIZED); 1301 } 1302 } 1303 1304 private void setViewStates(int mode) { 1305 // Extra canModify check just in case 1306 if (mode == Utils.MODIFY_UNINITIALIZED || !EditEventHelper.canModifyEvent(mModel)) { 1307 setWhenString(); 1308 1309 for (View v : mViewOnlyList) { 1310 v.setVisibility(View.VISIBLE); 1311 } 1312 for (View v : mEditOnlyList) { 1313 v.setVisibility(View.GONE); 1314 } 1315 for (View v : mEditViewList) { 1316 v.setEnabled(false); 1317 v.setBackgroundDrawable(null); 1318 } 1319 mCalendarSelectorGroup.setVisibility(View.GONE); 1320 mCalendarStaticGroup.setVisibility(View.VISIBLE); 1321 mRepeatsSpinner.setEnabled(false); 1322 mRepeatsSpinner.setBackgroundDrawable(null); 1323 if (EditEventHelper.canAddReminders(mModel)) { 1324 mRemindersGroup.setVisibility(View.VISIBLE); 1325 } else { 1326 mRemindersGroup.setVisibility(View.GONE); 1327 } 1328 setAttendeesEditable(false); 1329 if (mAttendeesView.getChildCount() == 0) { 1330 mAttendeesPane.setVisibility(View.GONE); 1331 } else { 1332 mAttendeesPane.setVisibility(View.VISIBLE); 1333 } 1334 if (mAllDayCheckBox.isChecked()) { 1335 mView.findViewById(R.id.timezone_textview_row).setVisibility(View.GONE); 1336 } 1337 if (TextUtils.isEmpty(mLocationTextView.getText())) { 1338 mLocationGroup.setVisibility(View.GONE); 1339 } 1340 if (TextUtils.isEmpty(mDescriptionTextView.getText())) { 1341 mDescriptionGroup.setVisibility(View.GONE); 1342 } 1343 } else { 1344 for (View v : mViewOnlyList) { 1345 v.setVisibility(View.GONE); 1346 } 1347 for (View v : mEditOnlyList) { 1348 v.setVisibility(View.VISIBLE); 1349 } 1350 for (View v : mEditViewList) { 1351 v.setEnabled(true); 1352 if (v.getTag() != null) { 1353 v.setBackgroundDrawable((Drawable) v.getTag()); 1354 } 1355 } 1356 if (mModel.mUri == null) { 1357 mCalendarSelectorGroup.setVisibility(View.VISIBLE); 1358 mCalendarStaticGroup.setVisibility(View.GONE); 1359 } else { 1360 mCalendarSelectorGroup.setVisibility(View.GONE); 1361 mCalendarStaticGroup.setVisibility(View.VISIBLE); 1362 } 1363 mRepeatsSpinner.setBackgroundDrawable((Drawable) mRepeatsSpinner.getTag()); 1364 if (mModel.mOriginalEvent == null) { 1365 mRepeatsSpinner.setEnabled(true); 1366 } else { 1367 mRepeatsSpinner.setEnabled(false); 1368 } 1369 mRemindersGroup.setVisibility(View.VISIBLE); 1370 setAttendeesEditable(true); 1371 mAttendeesPane.setVisibility(View.VISIBLE); 1372 1373 mLocationGroup.setVisibility(View.VISIBLE); 1374 mDescriptionGroup.setVisibility(View.VISIBLE); 1375 } 1376 } 1377 1378 /** 1379 * Shows or hides the Guests view and sets the buttons for removing 1380 * attendees based on the value passed in. 1381 * 1382 * @param editable View.GONE or View.VISIBLE 1383 */ 1384 protected void setAttendeesEditable(boolean editable) { 1385 int attCount = mAttendeesView.getChildCount(); 1386 if (attCount > 0) { 1387 mResponseGroup.setVisibility(View.VISIBLE); 1388 mAttendeesGroup.setVisibility(View.VISIBLE); 1389 } else { 1390 mResponseGroup.setVisibility(View.GONE); 1391 mAttendeesGroup.setVisibility(View.GONE); 1392 } 1393 mAttendeesView.setEnabled(editable); 1394 } 1395 1396 public void setModification(int modifyWhich) { 1397 mModification = modifyWhich; 1398 updateView(); 1399 updateHomeTime(); 1400 } 1401 1402 // Find the calendar position in the cursor that matches calendar in 1403 // preference 1404 private int findDefaultCalendarPosition(Cursor calendarsCursor) { 1405 if (calendarsCursor.getCount() <= 0) { 1406 return -1; 1407 } 1408 1409 String defaultCalendar = Utils.getSharedPreference( 1410 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, null); 1411 1412 if (defaultCalendar == null) { 1413 return 0; 1414 } 1415 int calendarsOwnerColumn = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); 1416 int position = 0; 1417 calendarsCursor.moveToPosition(-1); 1418 while (calendarsCursor.moveToNext()) { 1419 if (defaultCalendar.equals(calendarsCursor.getString(calendarsOwnerColumn))) { 1420 return position; 1421 } 1422 position++; 1423 } 1424 return 0; 1425 } 1426 1427 private void updateAttendees(HashMap<String, Attendee> attendeesList) { 1428 mAttendeesView.setRfc822Validator(mEmailValidator); 1429 mAttendeesView.addAttendees(attendeesList); 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); 1453 } else { 1454 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1455 mReminderMinuteValues, mReminderMinuteLabels, 1456 mReminderMethodValues, mReminderMethodLabels, 1457 ReminderEntry.valueOf(mDefaultReminderMinutes), 1458 mModel.mCalendarMaxReminders); 1459 } 1460 updateRemindersVisibility(mReminderItems.size()); 1461 } 1462 1463 // From com.google.android.gm.ComposeActivity 1464 private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) { 1465 RecipientEditTextView list = (RecipientEditTextView) mView.findViewById(res); 1466 if (ChipsUtil.supportsChipsUi()) { 1467 mAddressAdapter = new RecipientAdapter(mActivity); 1468 list.setAdapter((BaseRecipientAdapter) mAddressAdapter); 1469 Resources r = mActivity.getResources(); 1470 Bitmap def = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture); 1471 list.setChipDimensions( 1472 r.getDrawable(R.drawable.chip_background), 1473 r.getDrawable(R.drawable.chip_background_selected), 1474 r.getDrawable(R.drawable.chip_background_invalid), 1475 r.getDrawable(R.drawable.chip_delete), 1476 def, 1477 R.string.more_string, 1478 R.layout.chips_alternate_item, 1479 r.getDimension(R.dimen.chip_height), 1480 r.getDimension(R.dimen.chip_padding), 1481 r.getDimension(R.dimen.chip_text_size)); 1482 } else { 1483 mAddressAdapter = new EmailAddressAdapter(mActivity); 1484 list.setAdapter((EmailAddressAdapter)mAddressAdapter); 1485 } 1486 list.setTokenizer(new Rfc822Tokenizer()); 1487 list.setValidator(mEmailValidator); 1488 1489 // NOTE: assumes no other filters are set 1490 list.setFilters(sRecipientFilters); 1491 1492 return list; 1493 } 1494 1495 /** 1496 * From com.google.android.gm.ComposeActivity Implements special address 1497 * cleanup rules: The first space key entry following an "@" symbol that is 1498 * followed by any combination of letters and symbols, including one+ dots 1499 * and zero commas, should insert an extra comma (followed by the space). 1500 */ 1501 private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() }; 1502 1503 private void setDate(TextView view, long millis) { 1504 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR 1505 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH 1506 | DateUtils.FORMAT_ABBREV_WEEKDAY; 1507 1508 // Unfortunately, DateUtils doesn't support a timezone other than the 1509 // default timezone provided by the system, so we have this ugly hack 1510 // here to trick it into formatting our time correctly. In order to 1511 // prevent all sorts of craziness, we synchronize on the TimeZone class 1512 // to prevent other threads from reading an incorrect timezone from 1513 // calls to TimeZone#getDefault() 1514 // TODO fix this if/when DateUtils allows for passing in a timezone 1515 String dateString; 1516 synchronized (TimeZone.class) { 1517 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1518 dateString = DateUtils.formatDateTime(mActivity, millis, flags); 1519 // setting the default back to null restores the correct behavior 1520 TimeZone.setDefault(null); 1521 } 1522 view.setText(dateString); 1523 } 1524 1525 private void setTime(TextView view, long millis) { 1526 int flags = DateUtils.FORMAT_SHOW_TIME; 1527 if (DateFormat.is24HourFormat(mActivity)) { 1528 flags |= DateUtils.FORMAT_24HOUR; 1529 } 1530 1531 // Unfortunately, DateUtils doesn't support a timezone other than the 1532 // default timezone provided by the system, so we have this ugly hack 1533 // here to trick it into formatting our time correctly. In order to 1534 // prevent all sorts of craziness, we synchronize on the TimeZone class 1535 // to prevent other threads from reading an incorrect timezone from 1536 // calls to TimeZone#getDefault() 1537 // TODO fix this if/when DateUtils allows for passing in a timezone 1538 String timeString; 1539 synchronized (TimeZone.class) { 1540 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1541 timeString = DateUtils.formatDateTime(mActivity, millis, flags); 1542 TimeZone.setDefault(null); 1543 } 1544 view.setText(timeString); 1545 } 1546 1547 private void setTimezone(int i) { 1548 if (i < 0 || i >= mTimezoneAdapter.getCount()) { 1549 return; // do nothing 1550 } 1551 TimezoneRow timezone = mTimezoneAdapter.getItem(i); 1552 mTimezoneTextView.setText(timezone.toString()); 1553 mTimezoneButton.setText(timezone.toString()); 1554 mTimezone = timezone.mId; 1555 mStartTime.timezone = mTimezone; 1556 mStartTime.normalize(true); 1557 mEndTime.timezone = mTimezone; 1558 mEndTime.normalize(true); 1559 mTimezoneAdapter.setCurrentTimezone(mTimezone); 1560 } 1561 1562 // TextWatcher interface 1563 @Override 1564 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1565 } 1566 1567 // TextWatcher interface 1568 @Override 1569 public void onTextChanged(CharSequence s, int start, int before, int count) { 1570 } 1571 1572 // TextWatcher interface 1573 @Override 1574 public void afterTextChanged(Editable s) { 1575 mAddAttendeesButton.setEnabled(s.length() > 0); 1576 } 1577 1578 /** 1579 * @param isChecked 1580 */ 1581 protected void setAllDayViewsVisibility(boolean isChecked) { 1582 if (isChecked) { 1583 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 1584 mEndTime.monthDay--; 1585 long endMillis = mEndTime.normalize(true); 1586 1587 // Do not allow an event to have an end time 1588 // before the 1589 // start time. 1590 if (mEndTime.before(mStartTime)) { 1591 mEndTime.set(mStartTime); 1592 endMillis = mEndTime.normalize(true); 1593 } 1594 setDate(mEndDateButton, endMillis); 1595 setTime(mEndTimeButton, endMillis); 1596 } 1597 1598 mStartTimeButton.setVisibility(View.GONE); 1599 mEndTimeButton.setVisibility(View.GONE); 1600 mTimezoneButton.setVisibility(View.GONE); 1601 mTimezoneLabel.setVisibility(View.GONE); 1602 } else { 1603 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 1604 mEndTime.monthDay++; 1605 long endMillis = mEndTime.normalize(true); 1606 setDate(mEndDateButton, endMillis); 1607 setTime(mEndTimeButton, endMillis); 1608 } 1609 mStartTimeButton.setVisibility(View.VISIBLE); 1610 mEndTimeButton.setVisibility(View.VISIBLE); 1611 mTimezoneButton.setVisibility(View.VISIBLE); 1612 mTimezoneLabel.setVisibility(View.VISIBLE); 1613 } 1614 updateHomeTime(); 1615 } 1616 1617 @Override 1618 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 1619 // This is only used for the Calendar spinner in new events, and only fires when the 1620 // calendar selection changes. 1621 Cursor c = (Cursor) parent.getItemAtPosition(position); 1622 if (c == null) { 1623 // TODO: can this happen? should we drop this check? 1624 Log.w(TAG, "Cursor not set on calendar item"); 1625 return; 1626 } 1627 1628 int colorColumn = c.getColumnIndexOrThrow(Calendars.CALENDAR_COLOR); 1629 mColorChip.setBackgroundColor(c.getInt(colorColumn)); 1630 mModel.mCalendarColor = c.getInt(colorColumn); 1631 mCalendarsSpinner.setBackgroundColor(mModel.mCalendarColor); 1632 mCalendarSelectorGroup.setBackgroundColor(mModel.mCalendarColor); 1633 1634 1635 // Update the max/allowed reminders with the new calendar properties. 1636 int maxRemindersColumn = c.getColumnIndexOrThrow(Calendars.MAX_REMINDERS); 1637 mModel.mCalendarMaxReminders = c.getInt(maxRemindersColumn); 1638 int allowedRemindersColumn = c.getColumnIndexOrThrow(Calendars.ALLOWED_REMINDERS); 1639 mModel.mCalendarAllowedReminders = c.getString(allowedRemindersColumn); 1640 1641 // Discard the current reminders and replace them with the model's default reminder set. 1642 // We could attempt to save & restore the reminders that have been added, but that's 1643 // probably more trouble than it's worth. 1644 mModel.mReminders.clear(); 1645 mModel.mReminders.addAll(mModel.mDefaultReminders); 1646 mModel.mHasAlarm = mModel.mReminders.size() != 0; 1647 1648 // Update the UI elements. 1649 mReminderItems.clear(); 1650 LinearLayout reminderLayout = 1651 (LinearLayout) mScrollView.findViewById(R.id.reminder_items_container); 1652 reminderLayout.removeAllViews(); 1653 prepareReminders(); 1654 } 1655 1656 /** 1657 * Checks if the start and end times for this event should be displayed in 1658 * the Calendar app's time zone as well and formats and displays them. 1659 */ 1660 private void updateHomeTime() { 1661 String tz = Utils.getTimeZone(mActivity, null); 1662 if (!mAllDayCheckBox.isChecked() && !TextUtils.equals(tz, mTimezone) 1663 && mModification != EditEventHelper.MODIFY_UNINITIALIZED) { 1664 int flags = DateUtils.FORMAT_SHOW_TIME; 1665 boolean is24Format = DateFormat.is24HourFormat(mActivity); 1666 if (is24Format) { 1667 flags |= DateUtils.FORMAT_24HOUR; 1668 } 1669 long millisStart = mStartTime.toMillis(false); 1670 long millisEnd = mEndTime.toMillis(false); 1671 1672 boolean isDSTStart = mStartTime.isDst != 0; 1673 boolean isDSTEnd = mEndTime.isDst != 0; 1674 1675 // First update the start date and times 1676 String tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( 1677 isDSTStart, TimeZone.SHORT, Locale.getDefault()); 1678 StringBuilder time = new StringBuilder(); 1679 1680 mSB.setLength(0); 1681 time.append(DateUtils 1682 .formatDateRange(mActivity, mF, millisStart, millisStart, flags, tz)) 1683 .append(" ").append(tzDisplay); 1684 mStartTimeHome.setText(time.toString()); 1685 1686 flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE 1687 | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; 1688 mSB.setLength(0); 1689 mStartDateHome 1690 .setText(DateUtils.formatDateRange( 1691 mActivity, mF, millisStart, millisStart, flags, tz).toString()); 1692 1693 // Make any adjustments needed for the end times 1694 if (isDSTEnd != isDSTStart) { 1695 tzDisplay = TimeZone.getTimeZone(tz).getDisplayName( 1696 isDSTEnd, TimeZone.SHORT, Locale.getDefault()); 1697 } 1698 flags = DateUtils.FORMAT_SHOW_TIME; 1699 if (is24Format) { 1700 flags |= DateUtils.FORMAT_24HOUR; 1701 } 1702 1703 // Then update the end times 1704 time.setLength(0); 1705 mSB.setLength(0); 1706 time.append(DateUtils.formatDateRange( 1707 mActivity, mF, millisEnd, millisEnd, flags, tz)).append(" ").append(tzDisplay); 1708 mEndTimeHome.setText(time.toString()); 1709 1710 flags = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE 1711 | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY; 1712 mSB.setLength(0); 1713 mEndDateHome.setText(DateUtils.formatDateRange( 1714 mActivity, mF, millisEnd, millisEnd, flags, tz).toString()); 1715 1716 mStartHomeGroup.setVisibility(View.VISIBLE); 1717 mEndHomeGroup.setVisibility(View.VISIBLE); 1718 } else { 1719 mStartHomeGroup.setVisibility(View.GONE); 1720 mEndHomeGroup.setVisibility(View.GONE); 1721 } 1722 } 1723 1724 @Override 1725 public void onNothingSelected(AdapterView<?> parent) { 1726 mColorChip.setBackgroundColor(0); 1727 1728 } 1729} 1730