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