EditEventView.java revision 29190975b9238dd6841f822f82f2fb83b0557f36
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.Utils; 27import com.android.calendar.TimezoneAdapter.TimezoneRow; 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.ProgressDialog; 36import android.app.DatePickerDialog.OnDateSetListener; 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.InputFilter; 50import android.text.TextUtils; 51import android.text.format.DateFormat; 52import android.text.format.DateUtils; 53import android.text.format.Time; 54import android.text.util.Rfc822Tokenizer; 55import android.util.Log; 56import android.view.LayoutInflater; 57import android.view.View; 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.MultiAutoCompleteTextView; 66import android.widget.RadioGroup; 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 RadioGroup mResponseRadioGroup; 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 @Override 600 public void onCancel(DialogInterface dialog) { 601 if (dialog == mLoadingCalendarsDialog) { 602 mLoadingCalendarsDialog = null; 603 mSaveAfterQueryComplete = false; 604 } else if (dialog == mNoCalendarsDialog) { 605 mDone.setDoneCode(Utils.DONE_REVERT); 606 mDone.run(); 607 return; 608 } 609 } 610 611 // This is called if the user clicks on a dialog button. 612 @Override 613 public void onClick(DialogInterface dialog, int which) { 614 if (dialog == mNoCalendarsDialog) { 615 mDone.setDoneCode(Utils.DONE_REVERT); 616 mDone.run(); 617 if (which == DialogInterface.BUTTON_POSITIVE) { 618 Intent nextIntent = new Intent(Settings.ACTION_ADD_ACCOUNT); 619 final String[] array = {"com.android.calendar"}; 620 nextIntent.putExtra(Settings.EXTRA_AUTHORITIES, array); 621 mActivity.startActivity(nextIntent); 622 } 623 } else if (dialog == mTimezoneDialog) { 624 if (which >= 0 && which < mTimezoneAdapter.getCount()) { 625 setTimezone(which); 626 dialog.dismiss(); 627 } 628 } 629 } 630 631 // Goes through the UI elements and updates the model as necessary 632 private boolean fillModelFromUI() { 633 if (mModel == null) { 634 return false; 635 } 636 mModel.mReminderMinutes = EventViewUtils.reminderItemsToMinutes( 637 mReminderItems, mReminderValues); 638 mModel.mHasAlarm = mReminderItems.size() > 0; 639 mModel.mTitle = mTitleTextView.getText().toString().trim(); 640 mModel.mAllDay = mAllDayCheckBox.isChecked(); 641 mModel.mLocation = mLocationTextView.getText().toString().trim(); 642 mModel.mDescription = mDescriptionTextView.getText().toString().trim(); 643 644 int status = EventInfoFragment.getResponseFromButtonId(mResponseRadioGroup 645 .getCheckedRadioButtonId()); 646 if (status != Attendees.ATTENDEE_STATUS_NONE) { 647 mModel.mSelfAttendeeStatus = status; 648 } 649 650 if (mAttendeesView != null && mAttendeesView.getChildCount() > 0) { 651 final int size = mAttendeesView.getChildCount(); 652 mModel.mAttendeesList.clear(); 653 for (int i = 0; i < size; i++) { 654 final Attendee attendee = mAttendeesView.getItem(i); 655 if (attendee == null || mAttendeesView.isMarkAsRemoved(i)) { 656 continue; 657 } 658 mModel.addAttendee(attendee); 659 } 660 } 661 662 // If this was a new event we need to fill in the Calendar information 663 if (mModel.mUri == null) { 664 mModel.mCalendarId = mCalendarsSpinner.getSelectedItemId(); 665 int calendarCursorPosition = mCalendarsSpinner.getSelectedItemPosition(); 666 if (mCalendarsCursor.moveToPosition(calendarCursorPosition)) { 667 String defaultCalendar = mCalendarsCursor.getString( 668 EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); 669 Utils.setSharedPreference( 670 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, defaultCalendar); 671 mModel.mOwnerAccount = defaultCalendar; 672 mModel.mOrganizer = defaultCalendar; 673 mModel.mCalendarId = mCalendarsCursor.getLong(EditEventHelper.CALENDARS_INDEX_ID); 674 } 675 } 676 677 if (mModel.mAllDay) { 678 // Reset start and end time, increment the monthDay by 1, and set 679 // the timezone to UTC, as required for all-day events. 680 mTimezone = Time.TIMEZONE_UTC; 681 mStartTime.hour = 0; 682 mStartTime.minute = 0; 683 mStartTime.second = 0; 684 mStartTime.timezone = mTimezone; 685 mModel.mStart = mStartTime.normalize(true); 686 687 mEndTime.hour = 0; 688 mEndTime.minute = 0; 689 mEndTime.second = 0; 690 mEndTime.timezone = mTimezone; 691 // When a user see the event duration as "X - Y" (e.g. Oct. 28 - Oct. 29), end time 692 // should be Y + 1 (Oct.30). 693 final long normalizedEndTimeMillis = 694 mEndTime.normalize(true) + DateUtils.DAY_IN_MILLIS; 695 if (normalizedEndTimeMillis < mModel.mStart) { 696 // mEnd should be midnight of the next day of mStart. 697 mModel.mEnd = mModel.mStart + DateUtils.DAY_IN_MILLIS; 698 } else { 699 mModel.mEnd = normalizedEndTimeMillis; 700 } 701 } else { 702 mStartTime.timezone = mTimezone; 703 mEndTime.timezone = mTimezone; 704 mModel.mStart = mStartTime.toMillis(true); 705 mModel.mEnd = mEndTime.toMillis(true); 706 } 707 mModel.mTimezone = mTimezone; 708 mModel.mVisibility = mVisibilitySpinner.getSelectedItemPosition(); 709 mModel.mTransparency = mTransparencySpinner.getSelectedItemPosition() != 0; 710 711 int selection; 712 // If we're making an exception we don't want it to be a repeating 713 // event. 714 if (mModification == EditEventHelper.MODIFY_SELECTED) { 715 selection = EditEventHelper.DOES_NOT_REPEAT; 716 } else { 717 int position = mRepeatsSpinner.getSelectedItemPosition(); 718 selection = mRecurrenceIndexes.get(position); 719 } 720 721 EditEventHelper.updateRecurrenceRule( 722 selection, mModel, Utils.getFirstDayOfWeek(mActivity) + 1); 723 724 // Save the timezone so we can display it as a standard option next time 725 if (!mModel.mAllDay) { 726 mTimezoneAdapter.saveRecentTimezone(mTimezone); 727 } 728 return true; 729 } 730 731 public EditEventView(Activity activity, View view, EditDoneRunnable done) { 732 733 mActivity = activity; 734 mView = view; 735 mDone = done; 736 737 // cache top level view elements 738 mLoadingMessage = (TextView) view.findViewById(R.id.loading_message); 739 mScrollView = (ScrollView) view.findViewById(R.id.scroll_view); 740 741 mLayoutInflater = activity.getLayoutInflater(); 742 743 // cache all the widgets 744 mCalendarsSpinner = (Spinner) view.findViewById(R.id.calendars); 745 mViewList.add(mCalendarsSpinner); 746 mTitleTextView = (TextView) view.findViewById(R.id.title); 747 mViewList.add(mTitleTextView); 748 mLocationTextView = (TextView) view.findViewById(R.id.location); 749 mViewList.add(mLocationTextView); 750 mDescriptionTextView = (TextView) view.findViewById(R.id.description); 751 mViewList.add(mDescriptionTextView); 752 mTimezoneTextView = (TextView) view.findViewById(R.id.timezone_label); 753 mTimezoneFooterView = (TextView) mLayoutInflater.inflate(R.layout.timezone_footer, null); 754 mStartDateButton = (Button) view.findViewById(R.id.start_date); 755 mViewList.add(mStartDateButton); 756 mEndDateButton = (Button) view.findViewById(R.id.end_date); 757 mViewList.add(mEndDateButton); 758 mStartTimeButton = (Button) view.findViewById(R.id.start_time); 759 mViewList.add(mStartTimeButton); 760 mEndTimeButton = (Button) view.findViewById(R.id.end_time); 761 mViewList.add(mEndTimeButton); 762 mTimezoneButton = (Button) view.findViewById(R.id.timezone); 763 mViewList.add(mTimezoneButton); 764 mAllDayCheckBox = (CheckBox) view.findViewById(R.id.is_all_day); 765 mRepeatsSpinner = (Spinner) view.findViewById(R.id.repeats); 766 mViewList.add(mRepeatsSpinner); 767 mTransparencySpinner = (Spinner) view.findViewById(R.id.availability); 768 mViewList.add(mTransparencySpinner); 769 mVisibilitySpinner = (Spinner) view.findViewById(R.id.visibility); 770 mViewList.add(mVisibilitySpinner); 771 772 mResponseRadioGroup = (RadioGroup) view.findViewById(R.id.response_value); 773 mRemindersContainer = (LinearLayout) view.findViewById(R.id.reminder_items_container); 774 775 mSaveButton = (Button) view.findViewById(R.id.save); 776 mDeleteButton = (Button) view.findViewById(R.id.delete); 777 778 mDiscardButton = (Button) view.findViewById(R.id.discard); 779 mDiscardButton.setOnClickListener(this); 780 781 mAddAttendeesButton = (ImageButton) view.findViewById(R.id.add_attendee_button); 782 mAddAttendeesListener = new AddAttendeeClickListener(); 783 mAddAttendeesButton.setOnClickListener(mAddAttendeesListener); 784 785 mAttendeesView = (AttendeesView)view.findViewById(R.id.attendee_list); 786 mViewList.add(mAttendeesView); 787 788 mStartTime = new Time(); 789 mEndTime = new Time(); 790 mTimezone = TimeZone.getDefault().getID(); 791 mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone); 792 793 // Display loading screen 794 setModel(null); 795 } 796 797 /** 798 * Fill in the view with the contents of the given event model. This allows 799 * an edit view to be initialized before the event has been loaded. Passing 800 * in null for the model will display a loading screen. A non-null model 801 * will fill in the view's fields with the data contained in the model. 802 * 803 * @param model The event model to pull the data from 804 */ 805 public void setModel(CalendarEventModel model) { 806 mModel = model; 807 808 // Need to close the autocomplete adapter to prevent leaking cursors. 809 if (mAddressAdapter != null) { 810 mAddressAdapter.close(); 811 mAddressAdapter = null; 812 } 813 814 if (model == null) { 815 // Display loading screen 816 mLoadingMessage.setVisibility(View.VISIBLE); 817 mScrollView.setVisibility(View.GONE); 818 mSaveButton.setEnabled(false); 819 mDeleteButton.setEnabled(false); 820 return; 821 } 822 823 boolean canModifyCalendar = EditEventHelper.canModifyCalendar(model); 824 boolean canModifyEvent = EditEventHelper.canModifyEvent(model); 825 boolean canRespond = EditEventHelper.canRespond(model); 826 827 long begin = model.mStart; 828 long end = model.mEnd; 829 mTimezone = model.mTimezone; // this will be UTC for all day events 830 831 // Set up the starting times 832 if (begin > 0) { 833 mStartTime.timezone = mTimezone; 834 mStartTime.set(begin); 835 mStartTime.normalize(true); 836 } 837 if (end > 0) { 838 mEndTime.timezone = mTimezone; 839 mEndTime.set(end); 840 mEndTime.normalize(true); 841 } 842 String rrule = model.mRrule; 843 if (rrule != null) { 844 mEventRecurrence.parse(rrule); 845 } 846 847 // If the user is allowed to change the attendees set up the view and 848 // validator 849 if (!model.mHasAttendeeData) { 850 mView.findViewById(R.id.attendees_group).setVisibility(View.GONE); 851 } else if (!canModifyEvent) { 852 // Hide views used for adding attendees 853 mView.findViewById(R.id.add_attendees_label).setVisibility(View.GONE); 854 mView.findViewById(R.id.add_attendees_group).setVisibility(View.GONE); 855 mAddAttendeesButton.setVisibility(View.GONE); 856 } else { 857 String domain = "gmail.com"; 858 if (!TextUtils.isEmpty(model.mOwnerAccount)) { 859 String ownerDomain = EditEventHelper.extractDomain(model.mOwnerAccount); 860 if (!TextUtils.isEmpty(ownerDomain)) { 861 domain = ownerDomain; 862 } 863 } 864 mAddressAdapter = new EmailAddressAdapter(mActivity); 865 mEmailValidator = new Rfc822Validator(domain); 866 mAttendeesList = initMultiAutoCompleteTextView(R.id.attendees); 867 mViewList.add(mAttendeesList); 868 } 869 870 if (canModifyEvent) { 871 mAllDayCheckBox 872 .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 873 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 874 if (isChecked) { 875 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 876 mEndTime.monthDay--; 877 long endMillis = mEndTime.normalize(true); 878 879 // Do not allow an event to have an end time 880 // before the 881 // start time. 882 if (mEndTime.before(mStartTime)) { 883 mEndTime.set(mStartTime); 884 endMillis = mEndTime.normalize(true); 885 } 886 setDate(mEndDateButton, endMillis); 887 setTime(mEndTimeButton, endMillis); 888 } 889 890 mStartTimeButton.setVisibility(View.GONE); 891 mEndTimeButton.setVisibility(View.GONE); 892 mTimezoneButton.setVisibility(View.GONE); 893 mTimezoneTextView.setVisibility(View.GONE); 894 } else { 895 if (mEndTime.hour == 0 && mEndTime.minute == 0) { 896 mEndTime.monthDay++; 897 long endMillis = mEndTime.normalize(true); 898 setDate(mEndDateButton, endMillis); 899 setTime(mEndTimeButton, endMillis); 900 } 901 mStartTimeButton.setVisibility(View.VISIBLE); 902 mEndTimeButton.setVisibility(View.VISIBLE); 903 mTimezoneButton.setVisibility(View.VISIBLE); 904 mTimezoneTextView.setVisibility(View.VISIBLE); 905 } 906 } 907 }); 908 } else { 909 // Hide all day if read only 910 mView.findViewById(R.id.is_all_day_label).setVisibility(View.GONE); 911 mAllDayCheckBox.setVisibility(View.GONE); 912 } 913 914 if (model.mAllDay) { 915 mAllDayCheckBox.setChecked(true); 916 // put things back in local time for all day events 917 mTimezone = TimeZone.getDefault().getID(); 918 mStartTime.timezone = mTimezone; 919 mStartTime.normalize(true); 920 mEndTime.timezone = mTimezone; 921 mEndTime.normalize(true); 922 } else { 923 mAllDayCheckBox.setChecked(false); 924 } 925 926 mTimezoneAdapter = new TimezoneAdapter(mActivity, mTimezone); 927 if (mTimezoneDialog != null) { 928 mTimezoneDialog.getListView().setAdapter(mTimezoneAdapter); 929 } 930 931 if (canRespond || canModifyEvent) { 932 mSaveButton.setOnClickListener(this); 933 mSaveButton.setEnabled(true); 934 } else { 935 mSaveButton.setEnabled(false); 936 } 937 938 if (canModifyCalendar) { 939 mDeleteButton.setOnClickListener(this); 940 mDeleteButton.setEnabled(true); 941 } else { 942 mDeleteButton.setEnabled(false); 943 } 944 945 // Initialize the reminder values array. 946 Resources r = mActivity.getResources(); 947 String[] strings = r.getStringArray(R.array.reminder_minutes_values); 948 int size = strings.length; 949 ArrayList<Integer> list = new ArrayList<Integer>(size); 950 for (int i = 0; i < size; i++) { 951 list.add(Integer.parseInt(strings[i])); 952 } 953 mReminderValues = list; 954 String[] labels = r.getStringArray(R.array.reminder_minutes_labels); 955 mReminderLabels = new ArrayList<String>(Arrays.asList(labels)); 956 957 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); 958 String durationString = prefs.getString(GeneralPreferences.KEY_DEFAULT_REMINDER, "0"); 959 mDefaultReminderMinutes = Integer.parseInt(durationString); 960 961 int numReminders = 0; 962 if (model.mHasAlarm) { 963 ArrayList<Integer> minutes = model.mReminderMinutes; 964 numReminders = minutes.size(); 965 for (Integer minute : minutes) { 966 EventViewUtils.addMinutesToList( 967 mActivity, mReminderValues, mReminderLabels, minute); 968 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 969 mReminderValues, mReminderLabels, minute); 970 } 971 } 972 973 ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add); 974 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { 975 public void onClick(View v) { 976 addReminder(); 977 } 978 }; 979 reminderAddButton.setOnClickListener(addReminderOnClickListener); 980 updateRemindersVisibility(numReminders); 981 982 mTitleTextView.setText(model.mTitle); 983 if (model.mIsOrganizer || TextUtils.isEmpty(model.mOrganizer) 984 || model.mOrganizer.endsWith(GOOGLE_SECONDARY_CALENDAR)) { 985 mView.findViewById(R.id.organizer_label).setVisibility(View.GONE); 986 mView.findViewById(R.id.organizer).setVisibility(View.GONE); 987 } else { 988 ((TextView) mView.findViewById(R.id.organizer)).setText(model.mOrganizerDisplayName); 989 } 990 mLocationTextView.setText(model.mLocation); 991 mDescriptionTextView.setText(model.mDescription); 992 mTransparencySpinner.setSelection(model.mTransparency ? 1 : 0); 993 mVisibilitySpinner.setSelection(model.mVisibility); 994 995 View responseLabel = mView.findViewById(R.id.response_label); 996 if (canRespond) { 997 int buttonToCheck = EventInfoFragment 998 .findButtonIdForResponse(model.mSelfAttendeeStatus); 999 mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons 1000 mResponseRadioGroup.setVisibility(View.VISIBLE); 1001 responseLabel.setVisibility(View.VISIBLE); 1002 } else { 1003 responseLabel.setVisibility(View.GONE); 1004 mResponseRadioGroup.setVisibility(View.GONE); 1005 } 1006 1007 if (model.mUri != null) { 1008 // This is an existing event so hide the calendar spinner 1009 // since we can't change the calendar. 1010 View calendarGroup = mView.findViewById(R.id.calendar_group); 1011 calendarGroup.setVisibility(View.GONE); 1012 } else { 1013 mDeleteButton.setVisibility(View.GONE); 1014 } 1015 1016 populateWhen(); 1017 populateTimezone(); 1018 populateRepeats(); 1019 updateAttendees(model.mAttendeesList); 1020 1021 // Mark read-only fields as disabled 1022 if (!canModifyEvent) { 1023 for (View v : mViewList) { 1024 v.setEnabled(false); 1025 } 1026 } 1027 mScrollView.setVisibility(View.VISIBLE); 1028 mLoadingMessage.setVisibility(View.GONE); 1029 } 1030 1031 public void setCalendarsCursor(Cursor cursor, boolean userVisible) { 1032 // If there are no syncable calendars, then we cannot allow 1033 // creating a new event. 1034 mCalendarsCursor = cursor; 1035 if (cursor == null || cursor.getCount() == 0) { 1036 // Cancel the "loading calendars" dialog if it exists 1037 if (mSaveAfterQueryComplete) { 1038 mLoadingCalendarsDialog.cancel(); 1039 } 1040 if (!userVisible) { 1041 return; 1042 } 1043 // Create an error message for the user that, when clicked, 1044 // will exit this activity without saving the event. 1045 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 1046 builder.setTitle(R.string.no_syncable_calendars) 1047 .setIcon(android.R.drawable.ic_dialog_alert) 1048 .setMessage(R.string.no_calendars_found) 1049 .setPositiveButton(R.string.add_account, this) 1050 .setNegativeButton(android.R.string.no, this) 1051 .setOnCancelListener(this); 1052 mNoCalendarsDialog = builder.show(); 1053 return; 1054 } 1055 1056 int defaultCalendarPosition = findDefaultCalendarPosition(cursor); 1057 1058 // populate the calendars spinner 1059 CalendarsAdapter adapter = new CalendarsAdapter(mActivity, cursor); 1060 mCalendarsSpinner.setAdapter(adapter); 1061 mCalendarsSpinner.setSelection(defaultCalendarPosition); 1062 1063 // Find user domain and set it to the validator. 1064 // TODO: we may want to update this validator if the user actually picks 1065 // a different calendar. maybe not. depends on what we want for the 1066 // user experience. this may change when we add support for multiple 1067 // accounts, anyway. 1068 if (mModel != null && mModel.mHasAttendeeData 1069 && cursor.moveToPosition(defaultCalendarPosition)) { 1070 String ownEmail = cursor.getString(EditEventHelper.CALENDARS_INDEX_OWNER_ACCOUNT); 1071 if (ownEmail != null) { 1072 String domain = EditEventHelper.extractDomain(ownEmail); 1073 if (domain != null) { 1074 mEmailValidator = new Rfc822Validator(domain); 1075 mAttendeesList.setValidator(mEmailValidator); 1076 } 1077 } 1078 } 1079 if (mSaveAfterQueryComplete) { 1080 mLoadingCalendarsDialog.cancel(); 1081 if (prepareForSave() && fillModelFromUI()) { 1082 int exit = userVisible ? Utils.DONE_EXIT : 0; 1083 mDone.setDoneCode(Utils.DONE_SAVE | exit); 1084 mDone.run(); 1085 } else if (userVisible) { 1086 mDone.setDoneCode(Utils.DONE_EXIT); 1087 mDone.run(); 1088 } else if (Log.isLoggable(TAG, Log.DEBUG)) { 1089 Log.d(TAG, "SetCalendarsCursor:Save failed and unable to exit view"); 1090 } 1091 return; 1092 } 1093 } 1094 1095 public void setModification(int modifyWhich) { 1096 mModification = modifyWhich; 1097 // If we are modifying all the events in a 1098 // series then disable and ignore the date. 1099 if (modifyWhich == Utils.MODIFY_ALL) { 1100 mStartDateButton.setEnabled(false); 1101 mEndDateButton.setEnabled(false); 1102 } else if (modifyWhich == Utils.MODIFY_SELECTED) { 1103 mRepeatsSpinner.setEnabled(false); 1104 } 1105 } 1106 1107 // Find the calendar position in the cursor that matches calendar in 1108 // preference 1109 private int findDefaultCalendarPosition(Cursor calendarsCursor) { 1110 if (calendarsCursor.getCount() <= 0) { 1111 return -1; 1112 } 1113 1114 String defaultCalendar = Utils.getSharedPreference( 1115 mActivity, GeneralPreferences.KEY_DEFAULT_CALENDAR, null); 1116 1117 if (defaultCalendar == null) { 1118 return 0; 1119 } 1120 int calendarsOwnerColumn = calendarsCursor.getColumnIndexOrThrow(Calendars.OWNER_ACCOUNT); 1121 int position = 0; 1122 calendarsCursor.moveToPosition(-1); 1123 while (calendarsCursor.moveToNext()) { 1124 if (defaultCalendar.equals(calendarsCursor.getString(calendarsOwnerColumn))) { 1125 return position; 1126 } 1127 position++; 1128 } 1129 return 0; 1130 } 1131 1132 private void updateAttendees(HashMap<String, Attendee> attendeesList) { 1133 mAttendeesView.setRfc822Validator(mEmailValidator); 1134 mAttendeesView.addAttendees(attendeesList); 1135 } 1136 1137 private void updateRemindersVisibility(int numReminders) { 1138 if (numReminders == 0) { 1139 mRemindersContainer.setVisibility(View.GONE); 1140 } else { 1141 mRemindersContainer.setVisibility(View.VISIBLE); 1142 } 1143 } 1144 1145 private void addReminder() { 1146 // TODO: when adding a new reminder, make it different from the 1147 // last one in the list (if any). 1148 if (mDefaultReminderMinutes == 0) { 1149 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1150 mReminderValues, mReminderLabels, 10 /* minutes */); 1151 } else { 1152 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderItems, 1153 mReminderValues, mReminderLabels, mDefaultReminderMinutes); 1154 } 1155 updateRemindersVisibility(mReminderItems.size()); 1156 mScrollView.fling(REMINDER_FLING_VELOCITY); 1157 } 1158 1159 // From com.google.android.gm.ComposeActivity 1160 private MultiAutoCompleteTextView initMultiAutoCompleteTextView(int res) { 1161 MultiAutoCompleteTextView list = (MultiAutoCompleteTextView) mView.findViewById(res); 1162 list.setAdapter(mAddressAdapter); 1163 list.setTokenizer(new Rfc822Tokenizer()); 1164 list.setValidator(mEmailValidator); 1165 1166 // NOTE: assumes no other filters are set 1167 list.setFilters(sRecipientFilters); 1168 1169 return list; 1170 } 1171 1172 /** 1173 * From com.google.android.gm.ComposeActivity Implements special address 1174 * cleanup rules: The first space key entry following an "@" symbol that is 1175 * followed by any combination of letters and symbols, including one+ dots 1176 * and zero commas, should insert an extra comma (followed by the space). 1177 */ 1178 private static InputFilter[] sRecipientFilters = new InputFilter[] { new Rfc822InputFilter() }; 1179 1180 private void setDate(TextView view, long millis) { 1181 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR 1182 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_MONTH 1183 | DateUtils.FORMAT_ABBREV_WEEKDAY; 1184 1185 // Unfortunately, DateUtils doesn't support a timezone other than the 1186 // default timezone provided by the system, so we have this ugly hack 1187 // here to trick it into formatting our time correctly. In order to 1188 // prevent all sorts of craziness, we synchronize on the TimeZone class 1189 // to prevent other threads from reading an incorrect timezone from 1190 // calls to TimeZone#getDefault() 1191 // TODO fix this if/when DateUtils allows for passing in a timezone 1192 String dateString; 1193 synchronized (TimeZone.class) { 1194 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1195 dateString = DateUtils.formatDateTime(mActivity, millis, flags); 1196 // setting the default back to null restores the correct behavior 1197 TimeZone.setDefault(null); 1198 } 1199 view.setText(dateString); 1200 } 1201 1202 private void setTime(TextView view, long millis) { 1203 int flags = DateUtils.FORMAT_SHOW_TIME; 1204 if (DateFormat.is24HourFormat(mActivity)) { 1205 flags |= DateUtils.FORMAT_24HOUR; 1206 } 1207 1208 // Unfortunately, DateUtils doesn't support a timezone other than the 1209 // default timezone provided by the system, so we have this ugly hack 1210 // here to trick it into formatting our time correctly. In order to 1211 // prevent all sorts of craziness, we synchronize on the TimeZone class 1212 // to prevent other threads from reading an incorrect timezone from 1213 // calls to TimeZone#getDefault() 1214 // TODO fix this if/when DateUtils allows for passing in a timezone 1215 String timeString; 1216 synchronized (TimeZone.class) { 1217 TimeZone.setDefault(TimeZone.getTimeZone(mTimezone)); 1218 timeString = DateUtils.formatDateTime(mActivity, millis, flags); 1219 TimeZone.setDefault(null); 1220 } 1221 view.setText(timeString); 1222 } 1223 1224 private void setTimezone(int i) { 1225 if (i < 0 || i >= mTimezoneAdapter.getCount()) { 1226 return; // do nothing 1227 } 1228 TimezoneRow timezone = mTimezoneAdapter.getItem(i); 1229 mTimezoneButton.setText(timezone.toString()); 1230 mTimezone = timezone.mId; 1231 mStartTime.timezone = mTimezone; 1232 mStartTime.normalize(true); 1233 mEndTime.timezone = mTimezone; 1234 mEndTime.normalize(true); 1235 mTimezoneAdapter.setCurrentTimezone(mTimezone); 1236 } 1237} 1238