EventInfoFragment.java revision 497a6259757e4b61ef8f15aa516284d5b0ac3451
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calendar;
18
19import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY;
20import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
21import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
22import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH;
23
24import android.animation.Animator;
25import android.animation.AnimatorListenerAdapter;
26import android.animation.ObjectAnimator;
27import android.app.Activity;
28import android.app.Dialog;
29import android.app.DialogFragment;
30import android.app.FragmentManager;
31import android.app.Service;
32import android.content.ActivityNotFoundException;
33import android.content.ContentProviderOperation;
34import android.content.ContentResolver;
35import android.content.ContentUris;
36import android.content.ContentValues;
37import android.content.Context;
38import android.content.DialogInterface;
39import android.content.Intent;
40import android.content.SharedPreferences;
41import android.content.pm.ApplicationInfo;
42import android.content.pm.PackageManager;
43import android.content.pm.PackageManager.NameNotFoundException;
44import android.content.res.Resources;
45import android.database.Cursor;
46import android.graphics.Color;
47import android.graphics.Rect;
48import android.graphics.drawable.Drawable;
49import android.net.Uri;
50import android.os.Bundle;
51import android.provider.CalendarContract;
52import android.provider.CalendarContract.Attendees;
53import android.provider.CalendarContract.Calendars;
54import android.provider.CalendarContract.Colors;
55import android.provider.CalendarContract.Events;
56import android.provider.CalendarContract.Reminders;
57import android.provider.ContactsContract;
58import android.provider.ContactsContract.CommonDataKinds;
59import android.provider.ContactsContract.Intents;
60import android.provider.ContactsContract.QuickContact;
61import android.text.Spannable;
62import android.text.SpannableStringBuilder;
63import android.text.TextUtils;
64import android.text.format.Time;
65import android.text.method.LinkMovementMethod;
66import android.text.method.MovementMethod;
67import android.text.style.ForegroundColorSpan;
68import android.text.util.Rfc822Token;
69import android.util.Log;
70import android.util.SparseIntArray;
71import android.view.Gravity;
72import android.view.LayoutInflater;
73import android.view.Menu;
74import android.view.MenuInflater;
75import android.view.MenuItem;
76import android.view.MotionEvent;
77import android.view.View;
78import android.view.View.OnClickListener;
79import android.view.View.OnTouchListener;
80import android.view.ViewGroup;
81import android.view.Window;
82import android.view.WindowManager;
83import android.view.accessibility.AccessibilityEvent;
84import android.view.accessibility.AccessibilityManager;
85import android.widget.AdapterView;
86import android.widget.AdapterView.OnItemSelectedListener;
87import android.widget.Button;
88import android.widget.LinearLayout;
89import android.widget.RadioButton;
90import android.widget.RadioGroup;
91import android.widget.RadioGroup.OnCheckedChangeListener;
92import android.widget.ScrollView;
93import android.widget.TextView;
94import android.widget.Toast;
95
96import com.android.calendar.CalendarController.EventInfo;
97import com.android.calendar.CalendarController.EventType;
98import com.android.calendar.CalendarEventModel.Attendee;
99import com.android.calendar.CalendarEventModel.ReminderEntry;
100import com.android.calendar.alerts.QuickResponseActivity;
101import com.android.calendar.event.AttendeesView;
102import com.android.calendar.event.EditEventActivity;
103import com.android.calendar.event.EditEventHelper;
104import com.android.calendar.event.EventColorPickerDialog;
105import com.android.calendar.event.EventViewUtils;
106import com.android.calendarcommon2.DateException;
107import com.android.calendarcommon2.Duration;
108import com.android.calendarcommon2.EventRecurrence;
109import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
110import com.android.colorpicker.HsvColorComparator;
111
112import java.util.ArrayList;
113import java.util.Arrays;
114import java.util.Collections;
115import java.util.List;
116
117public 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 {
450        public QueryHandler(Context context) {
451            super(context);
452        }
453
454        @Override
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
637    private void sendAccessibilityEventIfQueryDone(int token) {
638        mCurrentQuery |= token;
639        if (mCurrentQuery == TOKEN_QUERY_ALL) {
640            sendAccessibilityEvent();
641        }
642    }
643
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.
677    public EventInfoFragment() {
678    }
679
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
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
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
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
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
791    public void onNothingSelected(AdapterView<?> parent) {
792    }
793
794    @Override
795    public void onDetach() {
796        super.onDetach();
797        mController.deregisterEventHandler(R.layout.event_info);
798    }
799
800    @Override
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
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
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     */
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")
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
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
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
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
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
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
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
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     */
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
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     */
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
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
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
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
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
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
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
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
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     */
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
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     */
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
1898    private boolean hasEmailableOrganizer() {
1899        return mEventOrganizerEmail != null &&
1900                Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName);
1901    }
1902
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
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
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
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     */
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
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
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
2097    public void eventsChanged() {
2098    }
2099
2100    @Override
2101    public long getSupportedEventTypes() {
2102        return EventType.EVENTS_CHANGED;
2103    }
2104
2105    @Override
2106    public void handleEvent(EventInfo event) {
2107        reloadEvents();
2108    }
2109
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
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     */
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
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
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     */
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     */
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     */
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
2254    public void onDeleteStarted() {
2255        mEventDeletionStarted = true;
2256    }
2257
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