EventInfoFragment.java revision c0624ee90e59386b06a01b3415d0bb4e38f40db7
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 com.android.calendar.CalendarController.EventInfo;
20import com.android.calendar.CalendarController.EventType;
21import com.android.calendar.CalendarEventModel.Attendee;
22import com.android.calendar.event.AttendeesView;
23import com.android.calendar.event.EditEventHelper;
24
25import android.app.Activity;
26import android.app.Dialog;
27import android.app.DialogFragment;
28import android.content.ActivityNotFoundException;
29import android.content.ContentProviderOperation;
30import android.content.ContentResolver;
31import android.content.ContentUris;
32import android.content.ContentValues;
33import android.content.Context;
34import android.content.Intent;
35import android.content.res.Resources;
36import android.database.Cursor;
37import android.graphics.Rect;
38import android.graphics.Typeface;
39import android.net.Uri;
40import android.os.Bundle;
41import android.pim.EventRecurrence;
42import android.provider.Calendar;
43import android.provider.Calendar.Attendees;
44import android.provider.Calendar.Calendars;
45import android.provider.Calendar.Events;
46import android.provider.ContactsContract;
47import android.provider.ContactsContract.CommonDataKinds;
48import android.provider.ContactsContract.Intents;
49import android.provider.ContactsContract.QuickContact;
50import android.text.Spannable;
51import android.text.SpannableStringBuilder;
52import android.text.TextUtils;
53import android.text.format.DateFormat;
54import android.text.format.DateUtils;
55import android.text.format.Time;
56import android.text.style.ForegroundColorSpan;
57import android.text.style.StrikethroughSpan;
58import android.text.style.StyleSpan;
59import android.text.util.Linkify;
60import android.text.util.Rfc822Token;
61import android.util.Log;
62import android.view.Gravity;
63import android.view.LayoutInflater;
64import android.view.MotionEvent;
65import android.view.View;
66import android.view.View.OnClickListener;
67import android.view.View.OnTouchListener;
68import android.view.ViewGroup;
69import android.view.Window;
70import android.view.WindowManager;
71import android.view.accessibility.AccessibilityEvent;
72import android.view.accessibility.AccessibilityManager;
73import android.widget.AdapterView;
74import android.widget.Button;
75import android.widget.RadioButton;
76import android.widget.RadioGroup;
77import android.widget.RadioGroup.OnCheckedChangeListener;
78import android.widget.TextView;
79import android.widget.Toast;
80
81import java.util.ArrayList;
82import java.util.Formatter;
83import java.util.List;
84import java.util.Locale;
85import java.util.regex.Pattern;
86import java.util.TimeZone;
87
88
89public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener,
90        CalendarController.EventHandler {
91    public static final boolean DEBUG = false;
92
93    public static final String TAG = "EventInfoFragment";
94
95    private static final String BUNDLE_KEY_EVENT_ID = "key_event_id";
96    private static final String BUNDLE_KEY_START_MILLIS = "key_start_millis";
97    private static final String BUNDLE_KEY_END_MILLIS = "key_end_millis";
98    private static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog";
99
100    private static final String PERIOD_SPACE = ". ";
101
102    /**
103     * These are the corresponding indices into the array of strings
104     * "R.array.change_response_labels" in the resource file.
105     */
106    static final int UPDATE_SINGLE = 0;
107    static final int UPDATE_ALL = 1;
108
109    // Query tokens for QueryHandler
110    private static final int TOKEN_QUERY_EVENT = 1 << 0;
111    private static final int TOKEN_QUERY_CALENDARS = 1 << 1;
112    private static final int TOKEN_QUERY_ATTENDEES = 1 << 2;
113    private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3;
114    private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS
115            | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT;
116    private int mCurrentQuery = 0;
117
118    private static final String[] EVENT_PROJECTION = new String[] {
119        Events._ID,                  // 0  do not remove; used in DeleteEventHelper
120        Events.TITLE,                // 1  do not remove; used in DeleteEventHelper
121        Events.RRULE,                // 2  do not remove; used in DeleteEventHelper
122        Events.ALL_DAY,              // 3  do not remove; used in DeleteEventHelper
123        Events.CALENDAR_ID,          // 4  do not remove; used in DeleteEventHelper
124        Events.DTSTART,              // 5  do not remove; used in DeleteEventHelper
125        Events._SYNC_ID,             // 6  do not remove; used in DeleteEventHelper
126        Events.EVENT_TIMEZONE,       // 7  do not remove; used in DeleteEventHelper
127        Events.DESCRIPTION,          // 8
128        Events.EVENT_LOCATION,       // 9
129        Calendars.ACCESS_LEVEL,      // 10
130        Calendars.CALENDAR_COLOR,             // 11
131        Events.HAS_ATTENDEE_DATA,    // 12
132        Events.ORGANIZER,            // 13
133        Events.ORIGINAL_SYNC_ID        // 14 do not remove; used in DeleteEventHelper
134    };
135    private static final int EVENT_INDEX_ID = 0;
136    private static final int EVENT_INDEX_TITLE = 1;
137    private static final int EVENT_INDEX_RRULE = 2;
138    private static final int EVENT_INDEX_ALL_DAY = 3;
139    private static final int EVENT_INDEX_CALENDAR_ID = 4;
140    private static final int EVENT_INDEX_SYNC_ID = 6;
141    private static final int EVENT_INDEX_EVENT_TIMEZONE = 7;
142    private static final int EVENT_INDEX_DESCRIPTION = 8;
143    private static final int EVENT_INDEX_EVENT_LOCATION = 9;
144    private static final int EVENT_INDEX_ACCESS_LEVEL = 10;
145    private static final int EVENT_INDEX_COLOR = 11;
146    private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 12;
147    private static final int EVENT_INDEX_ORGANIZER = 13;
148
149    private static final String[] ATTENDEES_PROJECTION = new String[] {
150        Attendees._ID,                      // 0
151        Attendees.ATTENDEE_NAME,            // 1
152        Attendees.ATTENDEE_EMAIL,           // 2
153        Attendees.ATTENDEE_RELATIONSHIP,    // 3
154        Attendees.ATTENDEE_STATUS,          // 4
155    };
156    private static final int ATTENDEES_INDEX_ID = 0;
157    private static final int ATTENDEES_INDEX_NAME = 1;
158    private static final int ATTENDEES_INDEX_EMAIL = 2;
159    private static final int ATTENDEES_INDEX_RELATIONSHIP = 3;
160    private static final int ATTENDEES_INDEX_STATUS = 4;
161
162    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
163
164    private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, "
165            + Attendees.ATTENDEE_EMAIL + " ASC";
166
167    static final String[] CALENDARS_PROJECTION = new String[] {
168        Calendars._ID,           // 0
169        Calendars.DISPLAY_NAME,  // 1
170        Calendars.OWNER_ACCOUNT, // 2
171        Calendars.CAN_ORGANIZER_RESPOND // 3
172    };
173    static final int CALENDARS_INDEX_DISPLAY_NAME = 1;
174    static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
175    static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3;
176
177    static final String CALENDARS_WHERE = Calendars._ID + "=?";
178    static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.DISPLAY_NAME + "=?";
179
180    private View mView;
181
182    private Uri mUri;
183    private long mEventId;
184    private Cursor mEventCursor;
185    private Cursor mAttendeesCursor;
186    private Cursor mCalendarsCursor;
187    private static float mScale = 0; // Used for supporting different screen densities
188
189    private long mStartMillis;
190    private long mEndMillis;
191
192    private boolean mHasAttendeeData;
193    private boolean mIsOrganizer;
194    private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
195    private boolean mOwnerCanRespond;
196    private String mCalendarOwnerAccount;
197    private boolean mCanModifyCalendar;
198    private boolean mIsBusyFreeCalendar;
199    private int mNumOfAttendees;
200
201    private EditResponseHelper mEditResponseHelper;
202
203    private int mOriginalAttendeeResponse;
204    private int mAttendeeResponseFromIntent = CalendarController.ATTENDEE_NO_RESPONSE;
205    private boolean mIsRepeating;
206
207    private TextView mTitle;
208    private TextView mWhen;
209    private TextView mWhere;
210    private TextView mWhat;
211    private TextView mAttendees;
212    private AttendeesView mLongAttendees;
213    private TextView mCalendar;
214
215    private Pattern mWildcardPattern = Pattern.compile("^.*$");
216
217    ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>();
218    ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>();
219    ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>();
220    ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>();
221    private int mColor;
222
223    private QueryHandler mHandler;
224
225    private Runnable mTZUpdater = new Runnable() {
226        @Override
227        public void run() {
228            updateEvent(mView);
229        }
230    };
231
232    private static int DIALOG_WIDTH = 500;
233    private static int DIALOG_HEIGHT = 600;
234    private boolean mIsDialog = false;
235    private boolean mIsPaused = true;
236    private boolean mDismissOnResume = false;
237    private int mX = -1;
238    private int mY = -1;
239    private static boolean mIsFullScreen = true;
240    private Button mDescButton;  // Button to expand/collapse the description
241    private String mMoreLabel;   // Labels for the button
242    private String mLessLabel;
243    private boolean mShowMaxDescription;  // Current status of button
244    private int mDescLineNum;             // The default number of lines in the description
245
246    private class QueryHandler extends AsyncQueryService {
247        public QueryHandler(Context context) {
248            super(context);
249        }
250
251        @Override
252        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
253            // if the activity is finishing, then close the cursor and return
254            final Activity activity = getActivity();
255            if (activity == null || activity.isFinishing()) {
256                cursor.close();
257                return;
258            }
259
260            switch (token) {
261            case TOKEN_QUERY_EVENT:
262                mEventCursor = Utils.matrixCursorFromCursor(cursor);
263                if (initEventCursor()) {
264                    // The cursor is empty. This can happen if the event was
265                    // deleted.
266                    // FRAG_TODO we should no longer rely on Activity.finish()
267                    activity.finish();
268                    return;
269                }
270                updateEvent(mView);
271
272                // start calendar query
273                Uri uri = Calendars.CONTENT_URI;
274                String[] args = new String[] {
275                        Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))};
276                startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION,
277                        CALENDARS_WHERE, args, null);
278                break;
279            case TOKEN_QUERY_CALENDARS:
280                mCalendarsCursor = Utils.matrixCursorFromCursor(cursor);
281                updateCalendar(mView);
282                // FRAG_TODO fragments shouldn't set the title anymore
283                updateTitle();
284                // update the action bar since our option set might have changed
285                activity.invalidateOptionsMenu();
286
287                if (!mIsBusyFreeCalendar) {
288                    args = new String[] { Long.toString(mEventId) };
289
290                    // start attendees query
291                    uri = Attendees.CONTENT_URI;
292                    startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION,
293                            ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER);
294                } else {
295                    sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES);
296                }
297                break;
298            case TOKEN_QUERY_ATTENDEES:
299                mAttendeesCursor = Utils.matrixCursorFromCursor(cursor);
300                initAttendeesCursor(mView);
301                updateResponse(mView);
302                break;
303            case TOKEN_QUERY_DUPLICATE_CALENDARS:
304                Resources res = activity.getResources();
305                SpannableStringBuilder sb = new SpannableStringBuilder();
306
307                // Label
308                String label = res.getString(R.string.view_event_calendar_label);
309                sb.append(label).append(" ");
310                sb.setSpan(new StyleSpan(Typeface.BOLD), 0, label.length(),
311                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
312
313                // Calendar display name
314                String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
315                sb.append(calendarName);
316
317                // Show email account if display name is not unique and
318                // display name != email
319                String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
320                if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email)) {
321                    sb.append(" (").append(email).append(")");
322                }
323
324                mCalendar.setText(sb);
325                break;
326            }
327            cursor.close();
328            sendAccessibilityEventIfQueryDone(token);
329        }
330
331    }
332
333    private void sendAccessibilityEventIfQueryDone(int token) {
334        mCurrentQuery |= token;
335        if (mCurrentQuery == TOKEN_QUERY_ALL) {
336            sendAccessibilityEvent();
337        }
338    }
339
340    public EventInfoFragment() {
341        mUri = null;
342    }
343
344    public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis,
345            int attendeeResponse) {
346
347        if (mScale == 0) {
348            mScale = context.getResources().getDisplayMetrics().density;
349            if (mScale != 1) {
350                DIALOG_WIDTH *= mScale;
351                DIALOG_HEIGHT *= mScale;
352            }
353        }
354
355
356        mDescLineNum = context.getResources().getInteger((R.integer.event_info_desc_line_num));
357        mMoreLabel = context.getResources().getString((R.string.event_info_desc_more));
358        mLessLabel = context.getResources().getString((R.string.event_info_desc_less));
359
360        setStyle(DialogFragment.STYLE_NO_TITLE, 0);
361        mUri = uri;
362        mStartMillis = startMillis;
363        mEndMillis = endMillis;
364        mAttendeeResponseFromIntent = attendeeResponse;
365    }
366
367    public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis,
368            int attendeeResponse) {
369        this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis,
370                endMillis, attendeeResponse);
371        mEventId = eventId;
372    }
373
374    @Override
375    public void onActivityCreated(Bundle savedInstanceState) {
376        super.onActivityCreated(savedInstanceState);
377
378        if (savedInstanceState != null) {
379            mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false);
380        }
381
382        if (mIsDialog) {
383            applyDialogParams();
384        }
385    }
386
387    private void applyDialogParams() {
388        Dialog dialog = getDialog();
389        dialog.setCanceledOnTouchOutside(true);
390
391        Window window = dialog.getWindow();
392        window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
393
394        WindowManager.LayoutParams a = window.getAttributes();
395        a.dimAmount = .4f;
396
397        a.width = DIALOG_WIDTH;
398        a.height = DIALOG_HEIGHT;
399
400
401        // On tablets , do smart positioning of dialog
402        // On phones , use the whole screen
403
404        if (!mIsFullScreen) {
405            if (mX != -1 || mY != -1) {
406                a.x = mX - a.width - 64;
407                if (a.x < 0) {
408                    a.x = mX + 64;
409                }
410                a.y = mY - 64;
411                a.gravity = Gravity.LEFT | Gravity.TOP;
412            }
413        } else {
414            a.width = WindowManager.LayoutParams.MATCH_PARENT;
415            a.height = WindowManager.LayoutParams.MATCH_PARENT;
416        }
417        window.setAttributes(a);
418    }
419
420    public void setDialogParams(int x, int y, boolean isFullScreen) {
421        mIsDialog = true;
422        mX = x;
423        mY = y;
424        mIsFullScreen = isFullScreen;
425    }
426
427    // Implements OnCheckedChangeListener
428    @Override
429    public void onCheckedChanged(RadioGroup group, int checkedId) {
430        // If this is not a repeating event, then don't display the dialog
431        // asking which events to change.
432        if (!mIsRepeating) {
433            return;
434        }
435
436        // If the selection is the same as the original, then don't display the
437        // dialog asking which events to change.
438        if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) {
439            return;
440        }
441
442        // This is a repeating event. We need to ask the user if they mean to
443        // change just this one instance or all instances.
444        mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents());
445    }
446
447    public void onNothingSelected(AdapterView<?> parent) {
448    }
449
450    @Override
451    public void onAttach(Activity activity) {
452        super.onAttach(activity);
453        mEditResponseHelper = new EditResponseHelper(activity);
454        mHandler = new QueryHandler(activity);
455    }
456
457    @Override
458    public View onCreateView(LayoutInflater inflater, ViewGroup container,
459            Bundle savedInstanceState) {
460        mView = inflater.inflate(R.layout.event_info, container, false);
461        mTitle = (TextView) mView.findViewById(R.id.title);
462        mWhen = (TextView) mView.findViewById(R.id.when);
463        mWhere = (TextView) mView.findViewById(R.id.where);
464        mWhat = (TextView) mView.findViewById(R.id.description);
465        mAttendees = (TextView) mView.findViewById(R.id.attendee_list);
466        mLongAttendees = (AttendeesView)mView.findViewById(R.id.long_attendee_list);
467        mCalendar = (TextView) mView.findViewById(R.id.calendar);
468        mDescButton = (Button)mView.findViewById(R.id.desc_expand);
469        mDescButton.setOnClickListener(new View.OnClickListener() {
470            public void onClick(View v) {
471                mShowMaxDescription = !mShowMaxDescription;
472                updateDescription();
473            }
474        });
475        mShowMaxDescription = false; // Show short version of description as default.
476
477        if (mUri == null) {
478            // restore event ID from bundle
479            mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID);
480            mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId);
481            mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS);
482            mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS);
483        }
484
485        // start loading the data
486        mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
487                null, null, null);
488
489        Button b = (Button) mView.findViewById(R.id.delete);
490        b.setOnClickListener(new OnClickListener() {
491            @Override
492            public void onClick(View v) {
493                if (!mCanModifyCalendar) {
494                    return;
495                }
496                DeleteEventHelper deleteHelper = new DeleteEventHelper(
497                        getActivity(), getActivity(), false /* exitWhenDone */);
498                deleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable);
499            }});
500
501        return mView;
502    }
503
504    private Runnable onDeleteRunnable = new Runnable() {
505        @Override
506        public void run() {
507            if (EventInfoFragment.this.mIsPaused) {
508                mDismissOnResume = true;
509                return;
510            }
511            if (EventInfoFragment.this.isVisible()) {
512                EventInfoFragment.this.dismiss();
513            }
514        }
515    };
516
517    // Sets the description:
518    // Set the expand/collapse button
519    // Expand/collapse the description according the the current status
520    private void updateDescription() {
521
522        // Description is short, hide button
523        if (mWhat.getLineCount() <= mDescLineNum) {
524            mDescButton.setVisibility(View.GONE);
525            return;
526        }
527        // Show button and set label according to the expand/collapse status
528        mDescButton.setVisibility(View.VISIBLE);
529        if (mShowMaxDescription) {
530            mDescButton.setText(mLessLabel);
531            mWhat.setLines(mWhat.getLineCount());
532        } else {
533            mDescButton.setText(mMoreLabel);
534            mWhat.setLines(mDescLineNum);
535        }
536    }
537
538    private void updateTitle() {
539        Resources res = getActivity().getResources();
540        if (mCanModifyCalendar && !mIsOrganizer) {
541            getActivity().setTitle(res.getString(R.string.event_info_title_invite));
542        } else {
543            getActivity().setTitle(res.getString(R.string.event_info_title));
544        }
545    }
546
547    /**
548     * Initializes the event cursor, which is expected to point to the first
549     * (and only) result from a query.
550     * @return true if the cursor is empty.
551     */
552    private boolean initEventCursor() {
553        if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) {
554            return true;
555        }
556        mEventCursor.moveToFirst();
557        mEventId = mEventCursor.getInt(EVENT_INDEX_ID);
558        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
559        mIsRepeating = !TextUtils.isEmpty(rRule);
560        return false;
561    }
562
563    @SuppressWarnings("fallthrough")
564    private void initAttendeesCursor(View view) {
565        mOriginalAttendeeResponse = CalendarController.ATTENDEE_NO_RESPONSE;
566        mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE;
567        mNumOfAttendees = 0;
568        if (mAttendeesCursor != null) {
569            mNumOfAttendees = mAttendeesCursor.getCount();
570            if (mAttendeesCursor.moveToFirst()) {
571                mAcceptedAttendees.clear();
572                mDeclinedAttendees.clear();
573                mTentativeAttendees.clear();
574                mNoResponseAttendees.clear();
575
576                do {
577                    int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
578                    String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME);
579                    String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL);
580
581                    if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE &&
582                            mCalendarOwnerAccount.equalsIgnoreCase(email)) {
583                        mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID);
584                        mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS);
585                    } else {
586                        // Don't show your own status in the list because:
587                        //  1) it doesn't make sense for event without other guests.
588                        //  2) there's a spinner for that for events with guests.
589                        switch(status) {
590                            case Attendees.ATTENDEE_STATUS_ACCEPTED:
591                                mAcceptedAttendees.add(new Attendee(name, email,
592                                        Attendees.ATTENDEE_STATUS_ACCEPTED));
593                                break;
594                            case Attendees.ATTENDEE_STATUS_DECLINED:
595                                mDeclinedAttendees.add(new Attendee(name, email,
596                                        Attendees.ATTENDEE_STATUS_DECLINED));
597                                break;
598                            case Attendees.ATTENDEE_STATUS_TENTATIVE:
599                                mTentativeAttendees.add(new Attendee(name, email,
600                                        Attendees.ATTENDEE_STATUS_TENTATIVE));
601                                break;
602                            default:
603                                mNoResponseAttendees.add(new Attendee(name, email,
604                                        Attendees.ATTENDEE_STATUS_NONE));
605                        }
606                    }
607                } while (mAttendeesCursor.moveToNext());
608                mAttendeesCursor.moveToFirst();
609
610                updateAttendees(view);
611            }
612        }
613    }
614
615    @Override
616    public void onSaveInstanceState(Bundle outState) {
617        super.onSaveInstanceState(outState);
618        outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId);
619        outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis);
620        outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis);
621
622        outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog);
623    }
624
625
626    @Override
627    public void onDestroyView() {
628        if (saveResponse()) {
629            Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show();
630        }
631        super.onDestroyView();
632    }
633
634    @Override
635    public void onDestroy() {
636        if (mEventCursor != null) {
637            mEventCursor.close();
638        }
639        if (mCalendarsCursor != null) {
640            mCalendarsCursor.close();
641        }
642        if (mAttendeesCursor != null) {
643            mAttendeesCursor.close();
644        }
645        super.onDestroy();
646    }
647
648    /**
649     * Asynchronously saves the response to an invitation if the user changed
650     * the response. Returns true if the database will be updated.
651     *
652     * @param cr the ContentResolver
653     * @return true if the database will be changed
654     */
655    private boolean saveResponse() {
656        if (mAttendeesCursor == null || mEventCursor == null) {
657            return false;
658        }
659
660        RadioGroup radioGroup = (RadioGroup) getView().findViewById(R.id.response_value);
661        int status = getResponseFromButtonId(radioGroup.getCheckedRadioButtonId());
662        if (status == Attendees.ATTENDEE_STATUS_NONE) {
663            return false;
664        }
665
666        // If the status has not changed, then don't update the database
667        if (status == mOriginalAttendeeResponse) {
668            return false;
669        }
670
671        // If we never got an owner attendee id we can't set the status
672        if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) {
673            return false;
674        }
675
676        if (!mIsRepeating) {
677            // This is a non-repeating event
678            updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
679            return true;
680        }
681
682        // This is a repeating event
683        int whichEvents = mEditResponseHelper.getWhichEvents();
684        switch (whichEvents) {
685            case -1:
686                return false;
687            case UPDATE_SINGLE:
688                createExceptionResponse(mEventId, mCalendarOwnerAttendeeId, status);
689                return true;
690            case UPDATE_ALL:
691                updateResponse(mEventId, mCalendarOwnerAttendeeId, status);
692                return true;
693            default:
694                Log.e(TAG, "Unexpected choice for updating invitation response");
695                break;
696        }
697        return false;
698    }
699
700    private void updateResponse(long eventId, long attendeeId, int status) {
701        // Update the attendee status in the attendees table.  the provider
702        // takes care of updating the self attendance status.
703        ContentValues values = new ContentValues();
704
705        if (!TextUtils.isEmpty(mCalendarOwnerAccount)) {
706            values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount);
707        }
708        values.put(Attendees.ATTENDEE_STATUS, status);
709        values.put(Attendees.EVENT_ID, eventId);
710
711        Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId);
712
713        mHandler.startUpdate(mHandler.getNextToken(), null, uri, values,
714                null, null, Utils.UNDO_DELAY);
715    }
716
717    private void createExceptionResponse(long eventId, long attendeeId,
718            int status) {
719        if (mEventCursor == null || !mEventCursor.moveToFirst()) {
720            return;
721        }
722        // TODO change this fragment to build a CalendarEventModel and save via
723        // EditEventHelper
724
725        ContentValues values = new ContentValues();
726
727        String title = mEventCursor.getString(EVENT_INDEX_TITLE);
728        String timezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
729        int calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID);
730        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
731        String syncId = mEventCursor.getString(EVENT_INDEX_SYNC_ID);
732
733        values.put(Events.TITLE, title);
734        values.put(Events.EVENT_TIMEZONE, timezone);
735        values.put(Events.ALL_DAY, allDay ? 1 : 0);
736        values.put(Events.CALENDAR_ID, calendarId);
737        values.put(Events.DTSTART, mStartMillis);
738        values.put(Events.DTEND, mEndMillis);
739        values.put(Events.ORIGINAL_SYNC_ID, syncId);
740        values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis);
741        values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0);
742        values.put(Events.STATUS, Events.STATUS_CONFIRMED);
743        values.put(Events.SELF_ATTENDEE_STATUS, status);
744
745        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
746        int eventIdIndex = ops.size();
747
748        ops.add(ContentProviderOperation.newInsert(Events.CONTENT_URI).withValues(values).build());
749
750        if (mHasAttendeeData) {
751            // Insert the new attendees
752            for (Attendee attendee : mAcceptedAttendees) {
753                addAttendee(
754                        values, ops, eventIdIndex, attendee, Attendees.ATTENDEE_STATUS_ACCEPTED);
755            }
756            for (Attendee attendee : mDeclinedAttendees) {
757                addAttendee(
758                        values, ops, eventIdIndex, attendee, Attendees.ATTENDEE_STATUS_DECLINED);
759            }
760            for (Attendee attendee : mTentativeAttendees) {
761                addAttendee(
762                        values, ops, eventIdIndex, attendee, Attendees.ATTENDEE_STATUS_TENTATIVE);
763            }
764            for (Attendee attendee : mNoResponseAttendees) {
765                addAttendee(values, ops, eventIdIndex, attendee, Attendees.ATTENDEE_STATUS_NONE);
766            }
767        }
768
769        // Create a recurrence exception
770        mHandler.startBatch(
771                mHandler.getNextToken(), null, Calendar.AUTHORITY, ops, Utils.UNDO_DELAY);
772    }
773
774    /**
775     * @param values
776     * @param ops
777     * @param eventIdIndex
778     * @param attendee
779     */
780    private void addAttendee(ContentValues values, ArrayList<ContentProviderOperation> ops,
781            int eventIdIndex, Attendee attendee, int attendeeStatus) {
782        ContentProviderOperation.Builder b;
783        values.clear();
784        values.put(Attendees.ATTENDEE_NAME, attendee.mName);
785        values.put(Attendees.ATTENDEE_EMAIL, attendee.mEmail);
786        values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE);
787        values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_NONE);
788        values.put(Attendees.ATTENDEE_STATUS, attendeeStatus);
789
790        b = ContentProviderOperation.newInsert(Attendees.CONTENT_URI).withValues(values);
791        b.withValueBackReference(Attendees.EVENT_ID, eventIdIndex);
792        ops.add(b.build());
793    }
794
795    public static int getResponseFromButtonId(int buttonId) {
796        int response;
797        switch (buttonId) {
798            case R.id.response_yes:
799                response = Attendees.ATTENDEE_STATUS_ACCEPTED;
800                break;
801            case R.id.response_maybe:
802                response = Attendees.ATTENDEE_STATUS_TENTATIVE;
803                break;
804            case R.id.response_no:
805                response = Attendees.ATTENDEE_STATUS_DECLINED;
806                break;
807            default:
808                response = Attendees.ATTENDEE_STATUS_NONE;
809        }
810        return response;
811    }
812
813    public static int findButtonIdForResponse(int response) {
814        int buttonId;
815        switch (response) {
816            case Attendees.ATTENDEE_STATUS_ACCEPTED:
817                buttonId = R.id.response_yes;
818                break;
819            case Attendees.ATTENDEE_STATUS_TENTATIVE:
820                buttonId = R.id.response_maybe;
821                break;
822            case Attendees.ATTENDEE_STATUS_DECLINED:
823                buttonId = R.id.response_no;
824                break;
825                default:
826                    buttonId = -1;
827        }
828        return buttonId;
829    }
830
831    private void doEdit() {
832        Context c = getActivity();
833        // This ensures that we aren't in the process of closing and have been
834        // unattached already
835        if (c != null) {
836            CalendarController.getInstance(c).sendEventRelatedEvent(
837                    this, EventType.VIEW_EVENT_DETAILS, mEventId, mStartMillis, mEndMillis, 0, 0, -1);
838        }
839    }
840
841    private void updateEvent(View view) {
842        if (mEventCursor == null) {
843            return;
844        }
845
846        String eventName = mEventCursor.getString(EVENT_INDEX_TITLE);
847        if (eventName == null || eventName.length() == 0) {
848            eventName = getActivity().getString(R.string.no_title_label);
849        }
850
851        boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0;
852        String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION);
853        String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION);
854        String rRule = mEventCursor.getString(EVENT_INDEX_RRULE);
855        String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE);
856        String organizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
857
858        mColor = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff;
859        view.findViewById(R.id.color).setBackgroundColor(mColor);
860
861        // What
862        if (eventName != null) {
863            setTextCommon(view, R.id.title, eventName);
864        }
865
866        // When
867        String whenDate;
868        int flagsTime = DateUtils.FORMAT_SHOW_TIME;
869        int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY |
870                DateUtils.FORMAT_SHOW_YEAR;
871
872        if (DateFormat.is24HourFormat(getActivity())) {
873            flagsTime |= DateUtils.FORMAT_24HOUR;
874        }
875        if (allDay) {
876            Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
877            whenDate = DateUtils.formatDateRange(getActivity(), f, mStartMillis, mStartMillis,
878                    flagsDate, Time.TIMEZONE_UTC).toString();
879            setTextCommon(view, R.id.when_date, whenDate);
880            view.findViewById(R.id.when_time).setVisibility(View.GONE);
881
882        } else {
883            whenDate = Utils.formatDateRange(getActivity(), mStartMillis, mEndMillis, flagsDate);
884            String whenTime = Utils.formatDateRange(getActivity(), mStartMillis, mEndMillis,
885                    flagsTime);
886            setTextCommon(view, R.id.when_date, whenDate);
887            setTextCommon(view, R.id.when_time, whenTime);
888        }
889
890        // Show the event timezone if it is different from the local timezone
891        // TODO: Fix comparison of Timezone
892        Time time = new Time();
893        String localTimezone = time.timezone;
894        if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) {
895            String displayName;
896            TimeZone tz = TimeZone.getTimeZone(localTimezone);
897            if (tz == null || tz.getID().equals("GMT")) {
898                displayName = localTimezone;
899            } else {
900                displayName = tz.getDisplayName();
901            }
902
903            setTextCommon(view, R.id.timezone, displayName);
904            setVisibilityCommon(view, R.id.timezone_container, View.VISIBLE);
905        } else {
906            setVisibilityCommon(view, R.id.timezone_container, View.GONE);
907        }
908
909        // Organizer
910        // TODO: Hide if organizer is the user
911        setTextCommon(view, R.id.organizer, organizer);
912        setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE);
913
914        // Repeat
915        if (!TextUtils.isEmpty(rRule)) {
916            EventRecurrence eventRecurrence = new EventRecurrence();
917            eventRecurrence.parse(rRule);
918            Time date = new Time(Utils.getTimeZone(getActivity(), mTZUpdater));
919            if (allDay) {
920                date.timezone = Time.TIMEZONE_UTC;
921            }
922            date.set(mStartMillis);
923            eventRecurrence.setStartDate(date);
924            String repeatString = EventRecurrenceFormatter.getRepeatString(
925                    getActivity().getResources(), eventRecurrence);
926            setTextCommon(view, R.id.repeat, repeatString);
927            setVisibilityCommon(view, R.id.repeat_container, View.VISIBLE);
928        } else {
929            setVisibilityCommon(view, R.id.repeat_container, View.GONE);
930        }
931
932        // Where
933        if (location == null || location.trim().length() == 0) {
934            setVisibilityCommon(view, R.id.where, View.GONE);
935        } else {
936            final TextView textView = mWhere;
937            if (textView != null) {
938                textView.setAutoLinkMask(0);
939                textView.setText(location.trim());
940                if (!Linkify.addLinks(textView, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES
941                        | Linkify.MAP_ADDRESSES)) {
942                    Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q=");
943                }
944                textView.setOnTouchListener(new OnTouchListener() {
945                    public boolean onTouch(View v, MotionEvent event) {
946                        try {
947                            return v.onTouchEvent(event);
948                        } catch (ActivityNotFoundException e) {
949                            // ignore
950                            return true;
951                        }
952                    }
953                });
954            }
955        }
956
957        // Description
958        if (description != null && description.length() != 0) {
959            setTextCommon(view, R.id.description, description);
960        }
961        updateDescription ();  // Expand or collapse full description
962    }
963
964    private void sendAccessibilityEvent() {
965        AccessibilityManager am = AccessibilityManager.getInstance(getActivity());
966        if (!am.isEnabled()) {
967            return;
968        }
969
970        AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
971        event.setClassName(getClass().getName());
972        event.setPackageName(getActivity().getPackageName());
973        List<CharSequence> text = event.getText();
974
975        addFieldToAccessibilityEvent(text, mTitle);
976        addFieldToAccessibilityEvent(text, mCalendar);
977        addFieldToAccessibilityEvent(text, mWhen);
978        addFieldToAccessibilityEvent(text, mWhere);
979        addFieldToAccessibilityEvent(text, mWhat);
980        addFieldToAccessibilityEvent(text, mAttendees);
981
982        RadioGroup response = (RadioGroup) getView().findViewById(R.id.response_value);
983        if (response.getVisibility() == View.VISIBLE) {
984            int id = response.getCheckedRadioButtonId();
985            if (id != View.NO_ID) {
986                text.add(((TextView) getView().findViewById(R.id.response_label)).getText());
987                text.add((((RadioButton) (response.findViewById(id))).getText() + PERIOD_SPACE));
988            }
989        }
990
991        am.sendAccessibilityEvent(event);
992    }
993
994    /**
995     * @param text
996     */
997    private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView view) {
998        String str = view.toString().trim();
999        if (!TextUtils.isEmpty(str)) {
1000            text.add(mTitle.getText());
1001            text.add(PERIOD_SPACE);
1002        }
1003    }
1004
1005    private void updateCalendar(View view) {
1006        mCalendarOwnerAccount = "";
1007        if (mCalendarsCursor != null && mEventCursor != null) {
1008            mCalendarsCursor.moveToFirst();
1009            String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
1010            mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount;
1011            mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0;
1012
1013            String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME);
1014
1015            // start duplicate calendars query
1016            mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, Calendars.CONTENT_URI,
1017                    CALENDARS_PROJECTION, CALENDARS_DUPLICATE_NAME_WHERE,
1018                    new String[] {displayName}, null);
1019
1020            String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER);
1021            mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(eventOrganizer);
1022            mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0;
1023            mCanModifyCalendar =
1024                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS;
1025            mIsBusyFreeCalendar =
1026                    mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS;
1027
1028            if (!mIsBusyFreeCalendar) {
1029                Button b = (Button) mView.findViewById(R.id.edit);
1030                b.setEnabled(true);
1031                b.setOnClickListener(new OnClickListener() {
1032                    @Override
1033                    public void onClick(View v) {
1034                        doEdit();
1035                        EventInfoFragment.this.dismiss();
1036                    }
1037                });
1038            }
1039            if (!mCanModifyCalendar) {
1040                mView.findViewById(R.id.delete).setEnabled(false);
1041            }
1042        } else {
1043            setVisibilityCommon(view, R.id.calendar, View.GONE);
1044            sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS);
1045        }
1046    }
1047
1048    private void updateAttendees(View view) {
1049
1050
1051        // TODO: Add code to show only one list creating two versions (dialog/full screen)
1052        TextView tv = mAttendees;
1053        SpannableStringBuilder sb = new SpannableStringBuilder();
1054        formatAttendees(mAcceptedAttendees, sb, Attendees.ATTENDEE_STATUS_ACCEPTED);
1055        formatAttendees(mDeclinedAttendees, sb, Attendees.ATTENDEE_STATUS_DECLINED);
1056        formatAttendees(mTentativeAttendees, sb, Attendees.ATTENDEE_STATUS_TENTATIVE);
1057        formatAttendees(mNoResponseAttendees, sb, Attendees.ATTENDEE_STATUS_NONE);
1058
1059        if (sb.length() > 0) {
1060            // Add the label after the attendees are formatted because
1061            // formatAttendees would prepend ", " if sb.length != 0
1062            String label = getActivity().getResources().getString(R.string.attendees_label);
1063            sb.insert(0, label);
1064            sb.insert(label.length(), " ");
1065            sb.setSpan(new StyleSpan(Typeface.BOLD), 0, label.length(),
1066                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1067
1068            tv.setText(sb);
1069        }
1070        ((AttendeesView)mLongAttendees).addAttendees(mAcceptedAttendees);
1071        ((AttendeesView)mLongAttendees).addAttendees(mDeclinedAttendees);
1072        ((AttendeesView)mLongAttendees).addAttendees(mTentativeAttendees);
1073        ((AttendeesView)mLongAttendees).addAttendees(mNoResponseAttendees);
1074        mLongAttendees.setEnabled(false);
1075
1076    }
1077
1078    private void formatAttendees(ArrayList<Attendee> attendees, SpannableStringBuilder sb, int type) {
1079        if (attendees.size() <= 0) {
1080            return;
1081        }
1082
1083        int begin = sb.length();
1084        boolean firstTime = sb.length() == 0;
1085
1086        if (firstTime == false) {
1087            begin += 2; // skip over the ", " for formatting.
1088        }
1089
1090        for (Attendee attendee : attendees) {
1091            if (firstTime) {
1092                firstTime = false;
1093            } else {
1094                sb.append(", ");
1095            }
1096
1097            String name = attendee.getDisplayName();
1098            sb.append(name);
1099        }
1100
1101        switch (type) {
1102            case Attendees.ATTENDEE_STATUS_ACCEPTED:
1103                break;
1104            case Attendees.ATTENDEE_STATUS_DECLINED:
1105                sb.setSpan(new StrikethroughSpan(), begin, sb.length(),
1106                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1107                // fall through
1108            default:
1109                // The last INCLUSIVE causes the foreground color to be applied
1110                // to the rest of the span. If not, the comma at the end of the
1111                // declined or tentative may be black.
1112                sb.setSpan(new ForegroundColorSpan(0xFF999999), begin, sb.length(),
1113                        Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
1114                break;
1115        }
1116    }
1117
1118    void updateResponse(View view) {
1119        // we only let the user accept/reject/etc. a meeting if:
1120        // a) you can edit the event's containing calendar AND
1121        // b) you're not the organizer and only attendee AND
1122        // c) organizerCanRespond is enabled for the calendar
1123        // (if the attendee data has been hidden, the visible number of attendees
1124        // will be 1 -- the calendar owner's).
1125        // (there are more cases involved to be 100% accurate, such as
1126        // paying attention to whether or not an attendee status was
1127        // included in the feed, but we're currently omitting those corner cases
1128        // for simplicity).
1129
1130        // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel.
1131        if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) ||
1132                (mIsOrganizer && !mOwnerCanRespond)) {
1133            setVisibilityCommon(view, R.id.response_container, View.GONE);
1134            return;
1135        }
1136
1137        setVisibilityCommon(view, R.id.response_container, View.VISIBLE);
1138
1139
1140        int response;
1141        if (mAttendeeResponseFromIntent != CalendarController.ATTENDEE_NO_RESPONSE) {
1142            response = mAttendeeResponseFromIntent;
1143        } else {
1144            response = mOriginalAttendeeResponse;
1145        }
1146
1147        int buttonToCheck = findButtonIdForResponse(response);
1148        RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.response_value);
1149        radioGroup.check(buttonToCheck); // -1 clear all radio buttons
1150        radioGroup.setOnCheckedChangeListener(this);
1151    }
1152
1153    private void setTextCommon(View view, int id, CharSequence text) {
1154        TextView textView = (TextView) view.findViewById(id);
1155        if (textView == null)
1156            return;
1157        textView.setText(text);
1158    }
1159
1160    private void setVisibilityCommon(View view, int id, int visibility) {
1161        View v = view.findViewById(id);
1162        if (v != null) {
1163            v.setVisibility(visibility);
1164        }
1165        return;
1166    }
1167
1168    /**
1169     * Taken from com.google.android.gm.HtmlConversationActivity
1170     *
1171     * Send the intent that shows the Contact info corresponding to the email address.
1172     */
1173    public void showContactInfo(Attendee attendee, Rect rect) {
1174        // First perform lookup query to find existing contact
1175        final ContentResolver resolver = getActivity().getContentResolver();
1176        final String address = attendee.mEmail;
1177        final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
1178                Uri.encode(address));
1179        final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
1180
1181        if (lookupUri != null) {
1182            // Found matching contact, trigger QuickContact
1183            QuickContact.showQuickContact(getActivity(), rect, lookupUri,
1184                    QuickContact.MODE_MEDIUM, null);
1185        } else {
1186            // No matching contact, ask user to create one
1187            final Uri mailUri = Uri.fromParts("mailto", address, null);
1188            final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri);
1189
1190            // Pass along full E-mail string for possible create dialog
1191            Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null);
1192            intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString());
1193
1194            // Only provide personal name hint if we have one
1195            final String senderPersonal = attendee.mName;
1196            if (!TextUtils.isEmpty(senderPersonal)) {
1197                intent.putExtra(Intents.Insert.NAME, senderPersonal);
1198            }
1199
1200            startActivity(intent);
1201        }
1202    }
1203
1204    @Override
1205    public void onPause() {
1206        mIsPaused = true;
1207        mHandler.removeCallbacks(onDeleteRunnable);
1208        super.onPause();
1209    }
1210
1211    @Override
1212    public void onResume() {
1213        super.onResume();
1214        mIsPaused = false;
1215        if (mDismissOnResume) {
1216            mHandler.post(onDeleteRunnable);
1217        }
1218    }
1219
1220    @Override
1221    public void eventsChanged() {
1222    }
1223
1224    @Override
1225    public long getSupportedEventTypes() {
1226        return EventType.EVENTS_CHANGED;
1227    }
1228
1229    @Override
1230    public void handleEvent(EventInfo event) {
1231        if (event.eventType == EventType.EVENTS_CHANGED) {
1232            // reload the data
1233            mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION,
1234                    null, null, null);
1235        }
1236
1237    }
1238}
1239