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