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