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