Home | History | Annotate | Download | only in event
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calendar.event;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Fragment;
     22 import android.content.AsyncQueryHandler;
     23 import android.content.ContentProviderOperation;
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.content.DialogInterface;
     29 import android.content.DialogInterface.OnCancelListener;
     30 import android.content.DialogInterface.OnClickListener;
     31 import android.content.Intent;
     32 import android.database.Cursor;
     33 import android.database.MatrixCursor;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.provider.CalendarContract.Attendees;
     37 import android.provider.CalendarContract.Calendars;
     38 import android.provider.CalendarContract.Events;
     39 import android.provider.CalendarContract.Reminders;
     40 import android.text.TextUtils;
     41 import android.text.format.Time;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.Menu;
     45 import android.view.MenuInflater;
     46 import android.view.MenuItem;
     47 import android.view.View;
     48 import android.view.ViewGroup;
     49 import android.view.inputmethod.InputMethodManager;
     50 import android.widget.LinearLayout;
     51 import android.widget.Toast;
     52 
     53 import com.android.calendar.AsyncQueryService;
     54 import com.android.calendar.CalendarController;
     55 import com.android.calendar.CalendarController.EventHandler;
     56 import com.android.calendar.CalendarController.EventInfo;
     57 import com.android.calendar.CalendarController.EventType;
     58 import com.android.calendar.CalendarEventModel;
     59 import com.android.calendar.CalendarEventModel.Attendee;
     60 import com.android.calendar.CalendarEventModel.ReminderEntry;
     61 import com.android.calendar.DeleteEventHelper;
     62 import com.android.calendar.R;
     63 import com.android.calendar.Utils;
     64 
     65 import java.io.Serializable;
     66 import java.util.ArrayList;
     67 import java.util.Collections;
     68 
     69 public 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         switch (itemId) {
    514             case R.id.action_done:
    515                 if (EditEventHelper.canModifyEvent(mModel) || EditEventHelper.canRespond(mModel)) {
    516                     if (mView != null && mView.prepareForSave()) {
    517                         if (mModification == Utils.MODIFY_UNINITIALIZED) {
    518                             mModification = Utils.MODIFY_ALL;
    519                         }
    520                         mOnDone.setDoneCode(Utils.DONE_SAVE | Utils.DONE_EXIT);
    521                         mOnDone.run();
    522                     } else {
    523                         mOnDone.setDoneCode(Utils.DONE_REVERT);
    524                         mOnDone.run();
    525                     }
    526                 } else if (EditEventHelper.canAddReminders(mModel) && mModel.mId != -1
    527                         && mOriginalModel != null && mView.prepareForSave()) {
    528                     saveReminders();
    529                     mOnDone.setDoneCode(Utils.DONE_EXIT);
    530                     mOnDone.run();
    531                 } else {
    532                     mOnDone.setDoneCode(Utils.DONE_REVERT);
    533                     mOnDone.run();
    534                 }
    535                 break;
    536             case R.id.action_cancel:
    537                 mOnDone.setDoneCode(Utils.DONE_REVERT);
    538                 mOnDone.run();
    539                 break;
    540         }
    541         return true;
    542     }
    543 
    544     private void saveReminders() {
    545         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
    546         boolean changed = EditEventHelper.saveReminders(ops, mModel.mId, mModel.mReminders,
    547                 mOriginalModel.mReminders, false /* no force save */);
    548 
    549         if (!changed) {
    550             return;
    551         }
    552 
    553         AsyncQueryService service = new AsyncQueryService(getActivity());
    554         service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
    555         // Update the "hasAlarm" field for the event
    556         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mModel.mId);
    557         int len = mModel.mReminders.size();
    558         boolean hasAlarm = len > 0;
    559         if (hasAlarm != mOriginalModel.mHasAlarm) {
    560             ContentValues values = new ContentValues();
    561             values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
    562             service.startUpdate(0, null, uri, values, null, null, 0);
    563         }
    564 
    565         Toast.makeText(mContext, R.string.saving_event, Toast.LENGTH_SHORT).show();
    566     }
    567 
    568     protected void displayEditWhichDialog() {
    569         if (mModification == Utils.MODIFY_UNINITIALIZED) {
    570             final boolean notSynced = TextUtils.isEmpty(mModel.mSyncId);
    571             boolean isFirstEventInSeries = mModel.mIsFirstEventInSeries;
    572             int itemIndex = 0;
    573             CharSequence[] items;
    574 
    575             if (notSynced) {
    576                 // If this event has not been synced, then don't allow deleting
    577                 // or changing a single instance.
    578                 if (isFirstEventInSeries) {
    579                     // Still display the option so the user knows all events are
    580                     // changing
    581                     items = new CharSequence[1];
    582                 } else {
    583                     items = new CharSequence[2];
    584                 }
    585             } else {
    586                 if (isFirstEventInSeries) {
    587                     items = new CharSequence[2];
    588                 } else {
    589                     items = new CharSequence[3];
    590                 }
    591                 items[itemIndex++] = mContext.getText(R.string.modify_event);
    592             }
    593             items[itemIndex++] = mContext.getText(R.string.modify_all);
    594 
    595             // Do one more check to make sure this remains at the end of the list
    596             if (!isFirstEventInSeries) {
    597                 items[itemIndex++] = mContext.getText(R.string.modify_all_following);
    598             }
    599 
    600             // Display the modification dialog.
    601             if (mModifyDialog != null) {
    602                 mModifyDialog.dismiss();
    603                 mModifyDialog = null;
    604             }
    605             mModifyDialog = new AlertDialog.Builder(mContext).setTitle(R.string.edit_event_label)
    606                     .setItems(items, new OnClickListener() {
    607                         public void onClick(DialogInterface dialog, int which) {
    608                             if (which == 0) {
    609                                 // Update this if we start allowing exceptions
    610                                 // to unsynced events in the app
    611                                 mModification = notSynced ? Utils.MODIFY_ALL
    612                                         : Utils.MODIFY_SELECTED;
    613                                 if (mModification == Utils.MODIFY_SELECTED) {
    614                                     mModel.mOriginalSyncId = notSynced ? null : mModel.mSyncId;
    615                                     mModel.mOriginalId = mModel.mId;
    616                                 }
    617                             } else if (which == 1) {
    618                                 mModification = notSynced ? Utils.MODIFY_ALL_FOLLOWING
    619                                         : Utils.MODIFY_ALL;
    620                             } else if (which == 2) {
    621                                 mModification = Utils.MODIFY_ALL_FOLLOWING;
    622                             }
    623 
    624                             mView.setModification(mModification);
    625                         }
    626                     }).show();
    627 
    628             mModifyDialog.setOnCancelListener(new OnCancelListener() {
    629                 @Override
    630                 public void onCancel(DialogInterface dialog) {
    631                     Activity a = EditEventFragment.this.getActivity();
    632                     if (a != null) {
    633                         a.finish();
    634                     }
    635                 }
    636             });
    637         }
    638     }
    639 
    640     class Done implements EditEventHelper.EditDoneRunnable {
    641         private int mCode = -1;
    642 
    643         public void setDoneCode(int code) {
    644             mCode = code;
    645         }
    646 
    647         public void run() {
    648             // We only want this to get called once, either because the user
    649             // pressed back/home or one of the buttons on screen
    650             mSaveOnDetach = false;
    651             if (mModification == Utils.MODIFY_UNINITIALIZED) {
    652                 // If this is uninitialized the user hit back, the only
    653                 // changeable item is response to default to all events.
    654                 mModification = Utils.MODIFY_ALL;
    655             }
    656 
    657             if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null
    658                     && (EditEventHelper.canRespond(mModel)
    659                             || EditEventHelper.canModifyEvent(mModel))
    660                     && mView.prepareForSave()
    661                     && !isEmptyNewEvent()
    662                     && mModel.normalizeReminders()
    663                     && mHelper.saveEvent(mModel, mOriginalModel, mModification)) {
    664                 int stringResource;
    665                 if (!mModel.mAttendeesList.isEmpty()) {
    666                     if (mModel.mUri != null) {
    667                         stringResource = R.string.saving_event_with_guest;
    668                     } else {
    669                         stringResource = R.string.creating_event_with_guest;
    670                     }
    671                 } else {
    672                     if (mModel.mUri != null) {
    673                         stringResource = R.string.saving_event;
    674                     } else {
    675                         stringResource = R.string.creating_event;
    676                     }
    677                 }
    678                 Toast.makeText(mContext, stringResource, Toast.LENGTH_SHORT).show();
    679             } else if ((mCode & Utils.DONE_SAVE) != 0 && mModel != null && isEmptyNewEvent()) {
    680                 Toast.makeText(mContext, R.string.empty_event, Toast.LENGTH_SHORT).show();
    681             }
    682 
    683             if ((mCode & Utils.DONE_DELETE) != 0 && mOriginalModel != null
    684                     && EditEventHelper.canModifyCalendar(mOriginalModel)) {
    685                 long begin = mModel.mStart;
    686                 long end = mModel.mEnd;
    687                 int which = -1;
    688                 switch (mModification) {
    689                     case Utils.MODIFY_SELECTED:
    690                         which = DeleteEventHelper.DELETE_SELECTED;
    691                         break;
    692                     case Utils.MODIFY_ALL_FOLLOWING:
    693                         which = DeleteEventHelper.DELETE_ALL_FOLLOWING;
    694                         break;
    695                     case Utils.MODIFY_ALL:
    696                         which = DeleteEventHelper.DELETE_ALL;
    697                         break;
    698                 }
    699                 DeleteEventHelper deleteHelper = new DeleteEventHelper(
    700                         mContext, mContext, !mIsReadOnly /* exitWhenDone */);
    701                 deleteHelper.delete(begin, end, mOriginalModel, which);
    702             }
    703 
    704             if ((mCode & Utils.DONE_EXIT) != 0) {
    705                 // This will exit the edit event screen, should be called
    706                 // when we want to return to the main calendar views
    707                 if ((mCode & Utils.DONE_SAVE) != 0) {
    708                     if (mContext != null) {
    709                         long start = mModel.mStart;
    710                         long end = mModel.mEnd;
    711                         if (mModel.mAllDay) {
    712                             // For allday events we want to go to the day in the
    713                             // user's current tz
    714                             String tz = Utils.getTimeZone(mContext, null);
    715                             Time t = new Time(Time.TIMEZONE_UTC);
    716                             t.set(start);
    717                             t.timezone = tz;
    718                             start = t.toMillis(true);
    719 
    720                             t.timezone = Time.TIMEZONE_UTC;
    721                             t.set(end);
    722                             t.timezone = tz;
    723                             end = t.toMillis(true);
    724                         }
    725                         CalendarController.getInstance(mContext).launchViewEvent(-1, start, end,
    726                                 Attendees.ATTENDEE_STATUS_NONE);
    727                     }
    728                 }
    729                 Activity a = EditEventFragment.this.getActivity();
    730                 if (a != null) {
    731                     a.finish();
    732                 }
    733             }
    734 
    735             // Hide a software keyboard so that user won't see it even after this Fragment's
    736             // disappearing.
    737             final View focusedView = mContext.getCurrentFocus();
    738             if (focusedView != null) {
    739                 mInputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
    740                 focusedView.clearFocus();
    741             }
    742         }
    743     }
    744 
    745     boolean isEmptyNewEvent() {
    746         if (mOriginalModel != null) {
    747             // Not new
    748             return false;
    749         }
    750 
    751         return isEmpty();
    752     }
    753 
    754     private boolean isEmpty() {
    755         if (mModel.mTitle != null) {
    756             String title = mModel.mTitle.trim();
    757             if (title.length() > 0) {
    758                 return false;
    759             }
    760         }
    761 
    762         if (mModel.mLocation != null) {
    763             String location = mModel.mLocation.trim();
    764             if (location.length() > 0) {
    765                 return false;
    766             }
    767         }
    768 
    769         if (mModel.mDescription != null) {
    770             String description = mModel.mDescription.trim();
    771             if (description.length() > 0) {
    772                 return false;
    773             }
    774         }
    775 
    776         return true;
    777     }
    778 
    779     @Override
    780     public void onPause() {
    781         Activity act = getActivity();
    782         if (mSaveOnDetach && act != null && !mIsReadOnly && !act.isChangingConfigurations()
    783                 && mView.prepareForSave()) {
    784             mOnDone.setDoneCode(Utils.DONE_SAVE);
    785             mOnDone.run();
    786         }
    787         super.onPause();
    788     }
    789 
    790     @Override
    791     public void onDestroy() {
    792         if (mView != null) {
    793             mView.setModel(null);
    794         }
    795         if (mModifyDialog != null) {
    796             mModifyDialog.dismiss();
    797             mModifyDialog = null;
    798         }
    799         super.onDestroy();
    800     }
    801 
    802     @Override
    803     public void eventsChanged() {
    804         // TODO Requery to see if event has changed
    805     }
    806 
    807     @Override
    808     public void onSaveInstanceState(Bundle outState) {
    809         mView.prepareForSave();
    810         outState.putSerializable(BUNDLE_KEY_MODEL, mModel);
    811         outState.putInt(BUNDLE_KEY_EDIT_STATE, mModification);
    812         if (mEventBundle == null && mEvent != null) {
    813             mEventBundle = new EventBundle();
    814             mEventBundle.id = mEvent.id;
    815             if (mEvent.startTime != null) {
    816                 mEventBundle.start = mEvent.startTime.toMillis(true);
    817             }
    818             if (mEvent.endTime != null) {
    819                 mEventBundle.end = mEvent.startTime.toMillis(true);
    820             }
    821         }
    822         outState.putBoolean(BUNDLE_KEY_EDIT_ON_LAUNCH, mShowModifyDialogOnLaunch);
    823         outState.putSerializable(BUNDLE_KEY_EVENT, mEventBundle);
    824         outState.putBoolean(BUNDLE_KEY_READ_ONLY, mIsReadOnly);
    825     }
    826 
    827     @Override
    828     public long getSupportedEventTypes() {
    829         return EventType.USER_HOME;
    830     }
    831 
    832     @Override
    833     public void handleEvent(EventInfo event) {
    834         // It's currently unclear if we want to save the event or not when home
    835         // is pressed. When creating a new event we shouldn't save since we
    836         // can't get the id of the new event easily.
    837         if ((false && event.eventType == EventType.USER_HOME) || (event.eventType == EventType.GO_TO
    838                 && mSaveOnDetach)) {
    839             if (mView != null && mView.prepareForSave()) {
    840                 mOnDone.setDoneCode(Utils.DONE_SAVE);
    841                 mOnDone.run();
    842             }
    843         }
    844     }
    845 
    846     private static class EventBundle implements Serializable {
    847         private static final long serialVersionUID = 1L;
    848         long id = -1;
    849         long start = -1;
    850         long end = -1;
    851     }
    852 }
    853