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