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