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