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