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