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