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