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