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