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