1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calendar.event;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Fragment;
22import android.app.FragmentManager;
23import android.content.AsyncQueryHandler;
24import android.content.ContentProviderOperation;
25import android.content.ContentResolver;
26import android.content.ContentUris;
27import android.content.ContentValues;
28import android.content.Context;
29import android.content.DialogInterface;
30import android.content.DialogInterface.OnCancelListener;
31import android.content.DialogInterface.OnClickListener;
32import android.content.Intent;
33import android.database.Cursor;
34import android.database.MatrixCursor;
35import android.net.Uri;
36import android.os.Bundle;
37import android.provider.CalendarContract.Attendees;
38import android.provider.CalendarContract.Calendars;
39import android.provider.CalendarContract.Colors;
40import android.provider.CalendarContract.Events;
41import android.provider.CalendarContract.Reminders;
42import android.text.TextUtils;
43import android.text.format.Time;
44import android.util.Log;
45import android.view.LayoutInflater;
46import android.view.Menu;
47import android.view.MenuInflater;
48import android.view.MenuItem;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.inputmethod.InputMethodManager;
52import android.widget.LinearLayout;
53import android.widget.Toast;
54
55import com.android.calendar.AsyncQueryService;
56import com.android.calendar.CalendarController;
57import com.android.calendar.CalendarController.EventHandler;
58import com.android.calendar.CalendarController.EventInfo;
59import com.android.calendar.CalendarController.EventType;
60import com.android.calendar.CalendarEventModel;
61import com.android.calendar.CalendarEventModel.Attendee;
62import com.android.calendar.CalendarEventModel.ReminderEntry;
63import com.android.calendar.DeleteEventHelper;
64import com.android.calendar.R;
65import com.android.calendar.Utils;
66import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
67import com.android.colorpicker.HsvColorComparator;
68
69import java.io.Serializable;
70import java.util.ArrayList;
71import java.util.Collections;
72
73public class EditEventFragment extends Fragment implements EventHandler, OnColorSelectedListener {
74    private static final String TAG = "EditEventActivity";
75    private static final String COLOR_PICKER_DIALOG_TAG = "ColorPickerDialog";
76
77    private static final int REQUEST_CODE_COLOR_PICKER = 0;
78
79    private static final String BUNDLE_KEY_MODEL = "key_model";
80    private static final String BUNDLE_KEY_EDIT_STATE = "key_edit_state";
81    private static final String BUNDLE_KEY_EVENT = "key_event";
82    private static final String BUNDLE_KEY_READ_ONLY = "key_read_only";
83    private static final String BUNDLE_KEY_EDIT_ON_LAUNCH = "key_edit_on_launch";
84    private static final String BUNDLE_KEY_SHOW_COLOR_PALETTE = "show_color_palette";
85
86    private static final String BUNDLE_KEY_DATE_BUTTON_CLICKED = "date_button_clicked";
87
88    private static final boolean DEBUG = false;
89
90    private static final int TOKEN_EVENT = 1;
91    private static final int TOKEN_ATTENDEES = 1 << 1;
92    private static final int TOKEN_REMINDERS = 1 << 2;
93    private static final int TOKEN_CALENDARS = 1 << 3;
94    private static final int TOKEN_COLORS = 1 << 4;
95
96    private static final int TOKEN_ALL = TOKEN_EVENT | TOKEN_ATTENDEES | TOKEN_REMINDERS
97            | TOKEN_CALENDARS | TOKEN_COLORS;
98    private static final int TOKEN_UNITIALIZED = 1 << 31;
99
100    /**
101     * A bitfield of TOKEN_* to keep track which query hasn't been completed
102     * yet. Once all queries have returned, the model can be applied to the
103     * view.
104     */
105    private int mOutstandingQueries = TOKEN_UNITIALIZED;
106
107    EditEventHelper mHelper;
108    CalendarEventModel mModel;
109    CalendarEventModel mOriginalModel;
110    CalendarEventModel mRestoreModel;
111    EditEventView mView;
112    QueryHandler mHandler;
113
114    private AlertDialog mModifyDialog;
115    int mModification = Utils.MODIFY_UNINITIALIZED;
116
117    private final EventInfo mEvent;
118    private EventBundle mEventBundle;
119    private ArrayList<ReminderEntry> mReminders;
120    private int mEventColor;
121    private boolean mEventColorInitialized = false;
122    private Uri mUri;
123    private long mBegin;
124    private long mEnd;
125    private long mCalendarId = -1;
126
127    private EventColorPickerDialog mColorPickerDialog;
128
129    private Activity 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 {
156        public QueryHandler(ContentResolver cr) {
157            super(cr);
158        }
159
160        @Override
161        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
162            // If the query didn't return a cursor for some reason return
163            if (cursor == null) {
164                return;
165            }
166
167            // If the Activity is finishing, then close the cursor.
168            // Otherwise, use the new cursor in the adapter.
169            final Activity activity = EditEventFragment.this.getActivity();
170            if (activity == null || activity.isFinishing()) {
171                cursor.close();
172                return;
173            }
174            long eventId;
175            switch (token) {
176                case TOKEN_EVENT:
177                    if (cursor.getCount() == 0) {
178                        // The cursor is empty. This can happen if the event
179                        // was deleted.
180                        cursor.close();
181                        mOnDone.setDoneCode(Utils.DONE_EXIT);
182                        mSaveOnDetach = false;
183                        mOnDone.run();
184                        return;
185                    }
186                    mOriginalModel = new CalendarEventModel();
187                    EditEventHelper.setModelFromCursor(mOriginalModel, cursor);
188                    EditEventHelper.setModelFromCursor(mModel, cursor);
189                    cursor.close();
190
191                    mOriginalModel.mUri = mUri.toString();
192
193                    mModel.mUri = mUri.toString();
194                    mModel.mOriginalStart = mBegin;
195                    mModel.mOriginalEnd = mEnd;
196                    mModel.mIsFirstEventInSeries = mBegin == mOriginalModel.mStart;
197                    mModel.mStart = mBegin;
198                    mModel.mEnd = mEnd;
199                    if (mEventColorInitialized) {
200                        mModel.setEventColor(mEventColor);
201                    }
202                    eventId = mModel.mId;
203
204                    // TOKEN_ATTENDEES
205                    if (mModel.mHasAttendeeData && eventId != -1) {
206                        Uri attUri = Attendees.CONTENT_URI;
207                        String[] whereArgs = {
208                            Long.toString(eventId)
209                        };
210                        mHandler.startQuery(TOKEN_ATTENDEES, null, attUri,
211                                EditEventHelper.ATTENDEES_PROJECTION,
212                                EditEventHelper.ATTENDEES_WHERE /* selection */,
213                                whereArgs /* selection args */, null /* sort order */);
214                    } else {
215                        setModelIfDone(TOKEN_ATTENDEES);
216                    }
217
218                    // TOKEN_REMINDERS
219                    if (mModel.mHasAlarm && mReminders == null) {
220                        Uri rUri = Reminders.CONTENT_URI;
221                        String[] remArgs = {
222                                Long.toString(eventId)
223                        };
224                        mHandler.startQuery(TOKEN_REMINDERS, null, rUri,
225                                EditEventHelper.REMINDERS_PROJECTION,
226                                EditEventHelper.REMINDERS_WHERE /* selection */,
227                                remArgs /* selection args */, null /* sort order */);
228                    } else {
229                        if (mReminders == null) {
230                            // mReminders should not be null.
231                            mReminders = new ArrayList<ReminderEntry>();
232                        } else {
233                            Collections.sort(mReminders);
234                        }
235                        mOriginalModel.mReminders = mReminders;
236                        mModel.mReminders =
237                                (ArrayList<ReminderEntry>) mReminders.clone();
238                        setModelIfDone(TOKEN_REMINDERS);
239                    }
240
241                    // TOKEN_CALENDARS
242                    String[] selArgs = {
243                        Long.toString(mModel.mCalendarId)
244                    };
245                    mHandler.startQuery(TOKEN_CALENDARS, null, Calendars.CONTENT_URI,
246                            EditEventHelper.CALENDARS_PROJECTION, EditEventHelper.CALENDARS_WHERE,
247                            selArgs /* selection args */, null /* sort order */);
248
249                    // 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
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
433    public EditEventFragment() {
434        this(null, null, false, -1, false, null);
435    }
436
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
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
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
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
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
588    public void onDestroyView() {
589        super.onDestroyView();
590
591        if (mUseCustomActionBar) {
592            mActivity.getActionBar().setCustomView(null);
593        }
594    }
595
596    @Override
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
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
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     */
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
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
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
785        public void setDoneCode(int code) {
786            mCode = code;
787        }
788
789        @Override
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
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
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
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
929    public void eventsChanged() {
930        // TODO Requery to see if event has changed
931    }
932
933    @Override
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
958    public long getSupportedEventTypes() {
959        return EventType.USER_HOME;
960    }
961
962    @Override
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
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