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