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