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