• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
20 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
21 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
22 import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorListenerAdapter;
26 import android.animation.ObjectAnimator;
27 import android.app.Activity;
28 import android.app.Dialog;
29 import android.app.DialogFragment;
30 import android.app.FragmentManager;
31 import android.app.Service;
32 import android.content.ActivityNotFoundException;
33 import android.content.ContentProviderOperation;
34 import android.content.ContentResolver;
35 import android.content.ContentUris;
36 import android.content.ContentValues;
37 import android.content.Context;
38 import android.content.DialogInterface;
39 import android.content.Intent;
40 import android.content.SharedPreferences;
41 import android.content.pm.ApplicationInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.PackageManager.NameNotFoundException;
44 import android.content.res.Resources;
45 import android.database.Cursor;
46 import android.graphics.Color;
47 import android.graphics.Rect;
48 import android.graphics.drawable.Drawable;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.provider.CalendarContract;
52 import android.provider.CalendarContract.Attendees;
53 import android.provider.CalendarContract.Calendars;
54 import android.provider.CalendarContract.Colors;
55 import android.provider.CalendarContract.Events;
56 import android.provider.CalendarContract.Reminders;
57 import android.provider.ContactsContract;
58 import android.provider.ContactsContract.CommonDataKinds;
59 import android.provider.ContactsContract.Intents;
60 import android.provider.ContactsContract.QuickContact;
61 import android.text.Spannable;
62 import android.text.SpannableStringBuilder;
63 import android.text.TextUtils;
64 import android.text.format.Time;
65 import android.text.method.LinkMovementMethod;
66 import android.text.method.MovementMethod;
67 import android.text.style.ForegroundColorSpan;
68 import android.text.util.Rfc822Token;
69 import android.util.Log;
70 import android.util.SparseIntArray;
71 import android.view.Gravity;
72 import android.view.LayoutInflater;
73 import android.view.Menu;
74 import android.view.MenuInflater;
75 import android.view.MenuItem;
76 import android.view.MotionEvent;
77 import android.view.View;
78 import android.view.View.OnClickListener;
79 import android.view.View.OnTouchListener;
80 import android.view.ViewGroup;
81 import android.view.Window;
82 import android.view.WindowManager;
83 import android.view.accessibility.AccessibilityEvent;
84 import android.view.accessibility.AccessibilityManager;
85 import android.widget.AdapterView;
86 import android.widget.AdapterView.OnItemSelectedListener;
87 import android.widget.Button;
88 import android.widget.LinearLayout;
89 import android.widget.RadioButton;
90 import android.widget.RadioGroup;
91 import android.widget.RadioGroup.OnCheckedChangeListener;
92 import android.widget.ScrollView;
93 import android.widget.TextView;
94 import android.widget.Toast;
95 
96 import com.android.calendar.CalendarController.EventInfo;
97 import com.android.calendar.CalendarController.EventType;
98 import com.android.calendar.CalendarEventModel.Attendee;
99 import com.android.calendar.CalendarEventModel.ReminderEntry;
100 import com.android.calendar.alerts.QuickResponseActivity;
101 import com.android.calendar.event.AttendeesView;
102 import com.android.calendar.event.EditEventActivity;
103 import com.android.calendar.event.EditEventHelper;
104 import com.android.calendar.event.EventColorPickerDialog;
105 import com.android.calendar.event.EventViewUtils;
106 import com.android.calendarcommon2.DateException;
107 import com.android.calendarcommon2.Duration;
108 import com.android.calendarcommon2.EventRecurrence;
109 import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
110 import com.android.colorpicker.HsvColorComparator;
111 
112 import java.util.ArrayList;
113 import java.util.Arrays;
114 import java.util.Collections;
115 import java.util.List;
116 
117 public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
118         CalendarController.EventHandler, OnClickListener, DeleteEventHelper.DeleteNotifyListener,
119         OnColorSelectedListener {
120 
121     public static final boolean DEBUG = false;
122 
123     public static final String TAG = "EventInfoFragment";
124     public static final String COLOR_PICKER_DIALOG_TAG = "EventColorPickerDialog";
125 
126     private static final int REQUEST_CODE_COLOR_PICKER = 0;
127 
128     protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
129     protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
130     protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
131     protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
132     protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible";
133     protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style";
134     protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color";
135     protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init";
136     protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color";
137     protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key";
138     protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init";
139     protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color";
140     protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init";
141     protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response";
142     protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE =
143             "key_user_set_attendee_response";
144     protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE =
145             "key_tentative_user_response";
146     protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events";
147     protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes";
148     protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods";
149 
150 
151     private static final String PERIOD_SPACE = ". ";
152 
153     private static final String NO_EVENT_COLOR = "";
154 
155     /**
156      * These are the corresponding indices into the array of strings
157      * "R.array.change_response_labels" in the resource file.
158      */
159     static final int UPDATE_SINGLE = 0;
160     static final int UPDATE_ALL = 1;
161 
162     // Style of view
163     public static final int FULL_WINDOW_STYLE = 0;
164     public static final int DIALOG_WINDOW_STYLE = 1;
165 
166     private int mWindowStyle = DIALOG_WINDOW_STYLE;
167 
168     // Query tokens for QueryHandler
169     private static final int TOKEN_QUERY_EVENT = 1 << 0;
170     private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
171     private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
172     private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
173     private static final int TOKEN_QUERY_REMINDERS = 1 << 4;
174     private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5;
175     private static final int TOKEN_QUERY_COLORS = 1 << 6;
176 
177     private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
178             | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT
179             | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS;
180 
181     private int mCurrentQuery = 0;
182 
183     private static final String[] EVENT_PROJECTION = new String[] {
184         Events._ID,                  // 0  do not remove; used in DeleteEventHelper
185         Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
186         Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
187         Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
188         Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
189         Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
190         Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
191         Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
192         Events.DESCRIPTION,          // 8
193         Events.EVENT_LOCATION,       // 9
194         Calendars.CALENDAR_ACCESS_LEVEL, // 10
195         Events.CALENDAR_COLOR,       // 11
196         Events.EVENT_COLOR,          // 12
197         Events.HAS_ATTENDEE_DATA,    // 13
198         Events.ORGANIZER,            // 14
199         Events.HAS_ALARM,            // 15
200         Calendars.MAX_REMINDERS,     // 16
201         Calendars.ALLOWED_REMINDERS, // 17
202         Events.CUSTOM_APP_PACKAGE,   // 18
203         Events.CUSTOM_APP_URI,       // 19
204         Events.DTEND,                // 20
205         Events.DURATION,             // 21
206         Events.ORIGINAL_SYNC_ID      // 22 do not remove; used in DeleteEventHelper
207     };
208     private static final int EVENT_INDEX_ID = 0;
209     private static final int EVENT_INDEX_TITLE = 1;
210     private static final int EVENT_INDEX_RRULE = 2;
211     private static final int EVENT_INDEX_ALL_DAY = 3;
212     private static final int EVENT_INDEX_CALENDAR_ID = 4;
213     private static final int EVENT_INDEX_DTSTART = 5;
214     private static final int EVENT_INDEX_SYNC_ID = 6;
215     private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
216     private static final int EVENT_INDEX_DESCRIPTION = 8;
217     private static final int EVENT_INDEX_EVENT_LOCATION = 9;
218     private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
219     private static final int EVENT_INDEX_CALENDAR_COLOR = 11;
220     private static final int EVENT_INDEX_EVENT_COLOR = 12;
221     private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13;
222     private static final int EVENT_INDEX_ORGANIZER = 14;
223     private static final int EVENT_INDEX_HAS_ALARM = 15;
224     private static final int EVENT_INDEX_MAX_REMINDERS = 16;
225     private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17;
226     private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18;
227     private static final int EVENT_INDEX_CUSTOM_APP_URI = 19;
228     private static final int EVENT_INDEX_DTEND = 20;
229     private static final int EVENT_INDEX_DURATION = 21;
230 
231     private static final String[] ATTENDEES_PROJECTION = new String[] {
232         Attendees._ID,                      // 0
233         Attendees.ATTENDEE_NAME,            // 1
234         Attendees.ATTENDEE_EMAIL,           // 2
235         Attendees.ATTENDEE_RELATIONSHIP,    // 3
236         Attendees.ATTENDEE_STATUS,          // 4
237         Attendees.ATTENDEE_IDENTITY,        // 5
238         Attendees.ATTENDEE_ID_NAMESPACE     // 6
239     };
240     private static final int ATTENDEES_INDEX_ID = 0;
241     private static final int ATTENDEES_INDEX_NAME = 1;
242     private static final int ATTENDEES_INDEX_EMAIL = 2;
243     private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
244     private static final int ATTENDEES_INDEX_STATUS = 4;
245     private static final int ATTENDEES_INDEX_IDENTITY = 5;
246     private static final int ATTENDEES_INDEX_ID_NAMESPACE = 6;
247 
248     static {
249         if (!Utils.isJellybeanOrLater()) {
250             EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // dummy value
251             EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // dummy value
252 
253             ATTENDEES_PROJECTION[ATTENDEES_INDEX_IDENTITY] = Attendees._ID; // dummy value
254             ATTENDEES_PROJECTION[ATTENDEES_INDEX_ID_NAMESPACE] = Attendees._ID; // dummy value
255         }
256     }
257 
258     private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
259 
260     private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
261             + Attendees.ATTENDEE_EMAIL + " ASC";
262 
263     private static final String[] REMINDERS_PROJECTION = new String[] {
264         Reminders._ID,                      // 0
265         Reminders.MINUTES,            // 1
266         Reminders.METHOD           // 2
267     };
268     private static final int REMINDERS_INDEX_ID = 0;
269     private static final int REMINDERS_MINUTES_ID = 1;
270     private static final int REMINDERS_METHOD_ID = 2;
271 
272     private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?";
273 
274     static final String[] CALENDARS_PROJECTION = new String[] {
275         Calendars._ID,           // 0
276         Calendars.CALENDAR_DISPLAY_NAME,  // 1
277         Calendars.OWNER_ACCOUNT, // 2
278         Calendars.CAN_ORGANIZER_RESPOND, // 3
279         Calendars.ACCOUNT_NAME, // 4
280         Calendars.ACCOUNT_TYPE  // 5
281     };
282     static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
283     static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
284     static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
285     static final int CALENDARS_INDEX_ACCOUNT_NAME = 4;
286     static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5;
287 
288     static final String CALENDARS_WHERE = Calendars._ID + "=?";
289     static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?";
290     static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?";
291 
292     static final String[] COLORS_PROJECTION = new String[] {
293         Colors._ID, // 0
294         Colors.COLOR, // 1
295         Colors.COLOR_KEY // 2
296     };
297 
298     static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE +
299         "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT;
300 
301     public static final int COLORS_INDEX_COLOR = 1;
302     public static final int COLORS_INDEX_COLOR_KEY = 2;
303 
304     private View mView;
305 
306     private Uri mUri;
307     private long mEventId;
308     private Cursor mEventCursor;
309     private Cursor mAttendeesCursor;
310     private Cursor mCalendarsCursor;
311     private Cursor mRemindersCursor;
312 
313     private static float mScale = 0; // Used for supporting different screen densities
314 
315     private static int mCustomAppIconSize = 32;
316 
317     private long mStartMillis;
318     private long mEndMillis;
319     private boolean mAllDay;
320 
321     private boolean mHasAttendeeData;
322     private String mEventOrganizerEmail;
323     private String mEventOrganizerDisplayName = "";
324     private boolean mIsOrganizer;
325     private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
326     private boolean mOwnerCanRespond;
327     private String mSyncAccountName;
328     private String mCalendarOwnerAccount;
329     private boolean mCanModifyCalendar;
330     private boolean mCanModifyEvent;
331     private boolean mIsBusyFreeCalendar;
332     private int mNumOfAttendees;
333     private EditResponseHelper mEditResponseHelper;
334     private boolean mDeleteDialogVisible = false;
335     private DeleteEventHelper mDeleteHelper;
336 
337     private int mOriginalAttendeeResponse;
338     private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE;
339     private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
340     private int mWhichEvents = -1;
341     // Used as the temporary response until the dialog is confirmed. It is also
342     // able to be used as a state marker for configuration changes.
343     private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
344     private boolean mIsRepeating;
345     private boolean mHasAlarm;
346     private int mMaxReminders;
347     private String mCalendarAllowedReminders;
348     // Used to prevent saving changes in event if it is being deleted.
349     private boolean mEventDeletionStarted = false;
350 
351     private TextView mTitle;
352     private TextView mWhenDateTime;
353     private TextView mWhere;
354     private ExpandableTextView mDesc;
355     private AttendeesView mLongAttendees;
356     private Button emailAttendeesButton;
357     private Menu mMenu = null;
358     private View mHeadlines;
359     private ScrollView mScrollView;
360     private View mLoadingMsgView;
361     private ObjectAnimator mAnimateAlpha;
362     private long mLoadingMsgStartTime;
363 
364     private EventColorPickerDialog mColorPickerDialog;
365     private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
366     private int[] mColors;
367     private int mOriginalColor = -1;
368     private boolean mOriginalColorInitialized = false;
369     private int mCalendarColor = -1;
370     private boolean mCalendarColorInitialized = false;
371     private int mCurrentColor = -1;
372     private boolean mCurrentColorInitialized = false;
373     private int mCurrentColorKey = -1;
374 
375     private static final int FADE_IN_TIME = 300;   // in milliseconds
376     private static final int LOADING_MSG_DELAY = 600;   // in milliseconds
377     private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
378     private boolean mNoCrossFade = false;  // Used to prevent repeated cross-fade
379     private RadioGroup mResponseRadioGroup;
380 
381     ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
382     ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
383     ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
384     ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
385     ArrayList<String> mToEmails = new ArrayList<String>();
386     ArrayList<String> mCcEmails = new ArrayList<String>();
387 
388     private int mDefaultReminderMinutes;
389     private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0);
390     public ArrayList<ReminderEntry> mReminders;
391     public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>();
392     public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
393     private boolean mUserModifiedReminders = false;
394 
395     /**
396      * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
397      * with any additional values that were already associated with the event.
398      */
399     private ArrayList<Integer> mReminderMinuteValues;
400     private ArrayList<String> mReminderMinuteLabels;
401 
402     /**
403      * Contents of the "methods" spinner.  The "values" list specifies the method constant
404      * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
405      * aren't allowed by the Calendar will be removed.
406      */
407     private ArrayList<Integer> mReminderMethodValues;
408     private ArrayList<String> mReminderMethodLabels;
409 
410     private QueryHandler mHandler;
411 
412 
413     private final Runnable mTZUpdater = new Runnable() {
414         @Override
415         public void run() {
416             updateEvent(mView);
417         }
418     };
419 
420     private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
421         @Override
422         public void run() {
423             // Since this is run after a delay, make sure to only show the message
424             // if the event's data is not shown yet.
425             if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
426                 mLoadingMsgStartTime = System.currentTimeMillis();
427                 mLoadingMsgView.setAlpha(1);
428             }
429         }
430     };
431 
432     private OnItemSelectedListener mReminderChangeListener;
433 
434     private static int mDialogWidth = 500;
435     private static int mDialogHeight = 600;
436     private static int DIALOG_TOP_MARGIN = 8;
437     private boolean mIsDialog = false;
438     private boolean mIsPaused = true;
439     private boolean mDismissOnResume = false;
440     private int mX = -1;
441     private int mY = -1;
442     private int mMinTop;         // Dialog cannot be above this location
443     private boolean mIsTabletConfig;
444     private Activity mActivity;
445     private Context mContext;
446 
447     private CalendarController mController;
448 
449     private class QueryHandler extends AsyncQueryService {
QueryHandler(Context context)450         public QueryHandler(Context context) {
451             super(context);
452         }
453 
454         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)455         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
456             // if the activity is finishing, then close the cursor and return
457             final Activity activity = getActivity();
458             if (activity == null || activity.isFinishing()) {
459                 if (cursor != null) {
460                     cursor.close();
461                 }
462                 return;
463             }
464 
465             switch (token) {
466             case TOKEN_QUERY_EVENT:
467                 mEventCursor = Utils.matrixCursorFromCursor(cursor);
468                 if (initEventCursor()) {
469                     // The cursor is empty. This can happen if the event was
470                     // deleted.
471                     // FRAG_TODO we should no longer rely on Activity.finish()
472                     activity.finish();
473                     return;
474                 }
475                 if (!mCalendarColorInitialized) {
476                     mCalendarColor = Utils.getDisplayColorFromColor(
477                             mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR));
478                     mCalendarColorInitialized = true;
479                 }
480 
481                 if (!mOriginalColorInitialized) {
482                     mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR)
483                             ? mCalendarColor : Utils.getDisplayColorFromColor(
484                                     mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR));
485                     mOriginalColorInitialized = true;
486                 }
487 
488                 if (!mCurrentColorInitialized) {
489                     mCurrentColor = mOriginalColor;
490                     mCurrentColorInitialized = true;
491                 }
492 
493                 updateEvent(mView);
494                 prepareReminders();
495 
496                 // start calendar query
497                 Uri uri = Calendars.CONTENT_URI;
498                 String[] args = new String[] {
499                         Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
500                 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
501                         CALENDARS_WHERE, args, null);
502                 break;
503             case TOKEN_QUERY_CALENDARS:
504                 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
505                 updateCalendar(mView);
506                 // FRAG_TODO fragments shouldn't set the title anymore
507                 updateTitle();
508 
509                 args = new String[] {
510                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME),
511                         mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) };
512                 uri = Colors.CONTENT_URI;
513                 startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args,
514                         null);
515 
516                 if (!mIsBusyFreeCalendar) {
517                     args = new String[] { Long.toString(mEventId) };
518 
519                     // start attendees query
520                     uri = Attendees.CONTENT_URI;
521                     startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
522                             ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
523                 } else {
524                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
525                 }
526                 if (mHasAlarm) {
527                     // start reminders query
528                     args = new String[] { Long.toString(mEventId) };
529                     uri = Reminders.CONTENT_URI;
530                     startQuery(TOKEN_QUERY_REMINDERS, null, uri,
531                             REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
532                 } else {
533                     sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
534                 }
535                 break;
536             case TOKEN_QUERY_COLORS:
537                 ArrayList<Integer> colors = new ArrayList<Integer>();
538                 if (cursor.moveToFirst()) {
539                     do
540                     {
541                         int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY);
542                         int rawColor = cursor.getInt(COLORS_INDEX_COLOR);
543                         int displayColor = Utils.getDisplayColorFromColor(rawColor);
544                         mDisplayColorKeyMap.put(displayColor, colorKey);
545                         colors.add(displayColor);
546                     } while (cursor.moveToNext());
547                 }
548                 cursor.close();
549                 Integer[] sortedColors = new Integer[colors.size()];
550                 Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator());
551                 mColors = new int[sortedColors.length];
552                 for (int i = 0; i < sortedColors.length; i++) {
553                     mColors[i] = sortedColors[i].intValue();
554 
555                     float[] hsv = new float[3];
556                     Color.colorToHSV(mColors[i], hsv);
557                     if (DEBUG) {
558                         Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]);
559                     }
560                 }
561                 if (mCanModifyCalendar) {
562                     View button = mView.findViewById(R.id.change_color);
563                     if (button != null && mColors.length > 0) {
564                         button.setEnabled(true);
565                         button.setVisibility(View.VISIBLE);
566                     }
567                 }
568                 updateMenu();
569                 break;
570             case TOKEN_QUERY_ATTENDEES:
571                 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
572                 initAttendeesCursor(mView);
573                 updateResponse(mView);
574                 break;
575             case TOKEN_QUERY_REMINDERS:
576                 mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
577                 initReminders(mView, mRemindersCursor);
578                 break;
579             case TOKEN_QUERY_VISIBLE_CALENDARS:
580                 if (cursor.getCount() > 1) {
581                     // Start duplicate calendars query to detect whether to add the calendar
582                     // email to the calendar owner display.
583                     String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
584                     mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null,
585                             Calendars.CONTENT_URI, CALENDARS_PROJECTION,
586                             CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
587                 } else {
588                     // Don't need to display the calendar owner when there is only a single
589                     // calendar.  Skip the duplicate calendars query.
590                     setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
591                     mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS;
592                 }
593                 break;
594             case TOKEN_QUERY_DUPLICATE_CALENDARS:
595                 SpannableStringBuilder sb = new SpannableStringBuilder();
596 
597                 // Calendar display name
598                 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
599                 sb.append(calendarName);
600 
601                 // Show email account if display name is not unique and
602                 // display name != email
603                 String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
604                 if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
605                         Utils.isValidEmail(email)) {
606                     sb.append(" (").append(email).append(")");
607                 }
608 
609                 setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
610                 setTextCommon(mView, R.id.calendar_name, sb);
611                 break;
612             }
613             cursor.close();
614             sendAccessibilityEventIfQueryDone(token);
615 
616             // All queries are done, show the view.
617             if (mCurrentQuery == TOKEN_QUERY_ALL) {
618                 if (mLoadingMsgView.getAlpha() == 1) {
619                     // Loading message is showing, let it stay a bit more (to prevent
620                     // flashing) by adding a start delay to the event animation
621                     long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() -
622                             mLoadingMsgStartTime);
623                     if (timeDiff > 0) {
624                         mAnimateAlpha.setStartDelay(timeDiff);
625                     }
626                 }
627                 if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) {
628                     mAnimateAlpha.start();
629                 } else {
630                     mScrollView.setAlpha(1);
631                     mLoadingMsgView.setVisibility(View.GONE);
632                 }
633             }
634         }
635     }
636 
sendAccessibilityEventIfQueryDone(int token)637     private void sendAccessibilityEventIfQueryDone(int token) {
638         mCurrentQuery |= token;
639         if (mCurrentQuery == TOKEN_QUERY_ALL) {
640             sendAccessibilityEvent();
641         }
642     }
643 
EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis, int attendeeResponse, boolean isDialog, int windowStyle, ArrayList<ReminderEntry> reminders)644     public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
645             int attendeeResponse, boolean isDialog, int windowStyle,
646             ArrayList<ReminderEntry> reminders) {
647 
648         Resources r = context.getResources();
649         if (mScale == 0) {
650             mScale = context.getResources().getDisplayMetrics().density;
651             if (mScale != 1) {
652                 mCustomAppIconSize *= mScale;
653                 if (isDialog) {
654                     DIALOG_TOP_MARGIN *= mScale;
655                 }
656             }
657         }
658         if (isDialog) {
659             setDialogSize(r);
660         }
661         mIsDialog = isDialog;
662 
663         setStyle(DialogFragment.STYLE_NO_TITLE, 0);
664         mUri = uri;
665         mStartMillis = startMillis;
666         mEndMillis = endMillis;
667         mAttendeeResponseFromIntent = attendeeResponse;
668         mWindowStyle = windowStyle;
669 
670         // Pass in null if no reminders are being specified.
671         // This may be used to explicitly show certain reminders already known
672         // about, such as during configuration changes.
673         mReminders = reminders;
674     }
675 
676     // This is currently required by the fragment manager.
EventInfoFragment()677     public EventInfoFragment() {
678     }
679 
EventInfoFragment(Context context, long eventId, long startMillis, long endMillis, int attendeeResponse, boolean isDialog, int windowStyle, ArrayList<ReminderEntry> reminders)680     public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
681             int attendeeResponse, boolean isDialog, int windowStyle,
682             ArrayList<ReminderEntry> reminders) {
683         this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
684                 endMillis, attendeeResponse, isDialog, windowStyle, reminders);
685         mEventId = eventId;
686     }
687 
688     @Override
onActivityCreated(Bundle savedInstanceState)689     public void onActivityCreated(Bundle savedInstanceState) {
690         super.onActivityCreated(savedInstanceState);
691 
692         mReminderChangeListener = new OnItemSelectedListener() {
693             @Override
694             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
695                 Integer prevValue = (Integer) parent.getTag();
696                 if (prevValue == null || prevValue != position) {
697                     parent.setTag(position);
698                     mUserModifiedReminders = true;
699                 }
700             }
701 
702             @Override
703             public void onNothingSelected(AdapterView<?> parent) {
704                 // do nothing
705             }
706 
707         };
708 
709         if (savedInstanceState != null) {
710             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
711             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
712                     DIALOG_WINDOW_STYLE);
713         }
714 
715         if (mIsDialog) {
716             applyDialogParams();
717         }
718 
719         final Activity activity = getActivity();
720         mContext = activity;
721         mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager()
722                 .findFragmentByTag(COLOR_PICKER_DIALOG_TAG);
723         if (mColorPickerDialog != null) {
724             mColorPickerDialog.setOnColorSelectedListener(this);
725         }
726     }
727 
applyDialogParams()728     private void applyDialogParams() {
729         Dialog dialog = getDialog();
730         dialog.setCanceledOnTouchOutside(true);
731 
732         Window window = dialog.getWindow();
733         window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
734 
735         WindowManager.LayoutParams a = window.getAttributes();
736         a.dimAmount = .4f;
737 
738         a.width = mDialogWidth;
739         a.height = mDialogHeight;
740 
741 
742         // On tablets , do smart positioning of dialog
743         // On phones , use the whole screen
744 
745         if (mX != -1 || mY != -1) {
746             a.x = mX - mDialogWidth / 2;
747             a.y = mY - mDialogHeight / 2;
748             if (a.y < mMinTop) {
749                 a.y = mMinTop + DIALOG_TOP_MARGIN;
750             }
751             a.gravity = Gravity.LEFT | Gravity.TOP;
752         }
753         window.setAttributes(a);
754     }
755 
setDialogParams(int x, int y, int minTop)756     public void setDialogParams(int x, int y, int minTop) {
757         mX = x;
758         mY = y;
759         mMinTop = minTop;
760     }
761 
762     // Implements OnCheckedChangeListener
763     @Override
onCheckedChanged(RadioGroup group, int checkedId)764     public void onCheckedChanged(RadioGroup group, int checkedId) {
765         // If we haven't finished the return from the dialog yet, don't display.
766         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
767             return;
768         }
769 
770         // If this is not a repeating event, then don't display the dialog
771         // asking which events to change.
772         int response = getResponseFromButtonId(checkedId);
773         if (!mIsRepeating) {
774             mUserSetResponse = response;
775             return;
776         }
777 
778         // If the selection is the same as the original, then don't display the
779         // dialog asking which events to change.
780         if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
781             mUserSetResponse = response;
782             return;
783         }
784 
785         // This is a repeating event. We need to ask the user if they mean to
786         // change just this one instance or all instances.
787         mTentativeUserSetResponse = response;
788         mEditResponseHelper.showDialog(mWhichEvents);
789     }
790 
onNothingSelected(AdapterView<?> parent)791     public void onNothingSelected(AdapterView<?> parent) {
792     }
793 
794     @Override
onDetach()795     public void onDetach() {
796         super.onDetach();
797         mController.deregisterEventHandler(R.layout.event_info);
798     }
799 
800     @Override
onAttach(Activity activity)801     public void onAttach(Activity activity) {
802         super.onAttach(activity);
803         mActivity = activity;
804         // Ensure that mIsTabletConfig is set before creating the menu.
805         mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
806         mController = CalendarController.getInstance(mActivity);
807         mController.registerEventHandler(R.layout.event_info, this);
808         mEditResponseHelper = new EditResponseHelper(activity);
809         mEditResponseHelper.setDismissListener(
810                 new DialogInterface.OnDismissListener() {
811             @Override
812             public void onDismiss(DialogInterface dialog) {
813                 // If the user dismisses the dialog (without hitting OK),
814                 // then we want to revert the selection that opened the dialog.
815                 if (mEditResponseHelper.getWhichEvents() != -1) {
816                     mUserSetResponse = mTentativeUserSetResponse;
817                     mWhichEvents = mEditResponseHelper.getWhichEvents();
818                 } else {
819                     // Revert the attending response radio selection to whatever
820                     // was selected prior to this selection (possibly nothing).
821                     int oldResponse;
822                     if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
823                         oldResponse = mUserSetResponse;
824                     } else {
825                         oldResponse = mOriginalAttendeeResponse;
826                     }
827                     int buttonToCheck = findButtonIdForResponse(oldResponse);
828 
829                     if (mResponseRadioGroup != null) {
830                         mResponseRadioGroup.check(buttonToCheck);
831                     }
832 
833                     // If the radio group is being cleared, also clear the
834                     // dialog's selection of which events should be included
835                     // in this response.
836                     if (buttonToCheck == -1) {
837                         mEditResponseHelper.setWhichEvents(-1);
838                     }
839                 }
840 
841                 // Since OnPause will force the dialog to dismiss, do
842                 // not change the dialog status
843                 if (!mIsPaused) {
844                     mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
845                 }
846             }
847         });
848 
849         if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
850             mEditResponseHelper.setWhichEvents(UPDATE_ALL);
851             mWhichEvents = mEditResponseHelper.getWhichEvents();
852         }
853         mHandler = new QueryHandler(activity);
854         if (!mIsDialog) {
855             setHasOptionsMenu(true);
856         }
857     }
858 
859     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)860     public View onCreateView(LayoutInflater inflater, ViewGroup container,
861             Bundle savedInstanceState) {
862 
863         if (savedInstanceState != null) {
864             mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
865             mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
866                     DIALOG_WINDOW_STYLE);
867             mDeleteDialogVisible =
868                 savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false);
869             mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR);
870             mCalendarColorInitialized =
871                     savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT);
872             mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR);
873             mOriginalColorInitialized = savedInstanceState.getBoolean(
874                     BUNDLE_KEY_ORIGINAL_COLOR_INIT);
875             mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR);
876             mCurrentColorInitialized = savedInstanceState.getBoolean(
877                     BUNDLE_KEY_CURRENT_COLOR_INIT);
878             mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY);
879 
880             mTentativeUserSetResponse = savedInstanceState.getInt(
881                             BUNDLE_KEY_TENTATIVE_USER_RESPONSE,
882                             Attendees.ATTENDEE_STATUS_NONE);
883             if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
884                     mEditResponseHelper != null) {
885                 // If the edit response helper dialog is open, we'll need to
886                 // know if either of the choices were selected.
887                 mEditResponseHelper.setWhichEvents(savedInstanceState.getInt(
888                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1));
889             }
890             mUserSetResponse = savedInstanceState.getInt(
891                     BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE,
892                     Attendees.ATTENDEE_STATUS_NONE);
893             if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
894                 // If the response was set by the user before a configuration
895                 // change, we'll need to know which choice was selected.
896                 mWhichEvents = savedInstanceState.getInt(
897                         BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1);
898             }
899 
900             mReminders = Utils.readRemindersFromBundle(savedInstanceState);
901         }
902 
903         if (mWindowStyle == DIALOG_WINDOW_STYLE) {
904             mView = inflater.inflate(R.layout.event_info_dialog, container, false);
905         } else {
906             mView = inflater.inflate(R.layout.event_info, container, false);
907         }
908         mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
909         mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
910         mTitle = (TextView) mView.findViewById(R.id.title);
911         mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
912         mWhere = (TextView) mView.findViewById(R.id.where);
913         mDesc = (ExpandableTextView) mView.findViewById(R.id.description);
914         mHeadlines = mView.findViewById(R.id.event_info_headline);
915         mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list);
916 
917         mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
918 
919         if (mUri == null) {
920             // restore event ID from bundle
921             mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
922             mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
923             mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
924             mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
925         }
926 
927         mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
928         mAnimateAlpha.setDuration(FADE_IN_TIME);
929         mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
930             int defLayerType;
931 
932             @Override
933             public void onAnimationStart(Animator animation) {
934                 // Use hardware layer for better performance during animation
935                 defLayerType = mScrollView.getLayerType();
936                 mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
937                 // Ensure that the loading message is gone before showing the
938                 // event info
939                 mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
940                 mLoadingMsgView.setVisibility(View.GONE);
941             }
942 
943             @Override
944             public void onAnimationCancel(Animator animation) {
945                 mScrollView.setLayerType(defLayerType, null);
946             }
947 
948             @Override
949             public void onAnimationEnd(Animator animation) {
950                 mScrollView.setLayerType(defLayerType, null);
951                 // Do not cross fade after the first time
952                 mNoCrossFade = true;
953             }
954         });
955 
956         mLoadingMsgView.setAlpha(0);
957         mScrollView.setAlpha(0);
958         mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY);
959 
960         // start loading the data
961 
962         mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
963                 null, null, null);
964 
965         View b = mView.findViewById(R.id.delete);
966         b.setOnClickListener(new OnClickListener() {
967             @Override
968             public void onClick(View v) {
969                 if (!mCanModifyCalendar) {
970                     return;
971                 }
972                 mDeleteHelper =
973                         new DeleteEventHelper(mContext, mActivity, !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
974                 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
975                 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
976                 mDeleteDialogVisible = true;
977                 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
978             }
979         });
980 
981         b = mView.findViewById(R.id.change_color);
982         b.setOnClickListener(new OnClickListener() {
983             @Override
984             public void onClick(View v) {
985                 if (!mCanModifyCalendar) {
986                     return;
987                 }
988                 showEventColorPickerDialog();
989             }
990         });
991 
992         // Hide Edit/Delete buttons if in full screen mode on a phone
993         if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
994             mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE);
995         }
996 
997         // Create a listener for the email guests button
998         emailAttendeesButton = (Button) mView.findViewById(R.id.email_attendees_button);
999         if (emailAttendeesButton != null) {
1000             emailAttendeesButton.setOnClickListener(new View.OnClickListener() {
1001                 @Override
1002                 public void onClick(View v) {
1003                     emailAttendees();
1004                 }
1005             });
1006         }
1007 
1008         // Create a listener for the add reminder button
1009         View reminderAddButton = mView.findViewById(R.id.reminder_add);
1010         View.OnClickListener addReminderOnClickListener = new View.OnClickListener() {
1011             @Override
1012             public void onClick(View v) {
1013                 addReminder();
1014                 mUserModifiedReminders = true;
1015             }
1016         };
1017         reminderAddButton.setOnClickListener(addReminderOnClickListener);
1018 
1019         // Set reminders variables
1020 
1021         SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity);
1022         String defaultReminderString = prefs.getString(
1023                 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING);
1024         mDefaultReminderMinutes = Integer.parseInt(defaultReminderString);
1025         prepareReminders();
1026 
1027         return mView;
1028     }
1029 
1030     private final Runnable onDeleteRunnable = new Runnable() {
1031         @Override
1032         public void run() {
1033             if (EventInfoFragment.this.mIsPaused) {
1034                 mDismissOnResume = true;
1035                 return;
1036             }
1037             if (EventInfoFragment.this.isVisible()) {
1038                 EventInfoFragment.this.dismiss();
1039             }
1040         }
1041     };
1042 
updateTitle()1043     private void updateTitle() {
1044         Resources res = getActivity().getResources();
1045         if (mCanModifyCalendar && !mIsOrganizer) {
1046             getActivity().setTitle(res.getString(R.string.event_info_title_invite));
1047         } else {
1048             getActivity().setTitle(res.getString(R.string.event_info_title));
1049         }
1050     }
1051 
1052     /**
1053      * Initializes the event cursor, which is expected to point to the first
1054      * (and only) result from a query.
1055      * @return true if the cursor is empty.
1056      */
initEventCursor()1057     private boolean initEventCursor() {
1058         if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
1059             return true;
1060         }
1061         mEventCursor.moveToFirst();
1062         mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
1063         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
1064         mIsRepeating = !TextUtils.isEmpty(rRule);
1065         // mHasAlarm will be true if it was saved in the event already, or if
1066         // we've explicitly been provided reminders (e.g. during rotation).
1067         mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true :
1068             (mReminders != null && mReminders.size() > 0);
1069         mMaxReminders = mEventCursor.getInt(EVENT_INDEX_MAX_REMINDERS);
1070         mCalendarAllowedReminders =  mEventCursor.getString(EVENT_INDEX_ALLOWED_REMINDERS);
1071         return false;
1072     }
1073 
1074     @SuppressWarnings("fallthrough")
initAttendeesCursor(View view)1075     private void initAttendeesCursor(View view) {
1076         mOriginalAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE;
1077         mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
1078         mNumOfAttendees = 0;
1079         if (mAttendeesCursor != null) {
1080             mNumOfAttendees = mAttendeesCursor.getCount();
1081             if (mAttendeesCursor.moveToFirst()) {
1082                 mAcceptedAttendees.clear();
1083                 mDeclinedAttendees.clear();
1084                 mTentativeAttendees.clear();
1085                 mNoResponseAttendees.clear();
1086 
1087                 do {
1088                     int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
1089                     String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
1090                     String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
1091 
1092                     if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) ==
1093                             Attendees.RELATIONSHIP_ORGANIZER) {
1094 
1095                         // Overwrites the one from Event table if available
1096                         if (!TextUtils.isEmpty(name)) {
1097                             mEventOrganizerDisplayName = name;
1098                             if (!mIsOrganizer) {
1099                                 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
1100                                 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
1101                             }
1102                         }
1103                     }
1104 
1105                     if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
1106                             mCalendarOwnerAccount.equalsIgnoreCase(email)) {
1107                         mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
1108                         mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
1109                     } else {
1110                         String identity = null;
1111                         String idNamespace = null;
1112 
1113                         if (Utils.isJellybeanOrLater()) {
1114                             identity = mAttendeesCursor.getString(ATTENDEES_INDEX_IDENTITY);
1115                             idNamespace = mAttendeesCursor.getString(ATTENDEES_INDEX_ID_NAMESPACE);
1116                         }
1117 
1118                         // Don't show your own status in the list because:
1119                         //  1) it doesn't make sense for event without other guests.
1120                         //  2) there's a spinner for that for events with guests.
1121                         switch(status) {
1122                             case Attendees.ATTENDEE_STATUS_ACCEPTED:
1123                                 mAcceptedAttendees.add(new Attendee(name, email,
1124                                         Attendees.ATTENDEE_STATUS_ACCEPTED, identity,
1125                                         idNamespace));
1126                                 break;
1127                             case Attendees.ATTENDEE_STATUS_DECLINED:
1128                                 mDeclinedAttendees.add(new Attendee(name, email,
1129                                         Attendees.ATTENDEE_STATUS_DECLINED, identity,
1130                                         idNamespace));
1131                                 break;
1132                             case Attendees.ATTENDEE_STATUS_TENTATIVE:
1133                                 mTentativeAttendees.add(new Attendee(name, email,
1134                                         Attendees.ATTENDEE_STATUS_TENTATIVE, identity,
1135                                         idNamespace));
1136                                 break;
1137                             default:
1138                                 mNoResponseAttendees.add(new Attendee(name, email,
1139                                         Attendees.ATTENDEE_STATUS_NONE, identity,
1140                                         idNamespace));
1141                         }
1142                     }
1143                 } while (mAttendeesCursor.moveToNext());
1144                 mAttendeesCursor.moveToFirst();
1145 
1146                 updateAttendees(view);
1147             }
1148         }
1149     }
1150 
1151     @Override
onSaveInstanceState(Bundle outState)1152     public void onSaveInstanceState(Bundle outState) {
1153         super.onSaveInstanceState(outState);
1154         outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId);
1155         outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis);
1156         outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis);
1157         outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog);
1158         outState.putInt(BUNDLE_KEY_WINDOW_STYLE, mWindowStyle);
1159         outState.putBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE, mDeleteDialogVisible);
1160         outState.putInt(BUNDLE_KEY_CALENDAR_COLOR, mCalendarColor);
1161         outState.putBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT, mCalendarColorInitialized);
1162         outState.putInt(BUNDLE_KEY_ORIGINAL_COLOR, mOriginalColor);
1163         outState.putBoolean(BUNDLE_KEY_ORIGINAL_COLOR_INIT, mOriginalColorInitialized);
1164         outState.putInt(BUNDLE_KEY_CURRENT_COLOR, mCurrentColor);
1165         outState.putBoolean(BUNDLE_KEY_CURRENT_COLOR_INIT, mCurrentColorInitialized);
1166         outState.putInt(BUNDLE_KEY_CURRENT_COLOR_KEY, mCurrentColorKey);
1167 
1168         // We'll need the temporary response for configuration changes.
1169         outState.putInt(BUNDLE_KEY_TENTATIVE_USER_RESPONSE, mTentativeUserSetResponse);
1170         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
1171                 mEditResponseHelper != null) {
1172             outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS,
1173                     mEditResponseHelper.getWhichEvents());
1174         }
1175 
1176         // Save the current response.
1177         int response;
1178         if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
1179             response = mAttendeeResponseFromIntent;
1180         } else {
1181             response = mOriginalAttendeeResponse;
1182         }
1183         outState.putInt(BUNDLE_KEY_ATTENDEE_RESPONSE, response);
1184         if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
1185             response = mUserSetResponse;
1186             outState.putInt(BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, response);
1187             outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, mWhichEvents);
1188         }
1189 
1190         // Save the reminders.
1191         mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
1192                 mReminderMinuteValues, mReminderMethodValues);
1193         int numReminders = mReminders.size();
1194         ArrayList<Integer> reminderMinutes =
1195                 new ArrayList<Integer>(numReminders);
1196         ArrayList<Integer> reminderMethods =
1197                 new ArrayList<Integer>(numReminders);
1198         for (ReminderEntry reminder : mReminders) {
1199             reminderMinutes.add(reminder.getMinutes());
1200             reminderMethods.add(reminder.getMethod());
1201         }
1202         outState.putIntegerArrayList(
1203                 BUNDLE_KEY_REMINDER_MINUTES, reminderMinutes);
1204         outState.putIntegerArrayList(
1205                 BUNDLE_KEY_REMINDER_METHODS, reminderMethods);
1206     }
1207 
1208     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)1209     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
1210         super.onCreateOptionsMenu(menu, inflater);
1211         // Show color/edit/delete buttons only in non-dialog configuration
1212         if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) {
1213             inflater.inflate(R.menu.event_info_title_bar, menu);
1214             mMenu = menu;
1215             updateMenu();
1216         }
1217     }
1218 
1219     @Override
onOptionsItemSelected(MenuItem item)1220     public boolean onOptionsItemSelected(MenuItem item) {
1221 
1222         // If we're a dialog we don't want to handle menu buttons
1223         if (mIsDialog) {
1224             return false;
1225         }
1226         // Handles option menu selections:
1227         // Home button - close event info activity and start the main calendar
1228         // one
1229         // Edit button - start the event edit activity and close the info
1230         // activity
1231         // Delete button - start a delete query that calls a runnable that close
1232         // the info activity
1233 
1234         final int itemId = item.getItemId();
1235         if (itemId == android.R.id.home) {
1236             Utils.returnToCalendarHome(mContext);
1237             mActivity.finish();
1238             return true;
1239         } else if (itemId == R.id.info_action_edit) {
1240             doEdit();
1241             mActivity.finish();
1242         } else if (itemId == R.id.info_action_delete) {
1243             mDeleteHelper =
1244                     new DeleteEventHelper(mActivity, mActivity, true /* exitWhenDone */);
1245             mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this);
1246             mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
1247             mDeleteDialogVisible = true;
1248             mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
1249         } else if (itemId == R.id.info_action_change_color) {
1250             showEventColorPickerDialog();
1251         }
1252         return super.onOptionsItemSelected(item);
1253     }
1254 
showEventColorPickerDialog()1255     private void showEventColorPickerDialog() {
1256         if (mColorPickerDialog == null) {
1257             mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor,
1258                     mCalendarColor, mIsTabletConfig);
1259             mColorPickerDialog.setOnColorSelectedListener(this);
1260         }
1261         final FragmentManager fragmentManager = getFragmentManager();
1262         fragmentManager.executePendingTransactions();
1263         if (!mColorPickerDialog.isAdded()) {
1264             mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG);
1265         }
1266     }
1267 
saveEventColor()1268     private boolean saveEventColor() {
1269         if (mCurrentColor == mOriginalColor) {
1270             return false;
1271         }
1272 
1273         ContentValues values = new ContentValues();
1274         if (mCurrentColor != mCalendarColor) {
1275             values.put(Events.EVENT_COLOR_KEY, mCurrentColorKey);
1276         } else {
1277             values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR);
1278         }
1279         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
1280         mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
1281                 null, null, Utils.UNDO_DELAY);
1282         return true;
1283     }
1284 
1285     @Override
onStop()1286     public void onStop() {
1287         Activity act = getActivity();
1288         if (!mEventDeletionStarted && act != null && !act.isChangingConfigurations()) {
1289 
1290             boolean responseSaved = saveResponse();
1291             boolean eventColorSaved = saveEventColor();
1292             if (saveReminders() || responseSaved || eventColorSaved) {
1293                 Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show();
1294             }
1295         }
1296         super.onStop();
1297     }
1298 
1299     @Override
onDestroy()1300     public void onDestroy() {
1301         if (mEventCursor != null) {
1302             mEventCursor.close();
1303         }
1304         if (mCalendarsCursor != null) {
1305             mCalendarsCursor.close();
1306         }
1307         if (mAttendeesCursor != null) {
1308             mAttendeesCursor.close();
1309         }
1310         super.onDestroy();
1311     }
1312 
1313     /**
1314      * Asynchronously saves the response to an invitation if the user changed
1315      * the response. Returns true if the database will be updated.
1316      *
1317      * @return true if the database will be changed
1318      */
saveResponse()1319     private boolean saveResponse() {
1320         if (mAttendeesCursor == null || mEventCursor == null) {
1321             return false;
1322         }
1323 
1324         int status = getResponseFromButtonId(
1325                 mResponseRadioGroup.getCheckedRadioButtonId());
1326         if (status == Attendees.ATTENDEE_STATUS_NONE) {
1327             return false;
1328         }
1329 
1330         // If the status has not changed, then don't update the database
1331         if (status == mOriginalAttendeeResponse) {
1332             return false;
1333         }
1334 
1335         // If we never got an owner attendee id we can't set the status
1336         if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
1337             return false;
1338         }
1339 
1340         if (!mIsRepeating) {
1341             // This is a non-repeating event
1342             updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
1343             mOriginalAttendeeResponse = status;
1344             return true;
1345         }
1346 
1347         if (DEBUG) {
1348             Log.d(TAG, "Repeating event: mWhichEvents=" + mWhichEvents);
1349         }
1350         // This is a repeating event
1351         switch (mWhichEvents) {
1352             case -1:
1353                 return false;
1354             case UPDATE_SINGLE:
1355                 createExceptionResponse(mEventId, status);
1356                 mOriginalAttendeeResponse = status;
1357                 return true;
1358             case UPDATE_ALL:
1359                 updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
1360                 mOriginalAttendeeResponse = status;
1361                 return true;
1362             default:
1363                 Log.e(TAG, "Unexpected choice for updating invitation response");
1364                 break;
1365         }
1366         return false;
1367     }
1368 
updateResponse(long eventId, long attendeeId, int status)1369     private void updateResponse(long eventId, long attendeeId, int status) {
1370         // Update the attendee status in the attendees table.  the provider
1371         // takes care of updating the self attendance status.
1372         ContentValues values = new ContentValues();
1373 
1374         if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
1375             values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
1376         }
1377         values.put(Attendees.ATTENDEE_STATUS, status);
1378         values.put(Attendees.EVENT_ID, eventId);
1379 
1380         Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
1381 
1382         mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
1383                 null, null, Utils.UNDO_DELAY);
1384     }
1385 
1386     /**
1387      * Creates an exception to a recurring event.  The only change we're making is to the
1388      * "self attendee status" value.  The provider will take care of updating the corresponding
1389      * Attendees.attendeeStatus entry.
1390      *
1391      * @param eventId The recurring event.
1392      * @param status The new value for selfAttendeeStatus.
1393      */
createExceptionResponse(long eventId, int status)1394     private void createExceptionResponse(long eventId, int status) {
1395         ContentValues values = new ContentValues();
1396         values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
1397         values.put(Events.SELF_ATTENDEE_STATUS, status);
1398         values.put(Events.STATUS, Events.STATUS_CONFIRMED);
1399 
1400         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1401         Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
1402                 String.valueOf(eventId));
1403         ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build());
1404 
1405         mHandler.startBatch(mHandler.getNextToken(), null, CalendarContract.AUTHORITY, ops,
1406                 Utils.UNDO_DELAY);
1407    }
1408 
getResponseFromButtonId(int buttonId)1409     public static int getResponseFromButtonId(int buttonId) {
1410         int response;
1411         if (buttonId == R.id.response_yes) {
1412             response = Attendees.ATTENDEE_STATUS_ACCEPTED;
1413         } else if (buttonId == R.id.response_maybe) {
1414             response = Attendees.ATTENDEE_STATUS_TENTATIVE;
1415         } else if (buttonId == R.id.response_no) {
1416             response = Attendees.ATTENDEE_STATUS_DECLINED;
1417         } else {
1418             response = Attendees.ATTENDEE_STATUS_NONE;
1419         }
1420         return response;
1421     }
1422 
findButtonIdForResponse(int response)1423     public static int findButtonIdForResponse(int response) {
1424         int buttonId;
1425         switch (response) {
1426             case Attendees.ATTENDEE_STATUS_ACCEPTED:
1427                 buttonId = R.id.response_yes;
1428                 break;
1429             case Attendees.ATTENDEE_STATUS_TENTATIVE:
1430                 buttonId = R.id.response_maybe;
1431                 break;
1432             case Attendees.ATTENDEE_STATUS_DECLINED:
1433                 buttonId = R.id.response_no;
1434                 break;
1435                 default:
1436                     buttonId = -1;
1437         }
1438         return buttonId;
1439     }
1440 
doEdit()1441     private void doEdit() {
1442         Context c = getActivity();
1443         // This ensures that we aren't in the process of closing and have been
1444         // unattached already
1445         if (c != null) {
1446             Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
1447             Intent intent = new Intent(Intent.ACTION_EDIT, uri);
1448             intent.setClass(mActivity, EditEventActivity.class);
1449             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
1450             intent.putExtra(EXTRA_EVENT_END_TIME, mEndMillis);
1451             intent.putExtra(EXTRA_EVENT_ALL_DAY, mAllDay);
1452             intent.putExtra(EditEventActivity.EXTRA_EVENT_COLOR, mCurrentColor);
1453             intent.putExtra(EditEventActivity.EXTRA_EVENT_REMINDERS, EventViewUtils
1454                     .reminderItemsToReminders(mReminderViews, mReminderMinuteValues,
1455                     mReminderMethodValues));
1456             intent.putExtra(EVENT_EDIT_ON_LAUNCH, true);
1457             startActivity(intent);
1458         }
1459     }
1460 
updateEvent(View view)1461     private void updateEvent(View view) {
1462         if (mEventCursor == null || view == null) {
1463             return;
1464         }
1465 
1466         Context context = view.getContext();
1467         if (context == null) {
1468             return;
1469         }
1470 
1471         String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
1472         if (eventName == null || eventName.length() == 0) {
1473             eventName = getActivity().getString(R.string.no_title_label);
1474         }
1475 
1476         // 3rd parties might not have specified the start/end time when firing the
1477         // Events.CONTENT_URI intent.  Update these with values read from the db.
1478         if (mStartMillis == 0 && mEndMillis == 0) {
1479             mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
1480             mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
1481             if (mEndMillis == 0) {
1482                 String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
1483                 if (!TextUtils.isEmpty(duration)) {
1484                     try {
1485                         Duration d = new Duration();
1486                         d.parse(duration);
1487                         long endMillis = mStartMillis + d.getMillis();
1488                         if (endMillis >= mStartMillis) {
1489                             mEndMillis = endMillis;
1490                         } else {
1491                             Log.d(TAG, "Invalid duration string: " + duration);
1492                         }
1493                     } catch (DateException e) {
1494                         Log.d(TAG, "Error parsing duration string " + duration, e);
1495                     }
1496                 }
1497                 if (mEndMillis == 0) {
1498                     mEndMillis = mStartMillis;
1499                 }
1500             }
1501         }
1502 
1503         mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
1504         String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
1505         String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
1506         String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
1507         String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
1508 
1509         mHeadlines.setBackgroundColor(mCurrentColor);
1510 
1511         // What
1512         if (eventName != null) {
1513             setTextCommon(view, R.id.title, eventName);
1514         }
1515 
1516         // When
1517         // Set the date and repeats (if any)
1518         String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
1519 
1520         Resources resources = context.getResources();
1521         String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
1522                 System.currentTimeMillis(), localTimezone, mAllDay, context);
1523 
1524         String displayedTimezone = null;
1525         if (!mAllDay) {
1526             displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
1527                     eventTimezone);
1528         }
1529         // Display the datetime.  Make the timezone (if any) transparent.
1530         if (displayedTimezone == null) {
1531             setTextCommon(view, R.id.when_datetime, displayedDatetime);
1532         } else {
1533             int timezoneIndex = displayedDatetime.length();
1534             displayedDatetime += "  " + displayedTimezone;
1535             SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
1536             ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
1537                     resources.getColor(R.color.event_info_headline_transparent_color));
1538             sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
1539                     Spannable.SPAN_INCLUSIVE_INCLUSIVE);
1540             setTextCommon(view, R.id.when_datetime, sb);
1541         }
1542 
1543         // Display the repeat string (if any)
1544         String repeatString = null;
1545         if (!TextUtils.isEmpty(rRule)) {
1546             EventRecurrence eventRecurrence = new EventRecurrence();
1547             eventRecurrence.parse(rRule);
1548             Time date = new Time(localTimezone);
1549             date.set(mStartMillis);
1550             if (mAllDay) {
1551                 date.timezone = Time.TIMEZONE_UTC;
1552             }
1553             eventRecurrence.setStartDate(date);
1554             repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources,
1555                     eventRecurrence, true);
1556         }
1557         if (repeatString == null) {
1558             view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
1559         } else {
1560             setTextCommon(view, R.id.when_repeat, repeatString);
1561         }
1562 
1563         // Organizer view is setup in the updateCalendar method
1564 
1565 
1566         // Where
1567         if (location == null || location.trim().length() == 0) {
1568             setVisibilityCommon(view, R.id.where, View.GONE);
1569         } else {
1570             final TextView textView = mWhere;
1571             if (textView != null) {
1572                 textView.setAutoLinkMask(0);
1573                 textView.setText(location.trim());
1574                 try {
1575                     textView.setText(Utils.extendedLinkify(textView.getText().toString(), true));
1576 
1577                     // Linkify.addLinks() sets the TextView movement method if it finds any links.
1578                     // We must do the same here, in case linkify by itself did not find any.
1579                     // (This is cloned from Linkify.addLinkMovementMethod().)
1580                     MovementMethod mm = textView.getMovementMethod();
1581                     if ((mm == null) || !(mm instanceof LinkMovementMethod)) {
1582                         if (textView.getLinksClickable()) {
1583                             textView.setMovementMethod(LinkMovementMethod.getInstance());
1584                         }
1585                     }
1586                 } catch (Exception ex) {
1587                     // unexpected
1588                     Log.e(TAG, "Linkification failed", ex);
1589                 }
1590 
1591                 textView.setOnTouchListener(new OnTouchListener() {
1592                     @Override
1593                     public boolean onTouch(View v, MotionEvent event) {
1594                         try {
1595                             return v.onTouchEvent(event);
1596                         } catch (ActivityNotFoundException e) {
1597                             // ignore
1598                             return true;
1599                         }
1600                     }
1601                 });
1602             }
1603         }
1604 
1605         // Description
1606         if (description != null && description.length() != 0) {
1607             mDesc.setText(description);
1608         }
1609 
1610         // Launch Custom App
1611         if (Utils.isJellybeanOrLater()) {
1612             updateCustomAppButton();
1613         }
1614     }
1615 
updateCustomAppButton()1616     private void updateCustomAppButton() {
1617         buttonSetup: {
1618             final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button);
1619             if (launchButton == null)
1620                 break buttonSetup;
1621 
1622             final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE);
1623             final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI);
1624 
1625             if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri))
1626                 break buttonSetup;
1627 
1628             PackageManager pm = mContext.getPackageManager();
1629             if (pm == null)
1630                 break buttonSetup;
1631 
1632             ApplicationInfo info;
1633             try {
1634                 info = pm.getApplicationInfo(customAppPackage, 0);
1635                 if (info == null)
1636                     break buttonSetup;
1637             } catch (NameNotFoundException e) {
1638                 break buttonSetup;
1639             }
1640 
1641             Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
1642             final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri);
1643             intent.setPackage(customAppPackage);
1644             intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri);
1645             intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
1646 
1647             // See if we have a taker for our intent
1648             if (pm.resolveActivity(intent, 0) == null)
1649                 break buttonSetup;
1650 
1651             Drawable icon = pm.getApplicationIcon(info);
1652             if (icon != null) {
1653 
1654                 Drawable[] d = launchButton.getCompoundDrawables();
1655                 icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize);
1656                 launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]);
1657             }
1658 
1659             CharSequence label = pm.getApplicationLabel(info);
1660             if (label != null && label.length() != 0) {
1661                 launchButton.setText(label);
1662             } else if (icon == null) {
1663                 // No icon && no label. Hide button?
1664                 break buttonSetup;
1665             }
1666 
1667             // Launch custom app
1668             launchButton.setOnClickListener(new View.OnClickListener() {
1669                 @Override
1670                 public void onClick(View v) {
1671                     try {
1672                         startActivityForResult(intent, 0);
1673                     } catch (ActivityNotFoundException e) {
1674                         // Shouldn't happen as we checked it already
1675                         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1676                     }
1677                 }
1678             });
1679 
1680             setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE);
1681             return;
1682 
1683         }
1684 
1685         setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1686         return;
1687     }
1688 
sendAccessibilityEvent()1689     private void sendAccessibilityEvent() {
1690         AccessibilityManager am =
1691             (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
1692         if (!am.isEnabled()) {
1693             return;
1694         }
1695 
1696         AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1697         event.setClassName(getClass().getName());
1698         event.setPackageName(getActivity().getPackageName());
1699         List<CharSequence> text = event.getText();
1700 
1701         addFieldToAccessibilityEvent(text, mTitle, null);
1702         addFieldToAccessibilityEvent(text, mWhenDateTime, null);
1703         addFieldToAccessibilityEvent(text, mWhere, null);
1704         addFieldToAccessibilityEvent(text, null, mDesc);
1705 
1706         if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
1707             int id = mResponseRadioGroup.getCheckedRadioButtonId();
1708             if (id != View.NO_ID) {
1709                 text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
1710                 text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
1711                         .getText() + PERIOD_SPACE));
1712             }
1713         }
1714 
1715         am.sendAccessibilityEvent(event);
1716     }
1717 
addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv, ExpandableTextView etv)1718     private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv,
1719             ExpandableTextView etv) {
1720         CharSequence cs;
1721         if (tv != null) {
1722             cs = tv.getText();
1723         } else if (etv != null) {
1724             cs = etv.getText();
1725         } else {
1726             return;
1727         }
1728 
1729         if (!TextUtils.isEmpty(cs)) {
1730             cs = cs.toString().trim();
1731             if (cs.length() > 0) {
1732                 text.add(cs);
1733                 text.add(PERIOD_SPACE);
1734             }
1735         }
1736     }
1737 
updateCalendar(View view)1738     private void updateCalendar(View view) {
1739 
1740         mCalendarOwnerAccount = "";
1741         if (mCalendarsCursor != null && mEventCursor != null) {
1742             mCalendarsCursor.moveToFirst();
1743             String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
1744             mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
1745             mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
1746             mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
1747 
1748             // start visible calendars query
1749             mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI,
1750                     CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null);
1751 
1752             mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
1753             mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail);
1754 
1755             if (!TextUtils.isEmpty(mEventOrganizerEmail) &&
1756                     !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) {
1757                 mEventOrganizerDisplayName = mEventOrganizerEmail;
1758             }
1759 
1760             if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
1761                 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
1762                 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
1763             } else {
1764                 setVisibilityCommon(view, R.id.organizer_container, View.GONE);
1765             }
1766             mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
1767             mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
1768                     >= Calendars.CAL_ACCESS_CONTRIBUTOR;
1769             // TODO add "|| guestCanModify" after b/1299071 is fixed
1770             mCanModifyEvent = mCanModifyCalendar && mIsOrganizer;
1771             mIsBusyFreeCalendar =
1772                     mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
1773 
1774             if (!mIsBusyFreeCalendar) {
1775 
1776                 View b = mView.findViewById(R.id.edit);
1777                 b.setEnabled(true);
1778                 b.setOnClickListener(new OnClickListener() {
1779                     @Override
1780                     public void onClick(View v) {
1781                         doEdit();
1782                         // For dialogs, just close the fragment
1783                         // For full screen, close activity on phone, leave it for tablet
1784                         if (mIsDialog) {
1785                             EventInfoFragment.this.dismiss();
1786                         }
1787                         else if (!mIsTabletConfig){
1788                             getActivity().finish();
1789                         }
1790                     }
1791                 });
1792             }
1793             View button;
1794             if (mCanModifyCalendar) {
1795                 button = mView.findViewById(R.id.delete);
1796                 if (button != null) {
1797                     button.setEnabled(true);
1798                     button.setVisibility(View.VISIBLE);
1799                 }
1800             }
1801             if (mCanModifyEvent) {
1802                 button = mView.findViewById(R.id.edit);
1803                 if (button != null) {
1804                     button.setEnabled(true);
1805                     button.setVisibility(View.VISIBLE);
1806                 }
1807             }
1808             if ((!mIsDialog && !mIsTabletConfig ||
1809                     mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
1810                 mActivity.invalidateOptionsMenu();
1811             }
1812         } else {
1813             setVisibilityCommon(view, R.id.calendar, View.GONE);
1814             sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
1815         }
1816     }
1817 
1818     /**
1819      *
1820      */
updateMenu()1821     private void updateMenu() {
1822         if (mMenu == null) {
1823             return;
1824         }
1825         MenuItem delete = mMenu.findItem(R.id.info_action_delete);
1826         MenuItem edit = mMenu.findItem(R.id.info_action_edit);
1827         MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color);
1828         if (delete != null) {
1829             delete.setVisible(mCanModifyCalendar);
1830             delete.setEnabled(mCanModifyCalendar);
1831         }
1832         if (edit != null) {
1833             edit.setVisible(mCanModifyEvent);
1834             edit.setEnabled(mCanModifyEvent);
1835         }
1836         if (changeColor != null && mColors != null && mColors.length > 0) {
1837             changeColor.setVisible(mCanModifyCalendar);
1838             changeColor.setEnabled(mCanModifyCalendar);
1839         }
1840     }
1841 
updateAttendees(View view)1842     private void updateAttendees(View view) {
1843         if (mAcceptedAttendees.size() + mDeclinedAttendees.size() +
1844                 mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) {
1845             mLongAttendees.clearAttendees();
1846             (mLongAttendees).addAttendees(mAcceptedAttendees);
1847             (mLongAttendees).addAttendees(mDeclinedAttendees);
1848             (mLongAttendees).addAttendees(mTentativeAttendees);
1849             (mLongAttendees).addAttendees(mNoResponseAttendees);
1850             mLongAttendees.setEnabled(false);
1851             mLongAttendees.setVisibility(View.VISIBLE);
1852         } else {
1853             mLongAttendees.setVisibility(View.GONE);
1854         }
1855 
1856         if (hasEmailableAttendees()) {
1857             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
1858             if (emailAttendeesButton != null) {
1859                 emailAttendeesButton.setText(R.string.email_guests_label);
1860             }
1861         } else if (hasEmailableOrganizer()) {
1862             setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
1863             if (emailAttendeesButton != null) {
1864                 emailAttendeesButton.setText(R.string.email_organizer_label);
1865             }
1866         } else {
1867             setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE);
1868         }
1869     }
1870 
1871     /**
1872      * Returns true if there is at least 1 attendee that is not the viewer.
1873      */
hasEmailableAttendees()1874     private boolean hasEmailableAttendees() {
1875         for (Attendee attendee : mAcceptedAttendees) {
1876             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1877                 return true;
1878             }
1879         }
1880         for (Attendee attendee : mTentativeAttendees) {
1881             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1882                 return true;
1883             }
1884         }
1885         for (Attendee attendee : mNoResponseAttendees) {
1886             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1887                 return true;
1888             }
1889         }
1890         for (Attendee attendee : mDeclinedAttendees) {
1891             if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1892                 return true;
1893             }
1894         }
1895         return false;
1896     }
1897 
hasEmailableOrganizer()1898     private boolean hasEmailableOrganizer() {
1899         return mEventOrganizerEmail != null &&
1900                 Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName);
1901     }
1902 
initReminders(View view, Cursor cursor)1903     public void initReminders(View view, Cursor cursor) {
1904 
1905         // Add reminders
1906         mOriginalReminders.clear();
1907         mUnsupportedReminders.clear();
1908         while (cursor.moveToNext()) {
1909             int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
1910             int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD);
1911 
1912             if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) {
1913                 // Stash unsupported reminder types separately so we don't alter
1914                 // them in the UI
1915                 mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method));
1916             } else {
1917                 mOriginalReminders.add(ReminderEntry.valueOf(minutes, method));
1918             }
1919         }
1920         // Sort appropriately for display (by time, then type)
1921         Collections.sort(mOriginalReminders);
1922 
1923         if (mUserModifiedReminders) {
1924             // If the user has changed the list of reminders don't change what's
1925             // shown.
1926             return;
1927         }
1928 
1929         LinearLayout parent = (LinearLayout) mScrollView
1930                 .findViewById(R.id.reminder_items_container);
1931         if (parent != null) {
1932             parent.removeAllViews();
1933         }
1934         if (mReminderViews != null) {
1935             mReminderViews.clear();
1936         }
1937 
1938         if (mHasAlarm) {
1939             ArrayList<ReminderEntry> reminders;
1940             // If applicable, use reminders saved in the bundle.
1941             if (mReminders != null) {
1942                 reminders = mReminders;
1943             } else {
1944                 reminders = mOriginalReminders;
1945             }
1946             // Insert any minute values that aren't represented in the minutes list.
1947             for (ReminderEntry re : reminders) {
1948                 EventViewUtils.addMinutesToList(
1949                         mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
1950             }
1951             // Create a UI element for each reminder.  We display all of the reminders we get
1952             // from the provider, even if the count exceeds the calendar maximum.  (Also, for
1953             // a new event, we won't have a maxReminders value available.)
1954             for (ReminderEntry re : reminders) {
1955                 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
1956                         mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
1957                         mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener);
1958             }
1959             EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
1960             // TODO show unsupported reminder types in some fashion.
1961         }
1962     }
1963 
updateResponse(View view)1964     void updateResponse(View view) {
1965         // we only let the user accept/reject/etc. a meeting if:
1966         // a) you can edit the event's containing calendar AND
1967         // b) you're not the organizer and only attendee AND
1968         // c) organizerCanRespond is enabled for the calendar
1969         // (if the attendee data has been hidden, the visible number of attendees
1970         // will be 1 -- the calendar owner's).
1971         // (there are more cases involved to be 100% accurate, such as
1972         // paying attention to whether or not an attendee status was
1973         // included in the feed, but we're currently omitting those corner cases
1974         // for simplicity).
1975 
1976         // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
1977         if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
1978                 (mIsOrganizer && !mOwnerCanRespond)) {
1979             setVisibilityCommon(view, R.id.response_container, View.GONE);
1980             return;
1981         }
1982 
1983         setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
1984 
1985 
1986         int response;
1987         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
1988             response = mTentativeUserSetResponse;
1989         } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
1990             response = mUserSetResponse;
1991         } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
1992             response = mAttendeeResponseFromIntent;
1993         } else {
1994             response = mOriginalAttendeeResponse;
1995         }
1996 
1997         int buttonToCheck = findButtonIdForResponse(response);
1998         mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
1999         mResponseRadioGroup.setOnCheckedChangeListener(this);
2000     }
2001 
setTextCommon(View view, int id, CharSequence text)2002     private void setTextCommon(View view, int id, CharSequence text) {
2003         TextView textView = (TextView) view.findViewById(id);
2004         if (textView == null)
2005             return;
2006         textView.setText(text);
2007     }
2008 
setVisibilityCommon(View view, int id, int visibility)2009     private void setVisibilityCommon(View view, int id, int visibility) {
2010         View v = view.findViewById(id);
2011         if (v != null) {
2012             v.setVisibility(visibility);
2013         }
2014         return;
2015     }
2016 
2017     /**
2018      * Taken from com.google.android.gm.HtmlConversationActivity
2019      *
2020      * Send the intent that shows the Contact info corresponding to the email address.
2021      */
showContactInfo(Attendee attendee, Rect rect)2022     public void showContactInfo(Attendee attendee, Rect rect) {
2023         // First perform lookup query to find existing contact
2024         final ContentResolver resolver = getActivity().getContentResolver();
2025         final String address = attendee.mEmail;
2026         final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
2027                 Uri.encode(address));
2028         final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
2029 
2030         if (lookupUri != null) {
2031             // Found matching contact, trigger QuickContact
2032             QuickContact.showQuickContact(getActivity(), rect, lookupUri,
2033                     QuickContact.MODE_MEDIUM, null);
2034         } else {
2035             // No matching contact, ask user to create one
2036             final Uri mailUri = Uri.fromParts("mailto", address, null);
2037             final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
2038 
2039             // Pass along full E-mail string for possible create dialog
2040             Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
2041             intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
2042 
2043             // Only provide personal name hint if we have one
2044             final String senderPersonal = attendee.mName;
2045             if (!TextUtils.isEmpty(senderPersonal)) {
2046                 intent.putExtra(Intents.Insert.NAME, senderPersonal);
2047             }
2048 
2049             startActivity(intent);
2050         }
2051     }
2052 
2053     @Override
onPause()2054     public void onPause() {
2055         mIsPaused = true;
2056         mHandler.removeCallbacks(onDeleteRunnable);
2057         super.onPause();
2058         // Remove event deletion alert box since it is being rebuild in the OnResume
2059         // This is done to get the same behavior on OnResume since the AlertDialog is gone on
2060         // rotation but not if you press the HOME key
2061         if (mDeleteDialogVisible && mDeleteHelper != null) {
2062             mDeleteHelper.dismissAlertDialog();
2063             mDeleteHelper = null;
2064         }
2065         if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE
2066                 && mEditResponseHelper != null) {
2067             mEditResponseHelper.dismissAlertDialog();
2068         }
2069     }
2070 
2071     @Override
onResume()2072     public void onResume() {
2073         super.onResume();
2074         if (mIsDialog) {
2075             setDialogSize(getActivity().getResources());
2076             applyDialogParams();
2077         }
2078         mIsPaused = false;
2079         if (mDismissOnResume) {
2080             mHandler.post(onDeleteRunnable);
2081         }
2082         // Display the "delete confirmation" or "edit response helper" dialog if needed
2083         if (mDeleteDialogVisible) {
2084             mDeleteHelper = new DeleteEventHelper(
2085                     mContext, mActivity,
2086                     !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
2087             mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
2088             mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
2089         } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
2090             int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
2091             mResponseRadioGroup.check(buttonId);
2092             mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
2093         }
2094     }
2095 
2096     @Override
eventsChanged()2097     public void eventsChanged() {
2098     }
2099 
2100     @Override
getSupportedEventTypes()2101     public long getSupportedEventTypes() {
2102         return EventType.EVENTS_CHANGED;
2103     }
2104 
2105     @Override
handleEvent(EventInfo event)2106     public void handleEvent(EventInfo event) {
2107         reloadEvents();
2108     }
2109 
reloadEvents()2110     public void reloadEvents() {
2111         if (mHandler != null) {
2112             mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
2113                     null, null, null);
2114         }
2115     }
2116 
2117     @Override
onClick(View view)2118     public void onClick(View view) {
2119 
2120         // This must be a click on one of the "remove reminder" buttons
2121         LinearLayout reminderItem = (LinearLayout) view.getParent();
2122         LinearLayout parent = (LinearLayout) reminderItem.getParent();
2123         parent.removeView(reminderItem);
2124         mReminderViews.remove(reminderItem);
2125         mUserModifiedReminders = true;
2126         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
2127     }
2128 
2129 
2130     /**
2131      * Add a new reminder when the user hits the "add reminder" button.  We use the default
2132      * reminder time and method.
2133      */
addReminder()2134     private void addReminder() {
2135         // TODO: when adding a new reminder, make it different from the
2136         // last one in the list (if any).
2137         if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
2138             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
2139                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2140                     mReminderMethodLabels,
2141                     ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders,
2142                     mReminderChangeListener);
2143         } else {
2144             EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
2145                     mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2146                     mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes),
2147                     mMaxReminders, mReminderChangeListener);
2148         }
2149 
2150         EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
2151     }
2152 
prepareReminders()2153     synchronized private void prepareReminders() {
2154         // Nothing to do if we've already built these lists _and_ we aren't
2155         // removing not allowed methods
2156         if (mReminderMinuteValues != null && mReminderMinuteLabels != null
2157                 && mReminderMethodValues != null && mReminderMethodLabels != null
2158                 && mCalendarAllowedReminders == null) {
2159             return;
2160         }
2161         // Load the labels and corresponding numeric values for the minutes and methods lists
2162         // from the assets.  If we're switching calendars, we need to clear and re-populate the
2163         // lists (which may have elements added and removed based on calendar properties).  This
2164         // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
2165         // new event that aren't in the default set.
2166         Resources r = mActivity.getResources();
2167         mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
2168         mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
2169         mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
2170         mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
2171 
2172         // Remove any reminder methods that aren't allowed for this calendar.  If this is
2173         // a new event, mCalendarAllowedReminders may not be set the first time we're called.
2174         if (mCalendarAllowedReminders != null) {
2175             EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
2176                     mCalendarAllowedReminders);
2177         }
2178         if (mView != null) {
2179             mView.invalidate();
2180         }
2181     }
2182 
2183 
saveReminders()2184     private boolean saveReminders() {
2185         ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
2186 
2187         // Read reminders from UI
2188         mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
2189                 mReminderMinuteValues, mReminderMethodValues);
2190         mOriginalReminders.addAll(mUnsupportedReminders);
2191         Collections.sort(mOriginalReminders);
2192         mReminders.addAll(mUnsupportedReminders);
2193         Collections.sort(mReminders);
2194 
2195         // Check if there are any changes in the reminder
2196         boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders,
2197                 mOriginalReminders, false /* no force save */);
2198 
2199         if (!changed) {
2200             return false;
2201         }
2202 
2203         // save new reminders
2204         AsyncQueryService service = new AsyncQueryService(getActivity());
2205         service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
2206         mOriginalReminders = mReminders;
2207         // Update the "hasAlarm" field for the event
2208         Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
2209         int len = mReminders.size();
2210         boolean hasAlarm = len > 0;
2211         if (hasAlarm != mHasAlarm) {
2212             ContentValues values = new ContentValues();
2213             values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
2214             service.startUpdate(0, null, uri, values, null, null, 0);
2215         }
2216         return true;
2217     }
2218 
2219     /**
2220      * Email all the attendees of the event, except for the viewer (so as to not email
2221      * himself) and resources like conference rooms.
2222      */
emailAttendees()2223     private void emailAttendees() {
2224         Intent i = new Intent(getActivity(), QuickResponseActivity.class);
2225         i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId);
2226         i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2227         startActivity(i);
2228     }
2229 
2230     /**
2231      * Loads an integer array asset into a list.
2232      */
loadIntegerArray(Resources r, int resNum)2233     private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
2234         int[] vals = r.getIntArray(resNum);
2235         int size = vals.length;
2236         ArrayList<Integer> list = new ArrayList<Integer>(size);
2237 
2238         for (int i = 0; i < size; i++) {
2239             list.add(vals[i]);
2240         }
2241 
2242         return list;
2243     }
2244     /**
2245      * Loads a String array asset into a list.
2246      */
loadStringArray(Resources r, int resNum)2247     private static ArrayList<String> loadStringArray(Resources r, int resNum) {
2248         String[] labels = r.getStringArray(resNum);
2249         ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
2250         return list;
2251     }
2252 
2253     @Override
onDeleteStarted()2254     public void onDeleteStarted() {
2255         mEventDeletionStarted = true;
2256     }
2257 
createDeleteOnDismissListener()2258     private Dialog.OnDismissListener createDeleteOnDismissListener() {
2259         return new Dialog.OnDismissListener() {
2260                     @Override
2261                     public void onDismiss(DialogInterface dialog) {
2262                         // Since OnPause will force the dialog to dismiss , do
2263                         // not change the dialog status
2264                         if (!mIsPaused) {
2265                             mDeleteDialogVisible = false;
2266                         }
2267                     }
2268                 };
2269     }
2270 
2271     public long getEventId() {
2272         return mEventId;
2273     }
2274 
2275     public long getStartMillis() {
2276         return mStartMillis;
2277     }
2278     public long getEndMillis() {
2279         return mEndMillis;
2280     }
2281     private void setDialogSize(Resources r) {
2282         mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
2283         mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
2284     }
2285 
2286     @Override
2287     public void onColorSelected(int color) {
2288         mCurrentColor = color;
2289         mCurrentColorKey = mDisplayColorKeyMap.get(color);
2290         mHeadlines.setBackgroundColor(color);
2291     }
2292 }
2293