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