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 {
QueryHandler(ContentResolver cr)156         public QueryHandler(ContentResolver cr) {
157             super(cr);
158         }
159 
160         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)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 
setModelIfDone(int queryType)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 
EditEventFragment()433     public EditEventFragment() {
434         this(null, null, false, -1, false, null);
435     }
436 
EditEventFragment(EventInfo event, ArrayList<ReminderEntry> reminders, boolean eventColorInitialized, int eventColor, boolean readOnly, Intent intent)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
onActivityCreated(Bundle savedInstanceState)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 
startQuery()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
onAttach(Activity activity)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onDestroyView()588     public void onDestroyView() {
589         super.onDestroyView();
590 
591         if (mUseCustomActionBar) {
592             mActivity.getActionBar().setCustomView(null);
593         }
594     }
595 
596     @Override
onCreate(Bundle savedInstanceState)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
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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
onOptionsItemSelected(MenuItem item)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      */
onActionBarItemSelected(int itemId)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 
saveReminders()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 
displayEditWhichDialog()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
setDoneCode(int code)785         public void setDoneCode(int code) {
786             mCode = code;
787         }
788 
789         @Override
run()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 
isEmptyNewEvent()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
onPause()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
onDestroy()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
eventsChanged()929     public void eventsChanged() {
930         // TODO Requery to see if event has changed
931     }
932 
933     @Override
onSaveInstanceState(Bundle outState)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
getSupportedEventTypes()958     public long getSupportedEventTypes() {
959         return EventType.USER_HOME;
960     }
961 
962     @Override
handleEvent(EventInfo event)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
onColorSelected(int color)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