EditEventFragment.java revision 96a36f4cb7803d50ecf47945b6a240926f48e7c3
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 android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Fragment; 22import android.app.FragmentManager; 23import android.content.AsyncQueryHandler; 24import android.content.ContentProviderOperation; 25import android.content.ContentResolver; 26import android.content.ContentUris; 27import android.content.ContentValues; 28import android.content.Context; 29import android.content.DialogInterface; 30import android.content.DialogInterface.OnCancelListener; 31import android.content.DialogInterface.OnClickListener; 32import android.content.Intent; 33import android.database.Cursor; 34import android.database.MatrixCursor; 35import android.net.Uri; 36import android.os.Bundle; 37import android.provider.CalendarContract.Attendees; 38import android.provider.CalendarContract.Calendars; 39import android.provider.CalendarContract.Colors; 40import android.provider.CalendarContract.Events; 41import android.provider.CalendarContract.Reminders; 42import android.text.TextUtils; 43import android.text.format.Time; 44import android.util.Log; 45import android.view.LayoutInflater; 46import android.view.Menu; 47import android.view.MenuInflater; 48import android.view.MenuItem; 49import android.view.View; 50import android.view.ViewGroup; 51import android.view.inputmethod.InputMethodManager; 52import android.widget.LinearLayout; 53import android.widget.Toast; 54 55import com.android.calendar.AsyncQueryService; 56import com.android.calendar.CalendarController; 57import com.android.calendar.CalendarController.EventHandler; 58import com.android.calendar.CalendarController.EventInfo; 59import com.android.calendar.CalendarController.EventType; 60import com.android.calendar.CalendarEventModel; 61import com.android.calendar.CalendarEventModel.Attendee; 62import com.android.calendar.CalendarEventModel.ReminderEntry; 63import com.android.calendar.DeleteEventHelper; 64import com.android.calendar.R; 65import com.android.calendar.Utils; 66import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; 67import com.android.colorpicker.HsvColorComparator; 68 69import java.io.Serializable; 70import java.util.ArrayList; 71import java.util.Collections; 72 73public class EditEventFragment extends Fragment implements EventHandler, OnColorSelectedListener { 74 private static final String TAG = "EditEventActivity"; 75 private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog"; 76 77 private static final int REQUEST_CODE_COLOR_PICKER = 0; 78 79 private static final String BUNDLE_KEY_MODEL = "key_model"; 80 private static final String BUNDLE_KEY_EDIT_STATE = "key_edit_state"; 81 private static final String BUNDLE_KEY_EVENT = "key_event"; 82 private static final String BUNDLE_KEY_READ_ONLY = "key_read_only"; 83 private static final String BUNDLE_KEY_EDIT_ON_LAUNCH = "key_edit_on_launch"; 84 private static final String BUNDLE_KEY_SHOW_COLOR_PALETTE = "show_color_palette"; 85 86 private static final String BUNDLE_KEY_DATE_BUTTON_CLICKED = "date_button_clicked"; 87 88 private static final boolean DEBUG = false; 89 90 private static final int TOKEN_EVENT = 1; 91 private static final int TOKEN_ATTENDEES = 1 << 1; 92 private static final int TOKEN_REMINDERS = 1 << 2; 93 private static final int TOKEN_CALENDARS = 1 << 3; 94 private static final int TOKEN_COLORS = 1 << 4; 95 96 private static final int TOKEN_ALL = TOKEN_EVENT | TOKEN_ATTENDEES | TOKEN_REMINDERS 97 | TOKEN_CALENDARS | TOKEN_COLORS; 98 private static final int TOKEN_UNITIALIZED = 1 << 31; 99 100 /** 101 * A bitfield of TOKEN_* to keep track which query hasn't been completed 102 * yet. Once all queries have returned, the model can be applied to the 103 * view. 104 */ 105 private int mOutstandingQueries = TOKEN_UNITIALIZED; 106 107 EditEventHelper mHelper; 108 CalendarEventModel mModel; 109 CalendarEventModel mOriginalModel; 110 CalendarEventModel mRestoreModel; 111 EditEventView mView; 112 QueryHandler mHandler; 113 114 private AlertDialog mModifyDialog; 115 int mModification = Utils.MODIFY_UNINITIALIZED; 116 117 private final EventInfo mEvent; 118 private EventBundle mEventBundle; 119 private ArrayList<ReminderEntry> mReminders; 120 private int mEventColor; 121 private boolean mEventColorInitialized = false; 122 private Uri mUri; 123 private long mBegin; 124 private long mEnd; 125 private long mCalendarId = -1; 126 127 private EventColorPickerDialog mColorPickerDialog; 128 129 private Activity mContext; 130 private final Done mOnDone = new Done(); 131 132 private boolean mSaveOnDetach = true; 133 private boolean mIsReadOnly = false; 134 public boolean mShowModifyDialogOnLaunch = false; 135 private boolean mShowColorPalette = false; 136 137 private boolean mTimeSelectedWasStartTime; 138 private boolean mDateSelectedWasStartDate; 139 140 private InputMethodManager mInputMethodManager; 141 142 private final Intent mIntent; 143 144 private boolean mUseCustomActionBar; 145 146 private final View.OnClickListener mActionBarListener = new View.OnClickListener() { 147 @Override 148 public void onClick(View v) { 149 onActionBarItemSelected(v.getId()); 150 } 151 }; 152 153 // TODO turn this into a helper function in EditEventHelper for building the 154 // model 155 private class QueryHandler extends AsyncQueryHandler { 156 public QueryHandler(ContentResolver cr) { 157 super(cr); 158 } 159 160 @Override 161 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 162 // If the query didn't return a cursor for some reason return 163 if (cursor == null) { 164 return; 165 } 166 167 // If the Activity is finishing, then close the cursor. 168 // Otherwise, use the new cursor in the adapter. 169 final Activity activity = EditEventFragment.this.getActivity(); 170 if (activity == null || activity.isFinishing()) { 171 cursor.close(); 172 return; 173 } 174 long eventId; 175 switch (token) { 176 case TOKEN_EVENT: 177 if (cursor.getCount() == 0) { 178 // The cursor is empty. This can happen if the event 179 // was deleted. 180 cursor.close(); 181 mOnDone.setDoneCode(Utils.DONE_EXIT); 182 mSaveOnDetach = false; 183 mOnDone.run(); 184 return; 185 } 186 mOriginalModel = new CalendarEventModel(); 187 EditEventHelper.setModelFromCursor(mOriginalModel, cursor); 188 EditEventHelper.setModelFromCursor(mModel, cursor); 189 cursor.close(); 190 191 mOriginalModel.mUri = mUri.toString(); 192 193 mModel.mUri = mUri.toString(); 194 mModel.mOriginalStart = mBegin; 195 mModel.mOriginalEnd = mEnd; 196 mModel.mIsFirstEventInSeries = mBegin == mOriginalModel.mStart; 197 mModel.mStart = mBegin; 198 mModel.mEnd = mEnd; 199 if (mEventColorInitialized) { 200 mModel.setEventColor(mEventColor); 201 } 202 eventId = mModel.mId; 203 204 // TOKEN_ATTENDEES 205 if (mModel.mHasAttendeeData && eventId != -1) { 206 Uri attUri = Attendees.CONTENT_URI; 207 String[] whereArgs = { 208 Long.toString(eventId) 209 }; 210 mHandler.startQuery(TOKEN_ATTENDEES, null, attUri, 211 EditEventHelper.ATTENDEES_PROJECTION, 212 EditEventHelper.ATTENDEES_WHERE /* selection */, 213 whereArgs /* selection args */, null /* sort order */); 214 } else { 215 setModelIfDone(TOKEN_ATTENDEES); 216 } 217 218 // TOKEN_REMINDERS 219 if (mModel.mHasAlarm && mReminders == null) { 220 Uri rUri = Reminders.CONTENT_URI; 221 String[] remArgs = { 222 Long.toString(eventId) 223 }; 224 mHandler.startQuery(TOKEN_REMINDERS, null, rUri, 225 EditEventHelper.REMINDERS_PROJECTION, 226 EditEventHelper.REMINDERS_WHERE /* selection */, 227 remArgs /* selection args */, null /* sort order */); 228 } else { 229 if (mReminders == null) { 230 // mReminders should not be null. 231 mReminders = new ArrayList<ReminderEntry>(); 232 } else { 233 Collections.sort(mReminders); 234 } 235 mOriginalModel.mReminders = mReminders; 236 mModel.mReminders = 237 (ArrayList<ReminderEntry>) mReminders.clone(); 238 setModelIfDone(TOKEN_REMINDERS); 239 } 240 241 // TOKEN_CALENDARS 242 String[] selArgs = { 243 Long.toString(mModel.mCalendarId) 244 }; 245 mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, 246 EditEventHelper.CALENDARS_PROJECTION, EditEventHelper.CALENDARS_WHERE, 247 selArgs /* selection args */, null /* sort order */); 248 249 setModelIfDone(TOKEN_EVENT); 250 break; 251 case TOKEN_ATTENDEES: 252 try { 253 while (cursor.moveToNext()) { 254 String name = cursor.getString(EditEventHelper.ATTENDEES_INDEX_NAME); 255 String email = cursor.getString(EditEventHelper.ATTENDEES_INDEX_EMAIL); 256 int status = cursor.getInt(EditEventHelper.ATTENDEES_INDEX_STATUS); 257 int relationship = cursor 258 .getInt(EditEventHelper.ATTENDEES_INDEX_RELATIONSHIP); 259 if (relationship == Attendees.RELATIONSHIP_ORGANIZER) { 260 if (email != null) { 261 mModel.mOrganizer = email; 262 mModel.mIsOrganizer = mModel.mOwnerAccount 263 .equalsIgnoreCase(email); 264 mOriginalModel.mOrganizer = email; 265 mOriginalModel.mIsOrganizer = mOriginalModel.mOwnerAccount 266 .equalsIgnoreCase(email); 267 } 268 269 if (TextUtils.isEmpty(name)) { 270 mModel.mOrganizerDisplayName = mModel.mOrganizer; 271 mOriginalModel.mOrganizerDisplayName = 272 mOriginalModel.mOrganizer; 273 } else { 274 mModel.mOrganizerDisplayName = name; 275 mOriginalModel.mOrganizerDisplayName = name; 276 } 277 } 278 279 if (email != null) { 280 if (mModel.mOwnerAccount != null && 281 mModel.mOwnerAccount.equalsIgnoreCase(email)) { 282 int attendeeId = 283 cursor.getInt(EditEventHelper.ATTENDEES_INDEX_ID); 284 mModel.mOwnerAttendeeId = attendeeId; 285 mModel.mSelfAttendeeStatus = status; 286 mOriginalModel.mOwnerAttendeeId = attendeeId; 287 mOriginalModel.mSelfAttendeeStatus = status; 288 continue; 289 } 290 } 291 Attendee attendee = new Attendee(name, email); 292 attendee.mStatus = status; 293 mModel.addAttendee(attendee); 294 mOriginalModel.addAttendee(attendee); 295 } 296 } finally { 297 cursor.close(); 298 } 299 300 setModelIfDone(TOKEN_ATTENDEES); 301 break; 302 case TOKEN_REMINDERS: 303 try { 304 // Add all reminders to the models 305 while (cursor.moveToNext()) { 306 int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES); 307 int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD); 308 ReminderEntry re = ReminderEntry.valueOf(minutes, method); 309 mModel.mReminders.add(re); 310 mOriginalModel.mReminders.add(re); 311 } 312 313 // Sort appropriately for display 314 Collections.sort(mModel.mReminders); 315 Collections.sort(mOriginalModel.mReminders); 316 } finally { 317 cursor.close(); 318 } 319 320 setModelIfDone(TOKEN_REMINDERS); 321 break; 322 case TOKEN_CALENDARS: 323 try { 324 if (mModel.mId == -1) { 325 // Populate Calendar spinner only if no event id is set. 326 MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); 327 if (DEBUG) { 328 Log.d(TAG, "onQueryComplete: setting cursor with " 329 + matrixCursor.getCount() + " calendars"); 330 } 331 mView.setCalendarsCursor(matrixCursor, isAdded() && isResumed(), 332 mCalendarId); 333 } else { 334 // Populate model for an existing event 335 EditEventHelper.setModelFromCalendarCursor(mModel, cursor); 336 EditEventHelper.setModelFromCalendarCursor(mOriginalModel, cursor); 337 } 338 startQuery(TOKEN_COLORS, null, Colors.CONTENT_URI, 339 EditEventHelper.COLORS_PROJECTION, 340 Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT, null, null); 341 } finally { 342 cursor.close(); 343 } 344 setModelIfDone(TOKEN_CALENDARS); 345 break; 346 case TOKEN_COLORS: 347 if (cursor.moveToFirst()) { 348 EventColorCache cache = new EventColorCache(); 349 do 350 { 351 int colorKey = cursor.getInt(EditEventHelper.COLORS_INDEX_COLOR_KEY); 352 int rawColor = cursor.getInt(EditEventHelper.COLORS_INDEX_COLOR); 353 int displayColor = Utils.getDisplayColorFromColor(rawColor); 354 String accountName = cursor 355 .getString(EditEventHelper.COLORS_INDEX_ACCOUNT_NAME); 356 String accountType = cursor 357 .getString(EditEventHelper.COLORS_INDEX_ACCOUNT_TYPE); 358 cache.insertColor(accountName, accountType, 359 displayColor, colorKey); 360 } while (cursor.moveToNext()); 361 cache.sortPalettes(new HsvColorComparator()); 362 363 mModel.mEventColorCache = cache; 364 mView.mColorPickerNewEvent.setOnClickListener(mOnColorPickerClicked); 365 mView.mColorPickerExistingEvent.setOnClickListener(mOnColorPickerClicked); 366 } 367 if (cursor != null) { 368 cursor.close(); 369 } 370 371 // If the account name/type is null, the calendar event colors cannot be 372 // determined, so take the default/savedInstanceState value. 373 if (mModel.mCalendarAccountName == null 374 || mModel.mCalendarAccountType == null) { 375 mView.setColorPickerButtonStates(mShowColorPalette); 376 } else { 377 mView.setColorPickerButtonStates(mModel.getCalendarEventColors()); 378 } 379 380 setModelIfDone(TOKEN_COLORS); 381 break; 382 default: 383 cursor.close(); 384 break; 385 } 386 } 387 } 388 389 private View.OnClickListener mOnColorPickerClicked = new View.OnClickListener() { 390 391 @Override 392 public void onClick(View v) { 393 int[] colors = mModel.getCalendarEventColors(); 394 if (mColorPickerDialog == null) { 395 mColorPickerDialog = EventColorPickerDialog.newInstance(colors, 396 mModel.getEventColor(), mModel.getCalendarColor(), mView.mIsMultipane); 397 mColorPickerDialog.setOnColorSelectedListener(EditEventFragment.this); 398 } else { 399 mColorPickerDialog.setCalendarColor(mModel.getCalendarColor()); 400 mColorPickerDialog.setColors(colors, mModel.getEventColor()); 401 } 402 final FragmentManager fragmentManager = getFragmentManager(); 403 fragmentManager.executePendingTransactions(); 404 if (!mColorPickerDialog.isAdded()) { 405 mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); 406 } 407 } 408 }; 409 410 private void setModelIfDone(int queryType) { 411 synchronized (this) { 412 mOutstandingQueries &= ~queryType; 413 if (mOutstandingQueries == 0) { 414 if (mRestoreModel != null) { 415 mModel = mRestoreModel; 416 } 417 if (mShowModifyDialogOnLaunch && mModification == Utils.MODIFY_UNINITIALIZED) { 418 if (!TextUtils.isEmpty(mModel.mRrule)) { 419 displayEditWhichDialog(); 420 } else { 421 mModification = Utils.MODIFY_ALL; 422 } 423 424 } 425 mView.setModel(mModel); 426 mView.setModification(mModification); 427 } 428 } 429 } 430 431 public EditEventFragment() { 432 this(null, null, false, -1, false, null); 433 } 434 435 public EditEventFragment(EventInfo event, ArrayList<ReminderEntry> reminders, 436 boolean eventColorInitialized, int eventColor, boolean readOnly, Intent intent) { 437 mEvent = event; 438 mIsReadOnly = readOnly; 439 mIntent = intent; 440 441 mReminders = reminders; 442 mEventColorInitialized = eventColorInitialized; 443 if (eventColorInitialized) { 444 mEventColor = eventColor; 445 } 446 setHasOptionsMenu(true); 447 } 448 449 @Override 450 public void onActivityCreated(Bundle savedInstanceState) { 451 super.onActivityCreated(savedInstanceState); 452 mColorPickerDialog = (EventColorPickerDialog) getActivity().getFragmentManager() 453 .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); 454 if (mColorPickerDialog != null) { 455 mColorPickerDialog.setOnColorSelectedListener(this); 456 } 457 } 458 459 private void startQuery() { 460 mUri = null; 461 mBegin = -1; 462 mEnd = -1; 463 if (mEvent != null) { 464 if (mEvent.id != -1) { 465 mModel.mId = mEvent.id; 466 mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEvent.id); 467 } else { 468 // New event. All day? 469 mModel.mAllDay = mEvent.extraLong == CalendarController.EXTRA_CREATE_ALL_DAY; 470 } 471 if (mEvent.startTime != null) { 472 mBegin = mEvent.startTime.toMillis(true); 473 } 474 if (mEvent.endTime != null) { 475 mEnd = mEvent.endTime.toMillis(true); 476 } 477 if (mEvent.calendarId != -1) { 478 mCalendarId = mEvent.calendarId; 479 } 480 } else if (mEventBundle != null) { 481 if (mEventBundle.id != -1) { 482 mModel.mId = mEventBundle.id; 483 mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventBundle.id); 484 } 485 mBegin = mEventBundle.start; 486 mEnd = mEventBundle.end; 487 } 488 489 if (mReminders != null) { 490 mModel.mReminders = mReminders; 491 } 492 493 if (mEventColorInitialized) { 494 mModel.setEventColor(mEventColor); 495 } 496 497 if (mBegin <= 0) { 498 // use a default value instead 499 mBegin = mHelper.constructDefaultStartTime(System.currentTimeMillis()); 500 } 501 if (mEnd < mBegin) { 502 // use a default value instead 503 mEnd = mHelper.constructDefaultEndTime(mBegin); 504 } 505 506 // Kick off the query for the event 507 boolean newEvent = mUri == null; 508 if (!newEvent) { 509 mModel.mCalendarAccessLevel = Calendars.CAL_ACCESS_NONE; 510 mOutstandingQueries = TOKEN_ALL; 511 if (DEBUG) { 512 Log.d(TAG, "startQuery: uri for event is " + mUri.toString()); 513 } 514 mHandler.startQuery(TOKEN_EVENT, null, mUri, EditEventHelper.EVENT_PROJECTION, 515 null /* selection */, null /* selection args */, null /* sort order */); 516 } else { 517 mOutstandingQueries = TOKEN_CALENDARS | TOKEN_COLORS; 518 if (DEBUG) { 519 Log.d(TAG, "startQuery: Editing a new event."); 520 } 521 mModel.mOriginalStart = mBegin; 522 mModel.mOriginalEnd = mEnd; 523 mModel.mStart = mBegin; 524 mModel.mEnd = mEnd; 525 mModel.mCalendarId = mCalendarId; 526 mModel.mSelfAttendeeStatus = Attendees.ATTENDEE_STATUS_ACCEPTED; 527 528 // Start a query in the background to read the list of calendars 529 mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI, 530 EditEventHelper.CALENDARS_PROJECTION, 531 EditEventHelper.CALENDARS_WHERE_WRITEABLE_VISIBLE, null /* selection args */, 532 null /* sort order */); 533 534 mModification = Utils.MODIFY_ALL; 535 mView.setModification(mModification); 536 } 537 } 538 539 @Override 540 public void onAttach(Activity activity) { 541 super.onAttach(activity); 542 mContext = activity; 543 544 mHelper = new EditEventHelper(activity, null); 545 mHandler = new QueryHandler(activity.getContentResolver()); 546 mModel = new CalendarEventModel(activity, mIntent); 547 mInputMethodManager = (InputMethodManager) 548 activity.getSystemService(Context.INPUT_METHOD_SERVICE); 549 550 mUseCustomActionBar = !Utils.getConfigBool(mContext, R.bool.multiple_pane_config); 551 } 552 553 @Override 554 public View onCreateView(LayoutInflater inflater, ViewGroup container, 555 Bundle savedInstanceState) { 556// mContext.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 557 View view; 558 if (mIsReadOnly) { 559 view = inflater.inflate(R.layout.edit_event_single_column, null); 560 } else { 561 view = inflater.inflate(R.layout.edit_event, null); 562 } 563 mView = new EditEventView(mContext, view, mOnDone, mTimeSelectedWasStartTime, 564 mDateSelectedWasStartDate); 565 startQuery(); 566 567 if (mUseCustomActionBar) { 568 View actionBarButtons = inflater.inflate(R.layout.edit_event_custom_actionbar, 569 new LinearLayout(mContext), false); 570 View cancelActionView = actionBarButtons.findViewById(R.id.action_cancel); 571 cancelActionView.setOnClickListener(mActionBarListener); 572 View doneActionView = actionBarButtons.findViewById(R.id.action_done); 573 doneActionView.setOnClickListener(mActionBarListener); 574 575 mContext.getActionBar().setCustomView(actionBarButtons); 576 } 577 578 return view; 579 } 580 581 @Override 582 public void onDestroyView() { 583 super.onDestroyView(); 584 585 if (mUseCustomActionBar) { 586 mContext.getActionBar().setCustomView(null); 587 } 588 } 589 590 @Override 591 public void onCreate(Bundle savedInstanceState) { 592 super.onCreate(savedInstanceState); 593 if (savedInstanceState != null) { 594 if (savedInstanceState.containsKey(BUNDLE_KEY_MODEL)) { 595 mRestoreModel = (CalendarEventModel) savedInstanceState.getSerializable( 596 BUNDLE_KEY_MODEL); 597 } 598 if (savedInstanceState.containsKey(BUNDLE_KEY_EDIT_STATE)) { 599 mModification = savedInstanceState.getInt(BUNDLE_KEY_EDIT_STATE); 600 } 601 if (savedInstanceState.containsKey(BUNDLE_KEY_EDIT_ON_LAUNCH)) { 602 mShowModifyDialogOnLaunch = savedInstanceState 603 .getBoolean(BUNDLE_KEY_EDIT_ON_LAUNCH); 604 } 605 if (savedInstanceState.containsKey(BUNDLE_KEY_EVENT)) { 606 mEventBundle = (EventBundle) savedInstanceState.getSerializable(BUNDLE_KEY_EVENT); 607 } 608 if (savedInstanceState.containsKey(BUNDLE_KEY_READ_ONLY)) { 609 mIsReadOnly = savedInstanceState.getBoolean(BUNDLE_KEY_READ_ONLY); 610 } 611 if (savedInstanceState.containsKey("EditEventView_timebuttonclicked")) { 612 mTimeSelectedWasStartTime = savedInstanceState.getBoolean( 613 "EditEventView_timebuttonclicked"); 614 } 615 if (savedInstanceState.containsKey(BUNDLE_KEY_DATE_BUTTON_CLICKED)) { 616 mDateSelectedWasStartDate = savedInstanceState.getBoolean( 617 BUNDLE_KEY_DATE_BUTTON_CLICKED); 618 } 619 if (savedInstanceState.containsKey(BUNDLE_KEY_SHOW_COLOR_PALETTE)) { 620 mShowColorPalette = savedInstanceState.getBoolean(BUNDLE_KEY_SHOW_COLOR_PALETTE); 621 } 622 623 } 624 } 625 626 627 @Override 628 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 629 super.onCreateOptionsMenu(menu, inflater); 630 631 if (!mUseCustomActionBar) { 632 inflater.inflate(R.menu.edit_event_title_bar, menu); 633 } 634 } 635 636 @Override 637 public boolean onOptionsItemSelected(MenuItem item) { 638 return onActionBarItemSelected(item.getItemId()); 639 } 640 641 /** 642 * Handles menu item selections, whether they come from our custom action bar buttons or from 643 * the standard menu items. Depends on the menu item ids matching the custom action bar button 644 * ids. 645 * 646 * @param itemId the button or menu item id 647 * @return whether the event was handled here 648 */ 649 private boolean onActionBarItemSelected(int itemId) { 650 if (itemId == R.id.action_done) { 651 if (EditEventHelper.canModifyEvent(mModel) || EditEventHelper.canRespond(mModel)) { 652 if (mView != null && mView.prepareForSave()) { 653 if (mModification == Utils.MODIFY_UNINITIALIZED) { 654 mModification = Utils.MODIFY_ALL; 655 } 656 mOnDone.setDoneCode(Utils.DONE_SAVE | Utils.DONE_EXIT); 657 mOnDone.run(); 658 } else { 659 mOnDone.setDoneCode(Utils.DONE_REVERT); 660 mOnDone.run(); 661 } 662 } else if (EditEventHelper.canAddReminders(mModel) && mModel.mId != -1 663 && mOriginalModel != null && mView.prepareForSave()) { 664 saveReminders(); 665 mOnDone.setDoneCode(Utils.DONE_EXIT); 666 mOnDone.run(); 667 } else { 668 mOnDone.setDoneCode(Utils.DONE_REVERT); 669 mOnDone.run(); 670 } 671 } else if (itemId == R.id.action_cancel) { 672 mOnDone.setDoneCode(Utils.DONE_REVERT); 673 mOnDone.run(); 674 } 675 return true; 676 } 677 678 private void saveReminders() { 679 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); 680 boolean changed = EditEventHelper.saveReminders(ops, mModel.mId, mModel.mReminders, 681 mOriginalModel.mReminders, false /* no force save */); 682 683 if (!changed) { 684 return; 685 } 686 687 AsyncQueryService service = new AsyncQueryService(getActivity()); 688 service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0); 689 // Update the "hasAlarm" field for the event 690 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mModel.mId); 691 int len = mModel.mReminders.size(); 692 boolean hasAlarm = len > 0; 693 if (hasAlarm != mOriginalModel.mHasAlarm) { 694 ContentValues values = new ContentValues(); 695 values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0); 696 service.startUpdate(0, null, uri, values, null, null, 0); 697 } 698 699 Toast.makeText(mContext, R.string.saving_event, Toast.LENGTH_SHORT).show(); 700 } 701 702 protected void displayEditWhichDialog() { 703 if (mModification == Utils.MODIFY_UNINITIALIZED) { 704 final boolean notSynced = TextUtils.isEmpty(mModel.mSyncId); 705 boolean isFirstEventInSeries = mModel.mIsFirstEventInSeries; 706 int itemIndex = 0; 707 CharSequence[] items; 708 709 if (notSynced) { 710 // If this event has not been synced, then don't allow deleting 711 // or changing a single instance. 712 if (isFirstEventInSeries) { 713 // Still display the option so the user knows all events are 714 // changing 715 items = new CharSequence[1]; 716 } else { 717 items = new CharSequence[2]; 718 } 719 } else { 720 if (isFirstEventInSeries) { 721 items = new CharSequence[2]; 722 } else { 723 items = new CharSequence[3]; 724 } 725 items[itemIndex++] = mContext.getText(R.string.modify_event); 726 } 727 items[itemIndex++] = mContext.getText(R.string.modify_all); 728 729 // Do one more check to make sure this remains at the end of the list 730 if (!isFirstEventInSeries) { 731 items[itemIndex++] = mContext.getText(R.string.modify_all_following); 732 } 733 734 // Display the modification dialog. 735 if (mModifyDialog != null) { 736 mModifyDialog.dismiss(); 737 mModifyDialog = null; 738 } 739 mModifyDialog = new AlertDialog.Builder(mContext).setTitle(R.string.edit_event_label) 740 .setItems(items, new OnClickListener() { 741 @Override 742 public void onClick(DialogInterface dialog, int which) { 743 if (which == 0) { 744 // Update this if we start allowing exceptions 745 // to unsynced events in the app 746 mModification = notSynced ? Utils.MODIFY_ALL 747 : Utils.MODIFY_SELECTED; 748 if (mModification == Utils.MODIFY_SELECTED) { 749 mModel.mOriginalSyncId = notSynced ? null : mModel.mSyncId; 750 mModel.mOriginalId = mModel.mId; 751 } 752 } else if (which == 1) { 753 mModification = notSynced ? Utils.MODIFY_ALL_FOLLOWING 754 : Utils.MODIFY_ALL; 755 } else if (which == 2) { 756 mModification = Utils.MODIFY_ALL_FOLLOWING; 757 } 758 759 mView.setModification(mModification); 760 } 761 }).show(); 762 763 mModifyDialog.setOnCancelListener(new OnCancelListener() { 764 @Override 765 public void onCancel(DialogInterface dialog) { 766 Activity a = EditEventFragment.this.getActivity(); 767 if (a != null) { 768 a.finish(); 769 } 770 } 771 }); 772 } 773 } 774 775 class Done implements EditEventHelper.EditDoneRunnable { 776 private int mCode = -1; 777 778 @Override 779 public void setDoneCode(int code) { 780 mCode = code; 781 } 782 783 @Override 784 public void run() { 785 // We only want this to get called once, either because the user 786 // pressed back/home or one of the buttons on screen 787 mSaveOnDetach = false; 788 if (mModification == Utils.MODIFY_UNINITIALIZED) { 789 // If this is uninitialized the user hit back, the only 790 // changeable item is response to default to all events. 791 mModification = Utils.MODIFY_ALL; 792 } 793 794 if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null 795 && (EditEventHelper.canRespond(mModel) 796 || EditEventHelper.canModifyEvent(mModel)) 797 && mView.prepareForSave() 798 && !isEmptyNewEvent() 799 && mModel.normalizeReminders() 800 && mHelper.saveEvent(mModel, mOriginalModel, mModification)) { 801 int stringResource; 802 if (!mModel.mAttendeesList.isEmpty()) { 803 if (mModel.mUri != null) { 804 stringResource = R.string.saving_event_with_guest; 805 } else { 806 stringResource = R.string.creating_event_with_guest; 807 } 808 } else { 809 if (mModel.mUri != null) { 810 stringResource = R.string.saving_event; 811 } else { 812 stringResource = R.string.creating_event; 813 } 814 } 815 Toast.makeText(mContext, stringResource, Toast.LENGTH_SHORT).show(); 816 } else if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null && isEmptyNewEvent()) { 817 Toast.makeText(mContext, R.string.empty_event, Toast.LENGTH_SHORT).show(); 818 } 819 820 if ((mCode & Utils.DONE_DELETE) != 0 && mOriginalModel != null 821 && EditEventHelper.canModifyCalendar(mOriginalModel)) { 822 long begin = mModel.mStart; 823 long end = mModel.mEnd; 824 int which = -1; 825 switch (mModification) { 826 case Utils.MODIFY_SELECTED: 827 which = DeleteEventHelper.DELETE_SELECTED; 828 break; 829 case Utils.MODIFY_ALL_FOLLOWING: 830 which = DeleteEventHelper.DELETE_ALL_FOLLOWING; 831 break; 832 case Utils.MODIFY_ALL: 833 which = DeleteEventHelper.DELETE_ALL; 834 break; 835 } 836 DeleteEventHelper deleteHelper = new DeleteEventHelper( 837 mContext, mContext, !mIsReadOnly /* exitWhenDone */); 838 deleteHelper.delete(begin, end, mOriginalModel, which); 839 } 840 841 if ((mCode & Utils.DONE_EXIT) != 0) { 842 // This will exit the edit event screen, should be called 843 // when we want to return to the main calendar views 844 if ((mCode & Utils.DONE_SAVE) != 0) { 845 if (mContext != null) { 846 long start = mModel.mStart; 847 long end = mModel.mEnd; 848 if (mModel.mAllDay) { 849 // For allday events we want to go to the day in the 850 // user's current tz 851 String tz = Utils.getTimeZone(mContext, null); 852 Time t = new Time(Time.TIMEZONE_UTC); 853 t.set(start); 854 t.timezone = tz; 855 start = t.toMillis(true); 856 857 t.timezone = Time.TIMEZONE_UTC; 858 t.set(end); 859 t.timezone = tz; 860 end = t.toMillis(true); 861 } 862 CalendarController.getInstance(mContext).launchViewEvent(-1, start, end, 863 Attendees.ATTENDEE_STATUS_NONE); 864 } 865 } 866 Activity a = EditEventFragment.this.getActivity(); 867 if (a != null) { 868 a.finish(); 869 } 870 } 871 872 // Hide a software keyboard so that user won't see it even after this Fragment's 873 // disappearing. 874 final View focusedView = mContext.getCurrentFocus(); 875 if (focusedView != null) { 876 mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); 877 focusedView.clearFocus(); 878 } 879 } 880 } 881 882 boolean isEmptyNewEvent() { 883 if (mOriginalModel != null) { 884 // Not new 885 return false; 886 } 887 888 if (mModel.mOriginalStart != mModel.mStart || mModel.mOriginalEnd != mModel.mEnd) { 889 return false; 890 } 891 892 if (!mModel.mAttendeesList.isEmpty()) { 893 return false; 894 } 895 896 return mModel.isEmpty(); 897 } 898 899 @Override 900 public void onPause() { 901 Activity act = getActivity(); 902 if (mSaveOnDetach && act != null && !mIsReadOnly && !act.isChangingConfigurations() 903 && mView.prepareForSave()) { 904 mOnDone.setDoneCode(Utils.DONE_SAVE); 905 mOnDone.run(); 906 } 907 super.onPause(); 908 } 909 910 @Override 911 public void onDestroy() { 912 if (mView != null) { 913 mView.setModel(null); 914 } 915 if (mModifyDialog != null) { 916 mModifyDialog.dismiss(); 917 mModifyDialog = null; 918 } 919 super.onDestroy(); 920 } 921 922 @Override 923 public void eventsChanged() { 924 // TODO Requery to see if event has changed 925 } 926 927 @Override 928 public void onSaveInstanceState(Bundle outState) { 929 mView.prepareForSave(); 930 outState.putSerializable(BUNDLE_KEY_MODEL, mModel); 931 outState.putInt(BUNDLE_KEY_EDIT_STATE, mModification); 932 if (mEventBundle == null && mEvent != null) { 933 mEventBundle = new EventBundle(); 934 mEventBundle.id = mEvent.id; 935 if (mEvent.startTime != null) { 936 mEventBundle.start = mEvent.startTime.toMillis(true); 937 } 938 if (mEvent.endTime != null) { 939 mEventBundle.end = mEvent.startTime.toMillis(true); 940 } 941 } 942 outState.putBoolean(BUNDLE_KEY_EDIT_ON_LAUNCH, mShowModifyDialogOnLaunch); 943 outState.putSerializable(BUNDLE_KEY_EVENT, mEventBundle); 944 outState.putBoolean(BUNDLE_KEY_READ_ONLY, mIsReadOnly); 945 outState.putBoolean(BUNDLE_KEY_SHOW_COLOR_PALETTE, mView.isColorPaletteVisible()); 946 947 outState.putBoolean("EditEventView_timebuttonclicked", mView.mTimeSelectedWasStartTime); 948 outState.putBoolean(BUNDLE_KEY_DATE_BUTTON_CLICKED, mView.mDateSelectedWasStartDate); 949 } 950 951 @Override 952 public long getSupportedEventTypes() { 953 return EventType.USER_HOME; 954 } 955 956 @Override 957 public void handleEvent(EventInfo event) { 958 // It's currently unclear if we want to save the event or not when home 959 // is pressed. When creating a new event we shouldn't save since we 960 // can't get the id of the new event easily. 961 if ((false && event.eventType == EventType.USER_HOME) || (event.eventType == EventType.GO_TO 962 && mSaveOnDetach)) { 963 if (mView != null && mView.prepareForSave()) { 964 mOnDone.setDoneCode(Utils.DONE_SAVE); 965 mOnDone.run(); 966 } 967 } 968 } 969 970 private static class EventBundle implements Serializable { 971 private static final long serialVersionUID = 1L; 972 long id = -1; 973 long start = -1; 974 long end = -1; 975 } 976 977 @Override 978 public void onColorSelected(int color) { 979 if (!mModel.isEventColorInitialized() || mModel.getEventColor() != color) { 980 mModel.setEventColor(color); 981 mView.updateHeadlineColor(mModel, color); 982 } 983 } 984} 985