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 View mErrorMsgView;
362    private ObjectAnimator mAnimateAlpha;
363    private long mLoadingMsgStartTime;
364
365    private EventColorPickerDialog mColorPickerDialog;
366    private SparseIntArray mDisplayColorKeyMap = new SparseIntArray();
367    private int[] mColors;
368    private int mOriginalColor = -1;
369    private boolean mOriginalColorInitialized = false;
370    private int mCalendarColor = -1;
371    private boolean mCalendarColorInitialized = false;
372    private int mCurrentColor = -1;
373    private boolean mCurrentColorInitialized = false;
374    private int mCurrentColorKey = -1;
375
376    private static final int FADE_IN_TIME = 300;   // in milliseconds
377    private static final int LOADING_MSG_DELAY = 600;   // in milliseconds
378    private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600;
379    private boolean mNoCrossFade = false;  // Used to prevent repeated cross-fade
380    private RadioGroup mResponseRadioGroup;
381
382    ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
383    ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
384    ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
385    ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
386    ArrayList<String> mToEmails = new ArrayList<String>();
387    ArrayList<String> mCcEmails = new ArrayList<String>();
388
389    private int mDefaultReminderMinutes;
390    private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0);
391    public ArrayList<ReminderEntry> mReminders;
392    public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>();
393    public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>();
394    private boolean mUserModifiedReminders = false;
395
396    /**
397     * Contents of the "minutes" spinner.  This has default values from the XML file, augmented
398     * with any additional values that were already associated with the event.
399     */
400    private ArrayList<Integer> mReminderMinuteValues;
401    private ArrayList<String> mReminderMinuteLabels;
402
403    /**
404     * Contents of the "methods" spinner.  The "values" list specifies the method constant
405     * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels.  Any methods that
406     * aren't allowed by the Calendar will be removed.
407     */
408    private ArrayList<Integer> mReminderMethodValues;
409    private ArrayList<String> mReminderMethodLabels;
410
411    private QueryHandler mHandler;
412
413
414    private final Runnable mTZUpdater = new Runnable() {
415        @Override
416        public void run() {
417            updateEvent(mView);
418        }
419    };
420
421    private final Runnable mLoadingMsgAlphaUpdater = new Runnable() {
422        @Override
423        public void run() {
424            // Since this is run after a delay, make sure to only show the message
425            // if the event's data is not shown yet.
426            if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) {
427                mLoadingMsgStartTime = System.currentTimeMillis();
428                mLoadingMsgView.setAlpha(1);
429            }
430        }
431    };
432
433    private OnItemSelectedListener mReminderChangeListener;
434
435    private static int mDialogWidth = 500;
436    private static int mDialogHeight = 600;
437    private static int DIALOG_TOP_MARGIN = 8;
438    private boolean mIsDialog = false;
439    private boolean mIsPaused = true;
440    private boolean mDismissOnResume = false;
441    private int mX = -1;
442    private int mY = -1;
443    private int mMinTop;         // Dialog cannot be above this location
444    private boolean mIsTabletConfig;
445    private Activity mActivity;
446    private Context mContext;
447
448    private CalendarController mController;
449
450    private class QueryHandler extends AsyncQueryService {
451        public QueryHandler(Context context) {
452            super(context);
453        }
454
455        @Override
456        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
457            // if the activity is finishing, then close the cursor and return
458            final Activity activity = getActivity();
459            if (activity == null || activity.isFinishing()) {
460                if (cursor != null) {
461                    cursor.close();
462                }
463                return;
464            }
465
466            switch (token) {
467            case TOKEN_QUERY_EVENT:
468                mEventCursor = Utils.matrixCursorFromCursor(cursor);
469                if (!initEventCursor()) {
470                    displayEventNotFound();
471                    return;
472                }
473                if (!mCalendarColorInitialized) {
474                    mCalendarColor = Utils.getDisplayColorFromColor(
475                            mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR));
476                    mCalendarColorInitialized = true;
477                }
478
479                if (!mOriginalColorInitialized) {
480                    mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR)
481                            ? mCalendarColor : Utils.getDisplayColorFromColor(
482                                    mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR));
483                    mOriginalColorInitialized = true;
484                }
485
486                if (!mCurrentColorInitialized) {
487                    mCurrentColor = mOriginalColor;
488                    mCurrentColorInitialized = true;
489                }
490
491                updateEvent(mView);
492                prepareReminders();
493
494                // start calendar query
495                Uri uri = Calendars.CONTENT_URI;
496                String[] args = new String[] {
497                        Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
498                startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
499                        CALENDARS_WHERE, args, null);
500                break;
501            case TOKEN_QUERY_CALENDARS:
502                mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
503                updateCalendar(mView);
504                // FRAG_TODO fragments shouldn't set the title anymore
505                updateTitle();
506
507                args = new String[] {
508                        mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME),
509                        mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) };
510                uri = Colors.CONTENT_URI;
511                startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args,
512                        null);
513
514                if (!mIsBusyFreeCalendar) {
515                    args = new String[] { Long.toString(mEventId) };
516
517                    // start attendees query
518                    uri = Attendees.CONTENT_URI;
519                    startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
520                            ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
521                } else {
522                    sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
523                }
524                if (mHasAlarm) {
525                    // start reminders query
526                    args = new String[] { Long.toString(mEventId) };
527                    uri = Reminders.CONTENT_URI;
528                    startQuery(TOKEN_QUERY_REMINDERS, null, uri,
529                            REMINDERS_PROJECTION, REMINDERS_WHERE, args, null);
530                } else {
531                    sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS);
532                }
533                break;
534            case TOKEN_QUERY_COLORS:
535                ArrayList<Integer> colors = new ArrayList<Integer>();
536                if (cursor.moveToFirst()) {
537                    do
538                    {
539                        int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY);
540                        int rawColor = cursor.getInt(COLORS_INDEX_COLOR);
541                        int displayColor = Utils.getDisplayColorFromColor(rawColor);
542                        mDisplayColorKeyMap.put(displayColor, colorKey);
543                        colors.add(displayColor);
544                    } while (cursor.moveToNext());
545                }
546                cursor.close();
547                Integer[] sortedColors = new Integer[colors.size()];
548                Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator());
549                mColors = new int[sortedColors.length];
550                for (int i = 0; i < sortedColors.length; i++) {
551                    mColors[i] = sortedColors[i].intValue();
552
553                    float[] hsv = new float[3];
554                    Color.colorToHSV(mColors[i], hsv);
555                    if (DEBUG) {
556                        Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]);
557                    }
558                }
559                if (mCanModifyCalendar) {
560                    View button = mView.findViewById(R.id.change_color);
561                    if (button != null && mColors.length > 0) {
562                        button.setEnabled(true);
563                        button.setVisibility(View.VISIBLE);
564                    }
565                }
566                updateMenu();
567                break;
568            case TOKEN_QUERY_ATTENDEES:
569                mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
570                initAttendeesCursor(mView);
571                updateResponse(mView);
572                break;
573            case TOKEN_QUERY_REMINDERS:
574                mRemindersCursor = Utils.matrixCursorFromCursor(cursor);
575                initReminders(mView, mRemindersCursor);
576                break;
577            case TOKEN_QUERY_VISIBLE_CALENDARS:
578                if (cursor.getCount() > 1) {
579                    // Start duplicate calendars query to detect whether to add the calendar
580                    // email to the calendar owner display.
581                    String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
582                    mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null,
583                            Calendars.CONTENT_URI, CALENDARS_PROJECTION,
584                            CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null);
585                } else {
586                    // Don't need to display the calendar owner when there is only a single
587                    // calendar.  Skip the duplicate calendars query.
588                    setVisibilityCommon(mView, R.id.calendar_container, View.GONE);
589                    mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS;
590                }
591                break;
592            case TOKEN_QUERY_DUPLICATE_CALENDARS:
593                SpannableStringBuilder sb = new SpannableStringBuilder();
594
595                // Calendar display name
596                String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
597                sb.append(calendarName);
598
599                // Show email account if display name is not unique and
600                // display name != email
601                String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
602                if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) &&
603                        Utils.isValidEmail(email)) {
604                    sb.append(" (").append(email).append(")");
605                }
606
607                setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE);
608                setTextCommon(mView, R.id.calendar_name, sb);
609                break;
610            }
611            cursor.close();
612            sendAccessibilityEventIfQueryDone(token);
613
614            // All queries are done, show the view.
615            if (mCurrentQuery == TOKEN_QUERY_ALL) {
616                if (mLoadingMsgView.getAlpha() == 1) {
617                    // Loading message is showing, let it stay a bit more (to prevent
618                    // flashing) by adding a start delay to the event animation
619                    long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() -
620                            mLoadingMsgStartTime);
621                    if (timeDiff > 0) {
622                        mAnimateAlpha.setStartDelay(timeDiff);
623                    }
624                }
625                if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) {
626                    mAnimateAlpha.start();
627                } else {
628                    mScrollView.setAlpha(1);
629                    mLoadingMsgView.setVisibility(View.GONE);
630                }
631            }
632        }
633    }
634
635    private void sendAccessibilityEventIfQueryDone(int token) {
636        mCurrentQuery |= token;
637        if (mCurrentQuery == TOKEN_QUERY_ALL) {
638            sendAccessibilityEvent();
639        }
640    }
641
642    public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
643            int attendeeResponse, boolean isDialog, int windowStyle,
644            ArrayList<ReminderEntry> reminders) {
645
646        Resources r = context.getResources();
647        if (mScale == 0) {
648            mScale = context.getResources().getDisplayMetrics().density;
649            if (mScale != 1) {
650                mCustomAppIconSize *= mScale;
651                if (isDialog) {
652                    DIALOG_TOP_MARGIN *= mScale;
653                }
654            }
655        }
656        if (isDialog) {
657            setDialogSize(r);
658        }
659        mIsDialog = isDialog;
660
661        setStyle(DialogFragment.STYLE_NO_TITLE, 0);
662        mUri = uri;
663        mStartMillis = startMillis;
664        mEndMillis = endMillis;
665        mAttendeeResponseFromIntent = attendeeResponse;
666        mWindowStyle = windowStyle;
667
668        // Pass in null if no reminders are being specified.
669        // This may be used to explicitly show certain reminders already known
670        // about, such as during configuration changes.
671        mReminders = reminders;
672    }
673
674    // This is currently required by the fragment manager.
675    public EventInfoFragment() {
676    }
677
678    public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
679            int attendeeResponse, boolean isDialog, int windowStyle,
680            ArrayList<ReminderEntry> reminders) {
681        this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
682                endMillis, attendeeResponse, isDialog, windowStyle, reminders);
683        mEventId = eventId;
684    }
685
686    @Override
687    public void onActivityCreated(Bundle savedInstanceState) {
688        super.onActivityCreated(savedInstanceState);
689
690        mReminderChangeListener = new OnItemSelectedListener() {
691            @Override
692            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
693                Integer prevValue = (Integer) parent.getTag();
694                if (prevValue == null || prevValue != position) {
695                    parent.setTag(position);
696                    mUserModifiedReminders = true;
697                }
698            }
699
700            @Override
701            public void onNothingSelected(AdapterView<?> parent) {
702                // do nothing
703            }
704
705        };
706
707        if (savedInstanceState != null) {
708            mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
709            mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
710                    DIALOG_WINDOW_STYLE);
711        }
712
713        if (mIsDialog) {
714            applyDialogParams();
715        }
716
717        final Activity activity = getActivity();
718        mContext = activity;
719        mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager()
720                .findFragmentByTag(COLOR_PICKER_DIALOG_TAG);
721        if (mColorPickerDialog != null) {
722            mColorPickerDialog.setOnColorSelectedListener(this);
723        }
724    }
725
726    private void applyDialogParams() {
727        Dialog dialog = getDialog();
728        dialog.setCanceledOnTouchOutside(true);
729
730        Window window = dialog.getWindow();
731        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
732
733        WindowManager.LayoutParams a = window.getAttributes();
734        a.dimAmount = .4f;
735
736        a.width = mDialogWidth;
737        a.height = mDialogHeight;
738
739
740        // On tablets , do smart positioning of dialog
741        // On phones , use the whole screen
742
743        if (mX != -1 || mY != -1) {
744            a.x = mX - mDialogWidth / 2;
745            a.y = mY - mDialogHeight / 2;
746            if (a.y < mMinTop) {
747                a.y = mMinTop + DIALOG_TOP_MARGIN;
748            }
749            a.gravity = Gravity.LEFT | Gravity.TOP;
750        }
751        window.setAttributes(a);
752    }
753
754    public void setDialogParams(int x, int y, int minTop) {
755        mX = x;
756        mY = y;
757        mMinTop = minTop;
758    }
759
760    // Implements OnCheckedChangeListener
761    @Override
762    public void onCheckedChanged(RadioGroup group, int checkedId) {
763        // If we haven't finished the return from the dialog yet, don't display.
764        if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
765            return;
766        }
767
768        // If this is not a repeating event, then don't display the dialog
769        // asking which events to change.
770        int response = getResponseFromButtonId(checkedId);
771        if (!mIsRepeating) {
772            mUserSetResponse = response;
773            return;
774        }
775
776        // If the selection is the same as the original, then don't display the
777        // dialog asking which events to change.
778        if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
779            mUserSetResponse = response;
780            return;
781        }
782
783        // This is a repeating event. We need to ask the user if they mean to
784        // change just this one instance or all instances.
785        mTentativeUserSetResponse = response;
786        mEditResponseHelper.showDialog(mWhichEvents);
787    }
788
789    public void onNothingSelected(AdapterView<?> parent) {
790    }
791
792    @Override
793    public void onDetach() {
794        super.onDetach();
795        mController.deregisterEventHandler(R.layout.event_info);
796    }
797
798    @Override
799    public void onAttach(Activity activity) {
800        super.onAttach(activity);
801        mActivity = activity;
802        // Ensure that mIsTabletConfig is set before creating the menu.
803        mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config);
804        mController = CalendarController.getInstance(mActivity);
805        mController.registerEventHandler(R.layout.event_info, this);
806        mEditResponseHelper = new EditResponseHelper(activity);
807        mEditResponseHelper.setDismissListener(
808                new DialogInterface.OnDismissListener() {
809            @Override
810            public void onDismiss(DialogInterface dialog) {
811                // If the user dismisses the dialog (without hitting OK),
812                // then we want to revert the selection that opened the dialog.
813                if (mEditResponseHelper.getWhichEvents() != -1) {
814                    mUserSetResponse = mTentativeUserSetResponse;
815                    mWhichEvents = mEditResponseHelper.getWhichEvents();
816                } else {
817                    // Revert the attending response radio selection to whatever
818                    // was selected prior to this selection (possibly nothing).
819                    int oldResponse;
820                    if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
821                        oldResponse = mUserSetResponse;
822                    } else {
823                        oldResponse = mOriginalAttendeeResponse;
824                    }
825                    int buttonToCheck = findButtonIdForResponse(oldResponse);
826
827                    if (mResponseRadioGroup != null) {
828                        mResponseRadioGroup.check(buttonToCheck);
829                    }
830
831                    // If the radio group is being cleared, also clear the
832                    // dialog's selection of which events should be included
833                    // in this response.
834                    if (buttonToCheck == -1) {
835                        mEditResponseHelper.setWhichEvents(-1);
836                    }
837                }
838
839                // Since OnPause will force the dialog to dismiss, do
840                // not change the dialog status
841                if (!mIsPaused) {
842                    mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE;
843                }
844            }
845        });
846
847        if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
848            mEditResponseHelper.setWhichEvents(UPDATE_ALL);
849            mWhichEvents = mEditResponseHelper.getWhichEvents();
850        }
851        mHandler = new QueryHandler(activity);
852        if (!mIsDialog) {
853            setHasOptionsMenu(true);
854        }
855    }
856
857    @Override
858    public View onCreateView(LayoutInflater inflater, ViewGroup container,
859            Bundle savedInstanceState) {
860
861        if (savedInstanceState != null) {
862            mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
863            mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE,
864                    DIALOG_WINDOW_STYLE);
865            mDeleteDialogVisible =
866                savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false);
867            mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR);
868            mCalendarColorInitialized =
869                    savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT);
870            mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR);
871            mOriginalColorInitialized = savedInstanceState.getBoolean(
872                    BUNDLE_KEY_ORIGINAL_COLOR_INIT);
873            mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR);
874            mCurrentColorInitialized = savedInstanceState.getBoolean(
875                    BUNDLE_KEY_CURRENT_COLOR_INIT);
876            mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY);
877
878            mTentativeUserSetResponse = savedInstanceState.getInt(
879                            BUNDLE_KEY_TENTATIVE_USER_RESPONSE,
880                            Attendees.ATTENDEE_STATUS_NONE);
881            if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE &&
882                    mEditResponseHelper != null) {
883                // If the edit response helper dialog is open, we'll need to
884                // know if either of the choices were selected.
885                mEditResponseHelper.setWhichEvents(savedInstanceState.getInt(
886                        BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1));
887            }
888            mUserSetResponse = savedInstanceState.getInt(
889                    BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE,
890                    Attendees.ATTENDEE_STATUS_NONE);
891            if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
892                // If the response was set by the user before a configuration
893                // change, we'll need to know which choice was selected.
894                mWhichEvents = savedInstanceState.getInt(
895                        BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1);
896            }
897
898            mReminders = Utils.readRemindersFromBundle(savedInstanceState);
899        }
900
901        if (mWindowStyle == DIALOG_WINDOW_STYLE) {
902            mView = inflater.inflate(R.layout.event_info_dialog, container, false);
903        } else {
904            mView = inflater.inflate(R.layout.event_info, container, false);
905        }
906        mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view);
907        mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg);
908        mErrorMsgView = mView.findViewById(R.id.event_info_error_msg);
909        mTitle = (TextView) mView.findViewById(R.id.title);
910        mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime);
911        mWhere = (TextView) mView.findViewById(R.id.where);
912        mDesc = (ExpandableTextView) mView.findViewById(R.id.description);
913        mHeadlines = mView.findViewById(R.id.event_info_headline);
914        mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list);
915
916        mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value);
917
918        if (mUri == null) {
919            // restore event ID from bundle
920            mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
921            mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
922            mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
923            mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
924        }
925
926        mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1);
927        mAnimateAlpha.setDuration(FADE_IN_TIME);
928        mAnimateAlpha.addListener(new AnimatorListenerAdapter() {
929            int defLayerType;
930
931            @Override
932            public void onAnimationStart(Animator animation) {
933                // Use hardware layer for better performance during animation
934                defLayerType = mScrollView.getLayerType();
935                mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
936                // Ensure that the loading message is gone before showing the
937                // event info
938                mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater);
939                mLoadingMsgView.setVisibility(View.GONE);
940            }
941
942            @Override
943            public void onAnimationCancel(Animator animation) {
944                mScrollView.setLayerType(defLayerType, null);
945            }
946
947            @Override
948            public void onAnimationEnd(Animator animation) {
949                mScrollView.setLayerType(defLayerType, null);
950                // Do not cross fade after the first time
951                mNoCrossFade = true;
952            }
953        });
954
955        mLoadingMsgView.setAlpha(0);
956        mScrollView.setAlpha(0);
957        mErrorMsgView.setVisibility(View.INVISIBLE);
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 false if the cursor is empty, true otherwise
1056     */
1057    private boolean initEventCursor() {
1058        if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
1059            return false;
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 true;
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 displayEventNotFound() {
1462        mErrorMsgView.setVisibility(View.VISIBLE);
1463        mScrollView.setVisibility(View.GONE);
1464        mLoadingMsgView.setVisibility(View.GONE);
1465    }
1466
1467    private void updateEvent(View view) {
1468        if (mEventCursor == null || view == null) {
1469            return;
1470        }
1471
1472        Context context = view.getContext();
1473        if (context == null) {
1474            return;
1475        }
1476
1477        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
1478        if (eventName == null || eventName.length() == 0) {
1479            eventName = getActivity().getString(R.string.no_title_label);
1480        }
1481
1482        // 3rd parties might not have specified the start/end time when firing the
1483        // Events.CONTENT_URI intent.  Update these with values read from the db.
1484        if (mStartMillis == 0 && mEndMillis == 0) {
1485            mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART);
1486            mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND);
1487            if (mEndMillis == 0) {
1488                String duration = mEventCursor.getString(EVENT_INDEX_DURATION);
1489                if (!TextUtils.isEmpty(duration)) {
1490                    try {
1491                        Duration d = new Duration();
1492                        d.parse(duration);
1493                        long endMillis = mStartMillis + d.getMillis();
1494                        if (endMillis >= mStartMillis) {
1495                            mEndMillis = endMillis;
1496                        } else {
1497                            Log.d(TAG, "Invalid duration string: " + duration);
1498                        }
1499                    } catch (DateException e) {
1500                        Log.d(TAG, "Error parsing duration string " + duration, e);
1501                    }
1502                }
1503                if (mEndMillis == 0) {
1504                    mEndMillis = mStartMillis;
1505                }
1506            }
1507        }
1508
1509        mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
1510        String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
1511        String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
1512        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
1513        String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
1514
1515        mHeadlines.setBackgroundColor(mCurrentColor);
1516
1517        // What
1518        if (eventName != null) {
1519            setTextCommon(view, R.id.title, eventName);
1520        }
1521
1522        // When
1523        // Set the date and repeats (if any)
1524        String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater);
1525
1526        Resources resources = context.getResources();
1527        String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis,
1528                System.currentTimeMillis(), localTimezone, mAllDay, context);
1529
1530        String displayedTimezone = null;
1531        if (!mAllDay) {
1532            displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone,
1533                    eventTimezone);
1534        }
1535        // Display the datetime.  Make the timezone (if any) transparent.
1536        if (displayedTimezone == null) {
1537            setTextCommon(view, R.id.when_datetime, displayedDatetime);
1538        } else {
1539            int timezoneIndex = displayedDatetime.length();
1540            displayedDatetime += "  " + displayedTimezone;
1541            SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime);
1542            ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan(
1543                    resources.getColor(R.color.event_info_headline_transparent_color));
1544            sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(),
1545                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
1546            setTextCommon(view, R.id.when_datetime, sb);
1547        }
1548
1549        // Display the repeat string (if any)
1550        String repeatString = null;
1551        if (!TextUtils.isEmpty(rRule)) {
1552            EventRecurrence eventRecurrence = new EventRecurrence();
1553            eventRecurrence.parse(rRule);
1554            Time date = new Time(localTimezone);
1555            date.set(mStartMillis);
1556            if (mAllDay) {
1557                date.timezone = Time.TIMEZONE_UTC;
1558            }
1559            eventRecurrence.setStartDate(date);
1560            repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources,
1561                    eventRecurrence, true);
1562        }
1563        if (repeatString == null) {
1564            view.findViewById(R.id.when_repeat).setVisibility(View.GONE);
1565        } else {
1566            setTextCommon(view, R.id.when_repeat, repeatString);
1567        }
1568
1569        // Organizer view is setup in the updateCalendar method
1570
1571
1572        // Where
1573        if (location == null || location.trim().length() == 0) {
1574            setVisibilityCommon(view, R.id.where, View.GONE);
1575        } else {
1576            final TextView textView = mWhere;
1577            if (textView != null) {
1578                textView.setAutoLinkMask(0);
1579                textView.setText(location.trim());
1580                try {
1581                    textView.setText(Utils.extendedLinkify(textView.getText().toString(), true));
1582
1583                    // Linkify.addLinks() sets the TextView movement method if it finds any links.
1584                    // We must do the same here, in case linkify by itself did not find any.
1585                    // (This is cloned from Linkify.addLinkMovementMethod().)
1586                    MovementMethod mm = textView.getMovementMethod();
1587                    if ((mm == null) || !(mm instanceof LinkMovementMethod)) {
1588                        if (textView.getLinksClickable()) {
1589                            textView.setMovementMethod(LinkMovementMethod.getInstance());
1590                        }
1591                    }
1592                } catch (Exception ex) {
1593                    // unexpected
1594                    Log.e(TAG, "Linkification failed", ex);
1595                }
1596
1597                textView.setOnTouchListener(new OnTouchListener() {
1598                    @Override
1599                    public boolean onTouch(View v, MotionEvent event) {
1600                        try {
1601                            return v.onTouchEvent(event);
1602                        } catch (ActivityNotFoundException e) {
1603                            // ignore
1604                            return true;
1605                        }
1606                    }
1607                });
1608            }
1609        }
1610
1611        // Description
1612        if (description != null && description.length() != 0) {
1613            mDesc.setText(description);
1614        }
1615
1616        // Launch Custom App
1617        if (Utils.isJellybeanOrLater()) {
1618            updateCustomAppButton();
1619        }
1620    }
1621
1622    private void updateCustomAppButton() {
1623        buttonSetup: {
1624            final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button);
1625            if (launchButton == null)
1626                break buttonSetup;
1627
1628            final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE);
1629            final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI);
1630
1631            if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri))
1632                break buttonSetup;
1633
1634            PackageManager pm = mContext.getPackageManager();
1635            if (pm == null)
1636                break buttonSetup;
1637
1638            ApplicationInfo info;
1639            try {
1640                info = pm.getApplicationInfo(customAppPackage, 0);
1641                if (info == null)
1642                    break buttonSetup;
1643            } catch (NameNotFoundException e) {
1644                break buttonSetup;
1645            }
1646
1647            Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
1648            final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri);
1649            intent.setPackage(customAppPackage);
1650            intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri);
1651            intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis);
1652
1653            // See if we have a taker for our intent
1654            if (pm.resolveActivity(intent, 0) == null)
1655                break buttonSetup;
1656
1657            Drawable icon = pm.getApplicationIcon(info);
1658            if (icon != null) {
1659
1660                Drawable[] d = launchButton.getCompoundDrawables();
1661                icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize);
1662                launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]);
1663            }
1664
1665            CharSequence label = pm.getApplicationLabel(info);
1666            if (label != null && label.length() != 0) {
1667                launchButton.setText(label);
1668            } else if (icon == null) {
1669                // No icon && no label. Hide button?
1670                break buttonSetup;
1671            }
1672
1673            // Launch custom app
1674            launchButton.setOnClickListener(new View.OnClickListener() {
1675                @Override
1676                public void onClick(View v) {
1677                    try {
1678                        startActivityForResult(intent, 0);
1679                    } catch (ActivityNotFoundException e) {
1680                        // Shouldn't happen as we checked it already
1681                        setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1682                    }
1683                }
1684            });
1685
1686            setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE);
1687            return;
1688
1689        }
1690
1691        setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE);
1692        return;
1693    }
1694
1695    private void sendAccessibilityEvent() {
1696        AccessibilityManager am =
1697            (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE);
1698        if (!am.isEnabled()) {
1699            return;
1700        }
1701
1702        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1703        event.setClassName(EventInfoFragment.class.getName());
1704        event.setPackageName(getActivity().getPackageName());
1705        List<CharSequence> text = event.getText();
1706
1707        addFieldToAccessibilityEvent(text, mTitle, null);
1708        addFieldToAccessibilityEvent(text, mWhenDateTime, null);
1709        addFieldToAccessibilityEvent(text, mWhere, null);
1710        addFieldToAccessibilityEvent(text, null, mDesc);
1711
1712        if (mResponseRadioGroup.getVisibility() == View.VISIBLE) {
1713            int id = mResponseRadioGroup.getCheckedRadioButtonId();
1714            if (id != View.NO_ID) {
1715                text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
1716                text.add((((RadioButton) (mResponseRadioGroup.findViewById(id)))
1717                        .getText() + PERIOD_SPACE));
1718            }
1719        }
1720
1721        am.sendAccessibilityEvent(event);
1722    }
1723
1724    private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv,
1725            ExpandableTextView etv) {
1726        CharSequence cs;
1727        if (tv != null) {
1728            cs = tv.getText();
1729        } else if (etv != null) {
1730            cs = etv.getText();
1731        } else {
1732            return;
1733        }
1734
1735        if (!TextUtils.isEmpty(cs)) {
1736            cs = cs.toString().trim();
1737            if (cs.length() > 0) {
1738                text.add(cs);
1739                text.add(PERIOD_SPACE);
1740            }
1741        }
1742    }
1743
1744    private void updateCalendar(View view) {
1745
1746        mCalendarOwnerAccount = "";
1747        if (mCalendarsCursor != null && mEventCursor != null) {
1748            mCalendarsCursor.moveToFirst();
1749            String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
1750            mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
1751            mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
1752            mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME);
1753
1754            // start visible calendars query
1755            mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI,
1756                    CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null);
1757
1758            mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
1759            mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail);
1760
1761            if (!TextUtils.isEmpty(mEventOrganizerEmail) &&
1762                    !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) {
1763                mEventOrganizerDisplayName = mEventOrganizerEmail;
1764            }
1765
1766            if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) {
1767                setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName);
1768                setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
1769            } else {
1770                setVisibilityCommon(view, R.id.organizer_container, View.GONE);
1771            }
1772            mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
1773            mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL)
1774                    >= Calendars.CAL_ACCESS_CONTRIBUTOR;
1775            // TODO add "|| guestCanModify" after b/1299071 is fixed
1776            mCanModifyEvent = mCanModifyCalendar && mIsOrganizer;
1777            mIsBusyFreeCalendar =
1778                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY;
1779
1780            if (!mIsBusyFreeCalendar) {
1781
1782                View b = mView.findViewById(R.id.edit);
1783                b.setEnabled(true);
1784                b.setOnClickListener(new OnClickListener() {
1785                    @Override
1786                    public void onClick(View v) {
1787                        doEdit();
1788                        // For dialogs, just close the fragment
1789                        // For full screen, close activity on phone, leave it for tablet
1790                        if (mIsDialog) {
1791                            EventInfoFragment.this.dismiss();
1792                        }
1793                        else if (!mIsTabletConfig){
1794                            getActivity().finish();
1795                        }
1796                    }
1797                });
1798            }
1799            View button;
1800            if (mCanModifyCalendar) {
1801                button = mView.findViewById(R.id.delete);
1802                if (button != null) {
1803                    button.setEnabled(true);
1804                    button.setVisibility(View.VISIBLE);
1805                }
1806            }
1807            if (mCanModifyEvent) {
1808                button = mView.findViewById(R.id.edit);
1809                if (button != null) {
1810                    button.setEnabled(true);
1811                    button.setVisibility(View.VISIBLE);
1812                }
1813            }
1814            if ((!mIsDialog && !mIsTabletConfig ||
1815                    mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) {
1816                mActivity.invalidateOptionsMenu();
1817            }
1818        } else {
1819            setVisibilityCommon(view, R.id.calendar, View.GONE);
1820            sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
1821        }
1822    }
1823
1824    /**
1825     *
1826     */
1827    private void updateMenu() {
1828        if (mMenu == null) {
1829            return;
1830        }
1831        MenuItem delete = mMenu.findItem(R.id.info_action_delete);
1832        MenuItem edit = mMenu.findItem(R.id.info_action_edit);
1833        MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color);
1834        if (delete != null) {
1835            delete.setVisible(mCanModifyCalendar);
1836            delete.setEnabled(mCanModifyCalendar);
1837        }
1838        if (edit != null) {
1839            edit.setVisible(mCanModifyEvent);
1840            edit.setEnabled(mCanModifyEvent);
1841        }
1842        if (changeColor != null && mColors != null && mColors.length > 0) {
1843            changeColor.setVisible(mCanModifyCalendar);
1844            changeColor.setEnabled(mCanModifyCalendar);
1845        }
1846    }
1847
1848    private void updateAttendees(View view) {
1849        if (mAcceptedAttendees.size() + mDeclinedAttendees.size() +
1850                mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) {
1851            mLongAttendees.clearAttendees();
1852            (mLongAttendees).addAttendees(mAcceptedAttendees);
1853            (mLongAttendees).addAttendees(mDeclinedAttendees);
1854            (mLongAttendees).addAttendees(mTentativeAttendees);
1855            (mLongAttendees).addAttendees(mNoResponseAttendees);
1856            mLongAttendees.setEnabled(false);
1857            mLongAttendees.setVisibility(View.VISIBLE);
1858        } else {
1859            mLongAttendees.setVisibility(View.GONE);
1860        }
1861
1862        if (hasEmailableAttendees()) {
1863            setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
1864            if (emailAttendeesButton != null) {
1865                emailAttendeesButton.setText(R.string.email_guests_label);
1866            }
1867        } else if (hasEmailableOrganizer()) {
1868            setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE);
1869            if (emailAttendeesButton != null) {
1870                emailAttendeesButton.setText(R.string.email_organizer_label);
1871            }
1872        } else {
1873            setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE);
1874        }
1875    }
1876
1877    /**
1878     * Returns true if there is at least 1 attendee that is not the viewer.
1879     */
1880    private boolean hasEmailableAttendees() {
1881        for (Attendee attendee : mAcceptedAttendees) {
1882            if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1883                return true;
1884            }
1885        }
1886        for (Attendee attendee : mTentativeAttendees) {
1887            if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1888                return true;
1889            }
1890        }
1891        for (Attendee attendee : mNoResponseAttendees) {
1892            if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1893                return true;
1894            }
1895        }
1896        for (Attendee attendee : mDeclinedAttendees) {
1897            if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) {
1898                return true;
1899            }
1900        }
1901        return false;
1902    }
1903
1904    private boolean hasEmailableOrganizer() {
1905        return mEventOrganizerEmail != null &&
1906                Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName);
1907    }
1908
1909    public void initReminders(View view, Cursor cursor) {
1910
1911        // Add reminders
1912        mOriginalReminders.clear();
1913        mUnsupportedReminders.clear();
1914        while (cursor.moveToNext()) {
1915            int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES);
1916            int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD);
1917
1918            if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) {
1919                // Stash unsupported reminder types separately so we don't alter
1920                // them in the UI
1921                mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method));
1922            } else {
1923                mOriginalReminders.add(ReminderEntry.valueOf(minutes, method));
1924            }
1925        }
1926        // Sort appropriately for display (by time, then type)
1927        Collections.sort(mOriginalReminders);
1928
1929        if (mUserModifiedReminders) {
1930            // If the user has changed the list of reminders don't change what's
1931            // shown.
1932            return;
1933        }
1934
1935        LinearLayout parent = (LinearLayout) mScrollView
1936                .findViewById(R.id.reminder_items_container);
1937        if (parent != null) {
1938            parent.removeAllViews();
1939        }
1940        if (mReminderViews != null) {
1941            mReminderViews.clear();
1942        }
1943
1944        if (mHasAlarm) {
1945            ArrayList<ReminderEntry> reminders;
1946            // If applicable, use reminders saved in the bundle.
1947            if (mReminders != null) {
1948                reminders = mReminders;
1949            } else {
1950                reminders = mOriginalReminders;
1951            }
1952            // Insert any minute values that aren't represented in the minutes list.
1953            for (ReminderEntry re : reminders) {
1954                EventViewUtils.addMinutesToList(
1955                        mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes());
1956            }
1957            // Create a UI element for each reminder.  We display all of the reminders we get
1958            // from the provider, even if the count exceeds the calendar maximum.  (Also, for
1959            // a new event, we won't have a maxReminders value available.)
1960            for (ReminderEntry re : reminders) {
1961                EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
1962                        mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
1963                        mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener);
1964            }
1965            EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
1966            // TODO show unsupported reminder types in some fashion.
1967        }
1968    }
1969
1970    void updateResponse(View view) {
1971        // we only let the user accept/reject/etc. a meeting if:
1972        // a) you can edit the event's containing calendar AND
1973        // b) you're not the organizer and only attendee AND
1974        // c) organizerCanRespond is enabled for the calendar
1975        // (if the attendee data has been hidden, the visible number of attendees
1976        // will be 1 -- the calendar owner's).
1977        // (there are more cases involved to be 100% accurate, such as
1978        // paying attention to whether or not an attendee status was
1979        // included in the feed, but we're currently omitting those corner cases
1980        // for simplicity).
1981
1982        // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
1983        if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
1984                (mIsOrganizer && !mOwnerCanRespond)) {
1985            setVisibilityCommon(view, R.id.response_container, View.GONE);
1986            return;
1987        }
1988
1989        setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
1990
1991
1992        int response;
1993        if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
1994            response = mTentativeUserSetResponse;
1995        } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
1996            response = mUserSetResponse;
1997        } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) {
1998            response = mAttendeeResponseFromIntent;
1999        } else {
2000            response = mOriginalAttendeeResponse;
2001        }
2002
2003        int buttonToCheck = findButtonIdForResponse(response);
2004        mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons
2005        mResponseRadioGroup.setOnCheckedChangeListener(this);
2006    }
2007
2008    private void setTextCommon(View view, int id, CharSequence text) {
2009        TextView textView = (TextView) view.findViewById(id);
2010        if (textView == null)
2011            return;
2012        textView.setText(text);
2013    }
2014
2015    private void setVisibilityCommon(View view, int id, int visibility) {
2016        View v = view.findViewById(id);
2017        if (v != null) {
2018            v.setVisibility(visibility);
2019        }
2020        return;
2021    }
2022
2023    /**
2024     * Taken from com.google.android.gm.HtmlConversationActivity
2025     *
2026     * Send the intent that shows the Contact info corresponding to the email address.
2027     */
2028    public void showContactInfo(Attendee attendee, Rect rect) {
2029        // First perform lookup query to find existing contact
2030        final ContentResolver resolver = getActivity().getContentResolver();
2031        final String address = attendee.mEmail;
2032        final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
2033                Uri.encode(address));
2034        final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
2035
2036        if (lookupUri != null) {
2037            // Found matching contact, trigger QuickContact
2038            QuickContact.showQuickContact(getActivity(), rect, lookupUri,
2039                    QuickContact.MODE_MEDIUM, null);
2040        } else {
2041            // No matching contact, ask user to create one
2042            final Uri mailUri = Uri.fromParts("mailto", address, null);
2043            final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
2044
2045            // Pass along full E-mail string for possible create dialog
2046            Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
2047            intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
2048
2049            // Only provide personal name hint if we have one
2050            final String senderPersonal = attendee.mName;
2051            if (!TextUtils.isEmpty(senderPersonal)) {
2052                intent.putExtra(Intents.Insert.NAME, senderPersonal);
2053            }
2054
2055            startActivity(intent);
2056        }
2057    }
2058
2059    @Override
2060    public void onPause() {
2061        mIsPaused = true;
2062        mHandler.removeCallbacks(onDeleteRunnable);
2063        super.onPause();
2064        // Remove event deletion alert box since it is being rebuild in the OnResume
2065        // This is done to get the same behavior on OnResume since the AlertDialog is gone on
2066        // rotation but not if you press the HOME key
2067        if (mDeleteDialogVisible && mDeleteHelper != null) {
2068            mDeleteHelper.dismissAlertDialog();
2069            mDeleteHelper = null;
2070        }
2071        if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE
2072                && mEditResponseHelper != null) {
2073            mEditResponseHelper.dismissAlertDialog();
2074        }
2075    }
2076
2077    @Override
2078    public void onResume() {
2079        super.onResume();
2080        if (mIsDialog) {
2081            setDialogSize(getActivity().getResources());
2082            applyDialogParams();
2083        }
2084        mIsPaused = false;
2085        if (mDismissOnResume) {
2086            mHandler.post(onDeleteRunnable);
2087        }
2088        // Display the "delete confirmation" or "edit response helper" dialog if needed
2089        if (mDeleteDialogVisible) {
2090            mDeleteHelper = new DeleteEventHelper(
2091                    mContext, mActivity,
2092                    !mIsDialog && !mIsTabletConfig /* exitWhenDone */);
2093            mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener());
2094            mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
2095        } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) {
2096            int buttonId = findButtonIdForResponse(mTentativeUserSetResponse);
2097            mResponseRadioGroup.check(buttonId);
2098            mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
2099        }
2100    }
2101
2102    @Override
2103    public void eventsChanged() {
2104    }
2105
2106    @Override
2107    public long getSupportedEventTypes() {
2108        return EventType.EVENTS_CHANGED;
2109    }
2110
2111    @Override
2112    public void handleEvent(EventInfo event) {
2113        reloadEvents();
2114    }
2115
2116    public void reloadEvents() {
2117        if (mHandler != null) {
2118            mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
2119                    null, null, null);
2120        }
2121    }
2122
2123    @Override
2124    public void onClick(View view) {
2125
2126        // This must be a click on one of the "remove reminder" buttons
2127        LinearLayout reminderItem = (LinearLayout) view.getParent();
2128        LinearLayout parent = (LinearLayout) reminderItem.getParent();
2129        parent.removeView(reminderItem);
2130        mReminderViews.remove(reminderItem);
2131        mUserModifiedReminders = true;
2132        EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
2133    }
2134
2135
2136    /**
2137     * Add a new reminder when the user hits the "add reminder" button.  We use the default
2138     * reminder time and method.
2139     */
2140    private void addReminder() {
2141        // TODO: when adding a new reminder, make it different from the
2142        // last one in the list (if any).
2143        if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) {
2144            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
2145                    mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2146                    mReminderMethodLabels,
2147                    ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders,
2148                    mReminderChangeListener);
2149        } else {
2150            EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews,
2151                    mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues,
2152                    mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes),
2153                    mMaxReminders, mReminderChangeListener);
2154        }
2155
2156        EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders);
2157    }
2158
2159    synchronized private void prepareReminders() {
2160        // Nothing to do if we've already built these lists _and_ we aren't
2161        // removing not allowed methods
2162        if (mReminderMinuteValues != null && mReminderMinuteLabels != null
2163                && mReminderMethodValues != null && mReminderMethodLabels != null
2164                && mCalendarAllowedReminders == null) {
2165            return;
2166        }
2167        // Load the labels and corresponding numeric values for the minutes and methods lists
2168        // from the assets.  If we're switching calendars, we need to clear and re-populate the
2169        // lists (which may have elements added and removed based on calendar properties).  This
2170        // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a
2171        // new event that aren't in the default set.
2172        Resources r = mActivity.getResources();
2173        mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values);
2174        mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels);
2175        mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values);
2176        mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels);
2177
2178        // Remove any reminder methods that aren't allowed for this calendar.  If this is
2179        // a new event, mCalendarAllowedReminders may not be set the first time we're called.
2180        if (mCalendarAllowedReminders != null) {
2181            EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels,
2182                    mCalendarAllowedReminders);
2183        }
2184        if (mView != null) {
2185            mView.invalidate();
2186        }
2187    }
2188
2189
2190    private boolean saveReminders() {
2191        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3);
2192
2193        // Read reminders from UI
2194        mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews,
2195                mReminderMinuteValues, mReminderMethodValues);
2196        mOriginalReminders.addAll(mUnsupportedReminders);
2197        Collections.sort(mOriginalReminders);
2198        mReminders.addAll(mUnsupportedReminders);
2199        Collections.sort(mReminders);
2200
2201        // Check if there are any changes in the reminder
2202        boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders,
2203                mOriginalReminders, false /* no force save */);
2204
2205        if (!changed) {
2206            return false;
2207        }
2208
2209        // save new reminders
2210        AsyncQueryService service = new AsyncQueryService(getActivity());
2211        service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0);
2212        mOriginalReminders = mReminders;
2213        // Update the "hasAlarm" field for the event
2214        Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
2215        int len = mReminders.size();
2216        boolean hasAlarm = len > 0;
2217        if (hasAlarm != mHasAlarm) {
2218            ContentValues values = new ContentValues();
2219            values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0);
2220            service.startUpdate(0, null, uri, values, null, null, 0);
2221        }
2222        return true;
2223    }
2224
2225    /**
2226     * Email all the attendees of the event, except for the viewer (so as to not email
2227     * himself) and resources like conference rooms.
2228     */
2229    private void emailAttendees() {
2230        Intent i = new Intent(getActivity(), QuickResponseActivity.class);
2231        i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId);
2232        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2233        startActivity(i);
2234    }
2235
2236    /**
2237     * Loads an integer array asset into a list.
2238     */
2239    private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) {
2240        int[] vals = r.getIntArray(resNum);
2241        int size = vals.length;
2242        ArrayList<Integer> list = new ArrayList<Integer>(size);
2243
2244        for (int i = 0; i < size; i++) {
2245            list.add(vals[i]);
2246        }
2247
2248        return list;
2249    }
2250    /**
2251     * Loads a String array asset into a list.
2252     */
2253    private static ArrayList<String> loadStringArray(Resources r, int resNum) {
2254        String[] labels = r.getStringArray(resNum);
2255        ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels));
2256        return list;
2257    }
2258
2259    @Override
2260    public void onDeleteStarted() {
2261        mEventDeletionStarted = true;
2262    }
2263
2264    private Dialog.OnDismissListener createDeleteOnDismissListener() {
2265        return new Dialog.OnDismissListener() {
2266                    @Override
2267                    public void onDismiss(DialogInterface dialog) {
2268                        // Since OnPause will force the dialog to dismiss , do
2269                        // not change the dialog status
2270                        if (!mIsPaused) {
2271                            mDeleteDialogVisible = false;
2272                        }
2273                    }
2274                };
2275    }
2276
2277    public long getEventId() {
2278        return mEventId;
2279    }
2280
2281    public long getStartMillis() {
2282        return mStartMillis;
2283    }
2284    public long getEndMillis() {
2285        return mEndMillis;
2286    }
2287    private void setDialogSize(Resources r) {
2288        mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width);
2289        mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height);
2290    }
2291
2292    @Override
2293    public void onColorSelected(int color) {
2294        mCurrentColor = color;
2295        mCurrentColorKey = mDisplayColorKeyMap.get(color);
2296        mHeadlines.setBackgroundColor(color);
2297    }
2298}
2299