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