EventInfoFragment.java revision d785cd533f6a3c23813c15315aed74efa9a116f5
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.EventType; 20import com.android.calendar.event.EditEventHelper; 21import com.android.calendar.event.EventViewUtils; 22 23import android.app.Activity; 24import android.app.Dialog; 25import android.app.DialogFragment; 26import android.content.ActivityNotFoundException; 27import android.content.AsyncQueryHandler; 28import android.content.ContentProviderOperation; 29import android.content.ContentResolver; 30import android.content.ContentUris; 31import android.content.ContentValues; 32import android.content.Context; 33import android.content.Intent; 34import android.content.SharedPreferences; 35import android.content.res.Resources; 36import android.database.Cursor; 37import android.database.MatrixCursor; 38import android.graphics.PorterDuff; 39import android.graphics.Rect; 40import android.net.Uri; 41import android.os.Bundle; 42import android.pim.EventRecurrence; 43import android.provider.ContactsContract; 44import android.provider.Calendar.Attendees; 45import android.provider.Calendar.Calendars; 46import android.provider.Calendar.Events; 47import android.provider.Calendar.Reminders; 48import android.provider.ContactsContract.CommonDataKinds; 49import android.provider.ContactsContract.Contacts; 50import android.provider.ContactsContract.Data; 51import android.provider.ContactsContract.Intents; 52import android.provider.ContactsContract.Presence; 53import android.provider.ContactsContract.QuickContact; 54import android.provider.ContactsContract.CommonDataKinds.Email; 55import android.text.Spannable; 56import android.text.SpannableStringBuilder; 57import android.text.TextUtils; 58import android.text.format.DateFormat; 59import android.text.format.DateUtils; 60import android.text.format.Time; 61import android.text.style.ForegroundColorSpan; 62import android.text.style.StrikethroughSpan; 63import android.text.util.Linkify; 64import android.text.util.Rfc822Token; 65import android.util.Log; 66import android.view.Gravity; 67import android.view.LayoutInflater; 68import android.view.Menu; 69import android.view.MenuInflater; 70import android.view.MenuItem; 71import android.view.MotionEvent; 72import android.view.View; 73import android.view.ViewGroup; 74import android.view.Window; 75import android.view.WindowManager; 76import android.view.View.OnClickListener; 77import android.view.View.OnTouchListener; 78import android.widget.AdapterView; 79import android.widget.ArrayAdapter; 80import android.widget.Button; 81import android.widget.ImageButton; 82import android.widget.ImageView; 83import android.widget.LinearLayout; 84import android.widget.QuickContactBadge; 85import android.widget.Spinner; 86import android.widget.TextView; 87import android.widget.Toast; 88 89import java.util.ArrayList; 90import java.util.Arrays; 91import java.util.HashMap; 92import java.util.regex.Pattern; 93 94public class EventInfoFragment extends DialogFragment implements View.OnClickListener, 95 AdapterView.OnItemSelectedListener { 96 public static final boolean DEBUG = false; 97 98 public static final String TAG = "EventInfoActivity"; 99 100 private static final String BUNDLE_KEY_EVENT_ID = "key_event_id"; 101 102 private static final String BUNDLE_KEY_START_MILLIS = "key_start_millis"; 103 104 private static final String BUNDLE_KEY_END_MILLIS = "key_end_millis"; 105 106 private static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog"; 107 108 private static final int MAX_REMINDERS = 5; 109 110 /** 111 * These are the corresponding indices into the array of strings 112 * "R.array.change_response_labels" in the resource file. 113 */ 114 static final int UPDATE_SINGLE = 0; 115 static final int UPDATE_ALL = 1; 116 117 // Query tokens for QueryHandler 118 private static final int TOKEN_QUERY_EVENT = 0; 119 private static final int TOKEN_QUERY_CALENDARS = 1; 120 private static final int TOKEN_QUERY_ATTENDEES = 2; 121 private static final int TOKEN_QUERY_REMINDERS = 3; 122 private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 4; 123 124 private static final String[] EVENT_PROJECTION = new String[] { 125 Events._ID, // 0 do not remove; used in DeleteEventHelper 126 Events.TITLE, // 1 do not remove; used in DeleteEventHelper 127 Events.RRULE, // 2 do not remove; used in DeleteEventHelper 128 Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper 129 Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper 130 Events.DTSTART, // 5 do not remove; used in DeleteEventHelper 131 Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper 132 Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper 133 Events.DESCRIPTION, // 8 134 Events.EVENT_LOCATION, // 9 135 Events.HAS_ALARM, // 10 136 Calendars.ACCESS_LEVEL, // 11 137 Calendars.COLOR, // 12 138 Events.HAS_ATTENDEE_DATA, // 13 139 Events.GUESTS_CAN_MODIFY, // 14 140 // TODO Events.GUESTS_CAN_INVITE_OTHERS has not been implemented in calendar provider 141 Events.GUESTS_CAN_INVITE_OTHERS, // 15 142 Events.ORGANIZER, // 16 143 Events.ORIGINAL_EVENT // 17 do not remove; used in DeleteEventHelper 144 }; 145 private static final int EVENT_INDEX_ID = 0; 146 private static final int EVENT_INDEX_TITLE = 1; 147 private static final int EVENT_INDEX_RRULE = 2; 148 private static final int EVENT_INDEX_ALL_DAY = 3; 149 private static final int EVENT_INDEX_CALENDAR_ID = 4; 150 private static final int EVENT_INDEX_SYNC_ID = 6; 151 private static final int EVENT_INDEX_EVENT_TIMEZONE = 7; 152 private static final int EVENT_INDEX_DESCRIPTION = 8; 153 private static final int EVENT_INDEX_EVENT_LOCATION = 9; 154 private static final int EVENT_INDEX_HAS_ALARM = 10; 155 private static final int EVENT_INDEX_ACCESS_LEVEL = 11; 156 private static final int EVENT_INDEX_COLOR = 12; 157 private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13; 158 private static final int EVENT_INDEX_GUESTS_CAN_MODIFY = 14; 159 private static final int EVENT_INDEX_CAN_INVITE_OTHERS = 15; 160 private static final int EVENT_INDEX_ORGANIZER = 16; 161 162 private static final String[] ATTENDEES_PROJECTION = new String[] { 163 Attendees._ID, // 0 164 Attendees.ATTENDEE_NAME, // 1 165 Attendees.ATTENDEE_EMAIL, // 2 166 Attendees.ATTENDEE_RELATIONSHIP, // 3 167 Attendees.ATTENDEE_STATUS, // 4 168 }; 169 private static final int ATTENDEES_INDEX_ID = 0; 170 private static final int ATTENDEES_INDEX_NAME = 1; 171 private static final int ATTENDEES_INDEX_EMAIL = 2; 172 private static final int ATTENDEES_INDEX_RELATIONSHIP = 3; 173 private static final int ATTENDEES_INDEX_STATUS = 4; 174 175 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; 176 177 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " 178 + Attendees.ATTENDEE_EMAIL + " ASC"; 179 180 static final String[] CALENDARS_PROJECTION = new String[] { 181 Calendars._ID, // 0 182 Calendars.DISPLAY_NAME, // 1 183 Calendars.OWNER_ACCOUNT, // 2 184 Calendars.ORGANIZER_CAN_RESPOND // 3 185 }; 186 static final int CALENDARS_INDEX_DISPLAY_NAME = 1; 187 static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; 188 static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3; 189 190 static final String CALENDARS_WHERE = Calendars._ID + "=?"; 191 static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.DISPLAY_NAME + "=?"; 192 193 private static final String[] REMINDERS_PROJECTION = new String[] { 194 Reminders._ID, // 0 195 Reminders.MINUTES, // 1 196 }; 197 private static final int REMINDERS_INDEX_MINUTES = 1; 198 private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=? AND (" + 199 Reminders.METHOD + "=" + Reminders.METHOD_ALERT + " OR " + Reminders.METHOD + "=" + 200 Reminders.METHOD_DEFAULT + ")"; 201 private static final String REMINDERS_SORT = Reminders.MINUTES; 202 203 private static final int MENU_GROUP_REMINDER = 1; 204 private static final int MENU_GROUP_EDIT = 2; 205 private static final int MENU_GROUP_DELETE = 3; 206 207 private static final int MENU_ADD_REMINDER = 1; 208 private static final int MENU_EDIT = 2; 209 private static final int MENU_DELETE = 3; 210 211 private View mView; 212 private LinearLayout mRemindersContainer; 213 private LinearLayout mOrganizerContainer; 214 private TextView mOrganizerView; 215 216 private Uri mUri; 217 private long mEventId; 218 private Cursor mEventCursor; 219 private Cursor mAttendeesCursor; 220 private Cursor mCalendarsCursor; 221 222 private long mStartMillis; 223 private long mEndMillis; 224 225 private boolean mHasAttendeeData; 226 private boolean mIsOrganizer; 227 private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; 228 private boolean mOrganizerCanRespond; 229 private String mCalendarOwnerAccount; 230 private boolean mCanModifyCalendar; 231 private boolean mIsBusyFreeCalendar; 232 private boolean mCanModifyEvent; 233 private int mNumOfAttendees; 234 private String mOrganizer; 235 236 private ArrayList<Integer> mOriginalMinutes = new ArrayList<Integer>(); 237 private ArrayList<LinearLayout> mReminderItems = new ArrayList<LinearLayout>(0); 238 private ArrayList<Integer> mReminderValues; 239 private ArrayList<String> mReminderLabels; 240 private int mDefaultReminderMinutes; 241 private boolean mOriginalHasAlarm; 242 243 private EditResponseHelper mEditResponseHelper; 244 245 private int mResponseOffset; 246 private int mOriginalAttendeeResponse; 247 private int mAttendeeResponseFromIntent = EditEventHelper.ATTENDEE_NO_RESPONSE; 248 private boolean mIsRepeating; 249 private boolean mIsDuplicateName; 250 251 private Pattern mWildcardPattern = Pattern.compile("^.*$"); 252 private LayoutInflater mLayoutInflater; 253 private LinearLayout mReminderAdder; 254 255 // TODO This can be removed when the contacts content provider doesn't return duplicates 256 private int mUpdateCounts; 257 private static class ViewHolder { 258 QuickContactBadge badge; 259 ImageView presence; 260 int updateCounts; 261 } 262 private HashMap<String, ViewHolder> mViewHolders = new HashMap<String, ViewHolder>(); 263 private PresenceQueryHandler mPresenceQueryHandler; 264 265 private static final Uri CONTACT_DATA_WITH_PRESENCE_URI = Data.CONTENT_URI; 266 267 int PRESENCE_PROJECTION_CONTACT_ID_INDEX = 0; 268 int PRESENCE_PROJECTION_PRESENCE_INDEX = 1; 269 int PRESENCE_PROJECTION_EMAIL_INDEX = 2; 270 int PRESENCE_PROJECTION_PHOTO_ID_INDEX = 3; 271 272 private static final String[] PRESENCE_PROJECTION = new String[] { 273 Email.CONTACT_ID, // 0 274 Email.CONTACT_PRESENCE, // 1 275 Email.DATA, // 2 276 Email.PHOTO_ID, // 3 277 }; 278 279 ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>(); 280 ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>(); 281 ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>(); 282 ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>(); 283 private int mColor; 284 285 private QueryHandler mHandler; 286 287 private static final int DIALOG_WIDTH = 500; // FRAG_TODO scale 288 private static final int DIALOG_HEIGHT = 500; 289 private boolean mIsDialog = false; 290 private int mX = -1; 291 private int mY = -1; 292 293 private class QueryHandler extends AsyncQueryService { 294 public QueryHandler(Context context) { 295 super(context); 296 } 297 298 @Override 299 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 300 // if the activity is finishing, then close the cursor and return 301 final Activity activity = getActivity(); 302 if (activity == null || activity.isFinishing()) { 303 cursor.close(); 304 return; 305 } 306 307 switch (token) { 308 case TOKEN_QUERY_EVENT: 309 mEventCursor = Utils.matrixCursorFromCursor(cursor); 310 if (initEventCursor()) { 311 // The cursor is empty. This can happen if the event was 312 // deleted. 313 // FRAG_TODO we should no longer rely on Activity.finish() 314 activity.finish(); 315 return; 316 } 317 updateEvent(mView); 318 319 // start calendar query 320 Uri uri = Calendars.CONTENT_URI; 321 String[] args = new String[] { 322 Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))}; 323 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION, 324 CALENDARS_WHERE, args, null); 325 break; 326 case TOKEN_QUERY_CALENDARS: 327 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor); 328 updateCalendar(mView); 329 // FRAG_TODO fragments shouldn't set the title anymore 330 updateTitle(); 331 // update the action bar since our option set might have changed 332 activity.invalidateOptionsMenu(); 333 334 // this is used for both attendees and reminders 335 args = new String[] { Long.toString(mEventId) }; 336 337 // start attendees query 338 uri = Attendees.CONTENT_URI; 339 startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION, 340 ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER); 341 342 // start reminders query 343 mOriginalHasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; 344 if (mOriginalHasAlarm) { 345 uri = Reminders.CONTENT_URI; 346 startQuery(TOKEN_QUERY_REMINDERS, null, uri, REMINDERS_PROJECTION, 347 REMINDERS_WHERE, args, REMINDERS_SORT); 348 } else { 349 // if no reminders, hide the appropriate fields 350 updateRemindersVisibility(); 351 } 352 break; 353 case TOKEN_QUERY_ATTENDEES: 354 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor); 355 initAttendeesCursor(mView); 356 updateResponse(mView); 357 break; 358 case TOKEN_QUERY_REMINDERS: 359 MatrixCursor reminderCursor = Utils.matrixCursorFromCursor(cursor); 360 try { 361 // First pass: collect all the custom reminder minutes 362 // (e.g., a reminder of 8 minutes) into a global list. 363 while (reminderCursor.moveToNext()) { 364 int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES); 365 EventViewUtils.addMinutesToList( 366 activity, mReminderValues, mReminderLabels, minutes); 367 } 368 369 // Second pass: create the reminder spinners 370 reminderCursor.moveToPosition(-1); 371 while (reminderCursor.moveToNext()) { 372 int minutes = reminderCursor.getInt(REMINDERS_INDEX_MINUTES); 373 mOriginalMinutes.add(minutes); 374 EventViewUtils.addReminder(activity, mRemindersContainer, 375 EventInfoFragment.this, mReminderItems, mReminderValues, 376 mReminderLabels, minutes); 377 } 378 } finally { 379 updateRemindersVisibility(); 380 reminderCursor.close(); 381 } 382 break; 383 case TOKEN_QUERY_DUPLICATE_CALENDARS: 384 mIsDuplicateName = cursor.getCount() > 1; 385 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); 386//CLEANUP String ownerAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); 387// if (mIsDuplicateName && !calendarName.equalsIgnoreCase(ownerAccount)) { 388// Resources res = activity.getResources(); 389// TextView ownerText = (TextView) mView.findViewById(R.id.owner); 390// ownerText.setText(ownerAccount); 391// ownerText.setTextColor(res.getColor(R.color.calendar_owner_text_color)); 392// } else { 393// setVisibilityCommon(mView, R.id.owner, View.GONE); 394// } 395 setTextCommon(mView, R.id.calendar, calendarName); 396 break; 397 } 398 cursor.close(); 399 } 400 401 } 402 403 public EventInfoFragment() { 404 mUri = null; 405 } 406 407 public EventInfoFragment(Uri uri, long startMillis, long endMillis, int attendeeResponse) { 408 setStyle(DialogFragment.STYLE_NO_TITLE, 0); 409 mUri = uri; 410 mStartMillis = startMillis; 411 mEndMillis = endMillis; 412 mAttendeeResponseFromIntent = attendeeResponse; 413 } 414 415 public EventInfoFragment(long eventId, long startMillis, long endMillis) { 416 this(ContentUris.withAppendedId(Events.CONTENT_URI, eventId), 417 startMillis, endMillis, EventInfoActivity.ATTENDEE_NO_RESPONSE); 418 mEventId = eventId; 419 } 420 421 @Override 422 public void onActivityCreated(Bundle savedInstanceState) { 423 super.onActivityCreated(savedInstanceState); 424 425 if (savedInstanceState != null) { 426 mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false); 427 } 428 429 if (mIsDialog) { 430 applyDialogParams(); 431 } 432 } 433 434 private void applyDialogParams() { 435 Dialog dialog = getDialog(); 436 dialog.setCanceledOnTouchOutside(true); 437 438 Window window = dialog.getWindow(); 439 window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 440 441 WindowManager.LayoutParams a = window.getAttributes(); 442 a.dimAmount = .4f; 443 444 a.width = DIALOG_WIDTH; 445 446 if (mX != -1 || mY != -1) { 447 a.x = mX - a.width - 64; // FRAG_TODO event sender should return the left edge or a rect 448 a.y = mY - 64; // FRAG_TODO should set height after layout is done 449 a.gravity = Gravity.LEFT | Gravity.TOP; 450 } 451 452 window.setAttributes(a); 453 } 454 455 public void setDialogParams(int x, int y) { 456 mIsDialog = true; 457 mX = x; 458 mY = y; 459 } 460 461 // This is called when one of the "remove reminder" buttons is selected. 462 public void onClick(View v) { 463 LinearLayout reminderItem = (LinearLayout) v.getParent(); 464 LinearLayout parent = (LinearLayout) reminderItem.getParent(); 465 parent.removeView(reminderItem); 466 mReminderItems.remove(reminderItem); 467 updateRemindersVisibility(); 468 } 469 470 public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { 471 // If they selected the "No response" option, then don't display the 472 // dialog asking which events to change. 473 if (id == 0 && mResponseOffset == 0) { 474 return; 475 } 476 477 // If this is not a repeating event, then don't display the dialog 478 // asking which events to change. 479 if (!mIsRepeating) { 480 return; 481 } 482 483 // If the selection is the same as the original, then don't display the 484 // dialog asking which events to change. 485 int index = findResponseIndexFor(mOriginalAttendeeResponse); 486 if (position == index + mResponseOffset) { 487 return; 488 } 489 490 // This is a repeating event. We need to ask the user if they mean to 491 // change just this one instance or all instances. 492 mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents()); 493 } 494 495 public void onNothingSelected(AdapterView<?> parent) { 496 } 497 498 @Override 499 public void onAttach(Activity activity) { 500 super.onAttach(activity); 501 mEditResponseHelper = new EditResponseHelper(activity); 502 setHasOptionsMenu(true); 503 mHandler = new QueryHandler(activity); 504 mPresenceQueryHandler = new PresenceQueryHandler(activity, activity.getContentResolver()); 505 } 506 507 @Override 508 public View onCreateView(LayoutInflater inflater, ViewGroup container, 509 Bundle savedInstanceState) { 510 mLayoutInflater = inflater; 511 mView = inflater.inflate(R.layout.event_info_activity, null); 512 mRemindersContainer = (LinearLayout) mView.findViewById(R.id.reminders_container); 513 mOrganizerContainer = (LinearLayout) mView.findViewById(R.id.organizer_container); 514 mOrganizerView = (TextView) mView.findViewById(R.id.organizer); 515 516 // Initialize the reminder values array. 517 Resources r = getActivity().getResources(); 518 String[] strings = r.getStringArray(R.array.reminder_minutes_values); 519 int size = strings.length; 520 ArrayList<Integer> list = new ArrayList<Integer>(size); 521 for (int i = 0 ; i < size ; i++) { 522 list.add(Integer.parseInt(strings[i])); 523 } 524 mReminderValues = list; 525 String[] labels = r.getStringArray(R.array.reminder_minutes_labels); 526 mReminderLabels = new ArrayList<String>(Arrays.asList(labels)); 527 528 SharedPreferences prefs = CalendarPreferenceActivity.getSharedPreferences(getActivity()); 529 String durationString = 530 prefs.getString(CalendarPreferenceActivity.KEY_DEFAULT_REMINDER, "0"); 531 mDefaultReminderMinutes = Integer.parseInt(durationString); 532 533 // Setup the + Add Reminder Button 534 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { 535 public void onClick(View v) { 536 addReminder(); 537 } 538 }; 539 ImageButton reminderAddButton = (ImageButton) mView.findViewById(R.id.reminder_add); 540 reminderAddButton.setOnClickListener(addReminderOnClickListener); 541 542//CLEANUP mReminderAdder = (LinearLayout) mView.findViewById(R.id.reminder_adder); 543 544 if (mUri == null) { 545 // restore event ID from bundle 546 mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID); 547 mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 548 mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS); 549 mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS); 550 } 551 552 // start loading the data 553 mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, 554 null, null, null); 555 556 Button b = (Button) mView.findViewById(R.id.done); 557 b.setOnClickListener(new OnClickListener() { 558 @Override 559 public void onClick(View v) { 560 EventInfoFragment.this.dismiss(); 561 }}); 562 563 return mView; 564 } 565 566 private void updateTitle() { 567 Resources res = getActivity().getResources(); 568 if (mCanModifyCalendar && !mIsOrganizer) { 569 getActivity().setTitle(res.getString(R.string.event_info_title_invite)); 570 } else { 571 getActivity().setTitle(res.getString(R.string.event_info_title)); 572 } 573 } 574 575 /** 576 * Initializes the event cursor, which is expected to point to the first 577 * (and only) result from a query. 578 * @return true if the cursor is empty. 579 */ 580 private boolean initEventCursor() { 581 if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) { 582 return true; 583 } 584 mEventCursor.moveToFirst(); 585 mEventId = mEventCursor.getInt(EVENT_INDEX_ID); 586 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); 587 mIsRepeating = (rRule != null); 588 return false; 589 } 590 591 private static class Attendee { 592 String mName; 593 String mEmail; 594 595 Attendee(String name, String email) { 596 mName = name; 597 mEmail = email; 598 } 599 600 String getDisplayName() { 601 if (TextUtils.isEmpty(mName)) { 602 return mEmail; 603 } else { 604 return mName; 605 } 606 } 607 } 608 609 @SuppressWarnings("fallthrough") 610 private void initAttendeesCursor(View view) { 611 mOriginalAttendeeResponse = EditEventHelper.ATTENDEE_NO_RESPONSE; 612 mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; 613 mNumOfAttendees = 0; 614 if (mAttendeesCursor != null) { 615 mNumOfAttendees = mAttendeesCursor.getCount(); 616 if (mAttendeesCursor.moveToFirst()) { 617 mAcceptedAttendees.clear(); 618 mDeclinedAttendees.clear(); 619 mTentativeAttendees.clear(); 620 mNoResponseAttendees.clear(); 621 622 do { 623 int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 624 String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME); 625 String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 626 627 if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) == 628 Attendees.RELATIONSHIP_ORGANIZER) { 629 // Overwrites the one from Event table if available 630 if (name != null && name.length() > 0) { 631 mOrganizer = name; 632 } else if (email != null && email.length() > 0) { 633 mOrganizer = email; 634 } 635 } 636 637 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE && 638 mCalendarOwnerAccount.equalsIgnoreCase(email)) { 639 mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID); 640 mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 641 } else { 642 // Don't show your own status in the list because: 643 // 1) it doesn't make sense for event without other guests. 644 // 2) there's a spinner for that for events with guests. 645 switch(status) { 646 case Attendees.ATTENDEE_STATUS_ACCEPTED: 647 mAcceptedAttendees.add(new Attendee(name, email)); 648 break; 649 case Attendees.ATTENDEE_STATUS_DECLINED: 650 mDeclinedAttendees.add(new Attendee(name, email)); 651 break; 652 case Attendees.ATTENDEE_STATUS_TENTATIVE: 653 mTentativeAttendees.add(new Attendee(name, email)); 654 break; 655 default: 656 mNoResponseAttendees.add(new Attendee(name, email)); 657 } 658 } 659 } while (mAttendeesCursor.moveToNext()); 660 mAttendeesCursor.moveToFirst(); 661 662 updateAttendees(view); 663 } 664 } 665 // only show the organizer if we're not the organizer and if 666 // we have attendee data (might have been removed by the server 667 // for events with a lot of attendees). 668//CLEANUP if (!mIsOrganizer && mHasAttendeeData) { 669// mOrganizerContainer.setVisibility(View.VISIBLE); 670// mOrganizerView.setText(mOrganizer); 671// } else { 672// mOrganizerContainer.setVisibility(View.GONE); 673// } 674 } 675 676 @Override 677 public void onSaveInstanceState(Bundle outState) { 678 super.onSaveInstanceState(outState); 679 outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId); 680 outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis); 681 outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis); 682 683 outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog); 684 } 685 686 687 @Override 688 public void onDestroyView() { 689 ArrayList<Integer> reminderMinutes = EventViewUtils.reminderItemsToMinutes(mReminderItems, 690 mReminderValues); 691 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); 692 boolean changed = EditEventHelper.saveReminders(ops, mEventId, reminderMinutes, 693 mOriginalMinutes, false /* no force save */); 694 mHandler.startBatch(mHandler.getNextToken(), null, 695 Calendars.CONTENT_URI.getAuthority(), ops, Utils.UNDO_DELAY); 696 697 // Update the "hasAlarm" field for the event 698 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 699 int len = reminderMinutes.size(); 700 boolean hasAlarm = len > 0; 701 if (hasAlarm != mOriginalHasAlarm) { 702 ContentValues values = new ContentValues(); 703 values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0); 704 mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, 705 null, null, Utils.UNDO_DELAY); 706 } 707 708 changed |= saveResponse(); 709 if (changed) { 710 Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show(); 711 } 712 super.onDestroyView(); 713 } 714 715 @Override 716 public void onDestroy() { 717 if (mEventCursor != null) { 718 mEventCursor.close(); 719 } 720 if (mCalendarsCursor != null) { 721 mCalendarsCursor.close(); 722 } 723 if (mAttendeesCursor != null) { 724 mAttendeesCursor.close(); 725 } 726 super.onDestroy(); 727 } 728 729 private boolean canAddReminders() { 730 return !mIsBusyFreeCalendar && mReminderItems.size() < MAX_REMINDERS; 731 } 732 733 private void addReminder() { 734 // TODO: when adding a new reminder, make it different from the 735 // last one in the list (if any). 736 if (mDefaultReminderMinutes == 0) { 737 EventViewUtils.addReminder(getActivity(), mRemindersContainer, this, mReminderItems, 738 mReminderValues, mReminderLabels, 10 /* minutes */); 739 } else { 740 EventViewUtils.addReminder(getActivity(), mRemindersContainer, this, mReminderItems, 741 mReminderValues, mReminderLabels, mDefaultReminderMinutes); 742 } 743 updateRemindersVisibility(); 744 } 745 746 @Override 747 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 748 MenuItem item; 749 item = menu.add(MENU_GROUP_REMINDER, MENU_ADD_REMINDER, 0, 750 R.string.add_new_reminder); 751 item.setIcon(R.drawable.ic_menu_reminder); 752 item.setAlphabeticShortcut('r'); 753 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 754 755 item = menu.add(MENU_GROUP_EDIT, MENU_EDIT, 0, R.string.edit_event_label); 756 item.setIcon(android.R.drawable.ic_menu_edit); 757 item.setAlphabeticShortcut('e'); 758 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 759 760 item = menu.add(MENU_GROUP_DELETE, MENU_DELETE, 0, R.string.delete_event_label); 761 item.setIcon(android.R.drawable.ic_menu_delete); 762 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 763 764 super.onCreateOptionsMenu(menu, inflater); 765 } 766 767 @Override 768 public void onPrepareOptionsMenu(Menu menu) { 769 boolean canAddReminders = canAddReminders(); 770 menu.setGroupVisible(MENU_GROUP_REMINDER, canAddReminders); 771 menu.setGroupEnabled(MENU_GROUP_REMINDER, canAddReminders); 772 773 menu.setGroupVisible(MENU_GROUP_EDIT, mCanModifyEvent); 774 menu.setGroupEnabled(MENU_GROUP_EDIT, mCanModifyEvent); 775 menu.setGroupVisible(MENU_GROUP_DELETE, mCanModifyCalendar); 776 menu.setGroupEnabled(MENU_GROUP_DELETE, mCanModifyCalendar); 777 778 super.onPrepareOptionsMenu(menu); 779 } 780 781 @Override 782 public boolean onOptionsItemSelected(MenuItem item) { 783 super.onOptionsItemSelected(item); 784 switch (item.getItemId()) { 785 case MENU_ADD_REMINDER: 786 addReminder(); 787 break; 788 case MENU_EDIT: 789 doEdit(); 790 break; 791 case MENU_DELETE: 792 doDelete(); 793 break; 794 } 795 return true; 796 } 797 798//CLEANUP @Override 799// public boolean onKeyDown(int keyCode, KeyEvent event) { 800// if (keyCode == KeyEvent.KEYCODE_DEL) { 801// doDelete(); 802// return true; 803// } 804// return super.onKeyDown(keyCode, event); 805// } 806 807 private void updateRemindersVisibility() { 808//CLEANUP if (mIsBusyFreeCalendar) { 809// mRemindersContainer.setVisibility(View.GONE); 810// } else { 811// mRemindersContainer.setVisibility(View.VISIBLE); 812// mReminderAdder.setVisibility(canAddReminders() ? View.VISIBLE : View.GONE); 813// } 814 } 815 816 /** 817 * Asynchronously saves the response to an invitation if the user changed 818 * the response. Returns true if the database will be updated. 819 * 820 * @param cr the ContentResolver 821 * @return true if the database will be changed 822 */ 823 private boolean saveResponse() { 824 if (mAttendeesCursor == null || mEventCursor == null) { 825 return false; 826 } 827 Spinner spinner = (Spinner) getView().findViewById(R.id.response_value); 828 int position = spinner.getSelectedItemPosition() - mResponseOffset; 829 if (position <= 0) { 830 return false; 831 } 832 833 int status = EditEventHelper.ATTENDEE_VALUES[position]; 834 835 // If the status has not changed, then don't update the database 836 if (status == mOriginalAttendeeResponse) { 837 return false; 838 } 839 840 // If we never got an owner attendee id we can't set the status 841 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) { 842 return false; 843 } 844 845 if (!mIsRepeating) { 846 // This is a non-repeating event 847 updateResponse(mEventId, mCalendarOwnerAttendeeId, status); 848 return true; 849 } 850 851 // This is a repeating event 852 int whichEvents = mEditResponseHelper.getWhichEvents(); 853 switch (whichEvents) { 854 case -1: 855 return false; 856 case UPDATE_SINGLE: 857 createExceptionResponse(mEventId, mCalendarOwnerAttendeeId, status); 858 return true; 859 case UPDATE_ALL: 860 updateResponse(mEventId, mCalendarOwnerAttendeeId, status); 861 return true; 862 default: 863 Log.e(TAG, "Unexpected choice for updating invitation response"); 864 break; 865 } 866 return false; 867 } 868 869 private void updateResponse(long eventId, long attendeeId, int status) { 870 // Update the attendee status in the attendees table. the provider 871 // takes care of updating the self attendance status. 872 ContentValues values = new ContentValues(); 873 874 if (!TextUtils.isEmpty(mCalendarOwnerAccount)) { 875 values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount); 876 } 877 values.put(Attendees.ATTENDEE_STATUS, status); 878 values.put(Attendees.EVENT_ID, eventId); 879 880 Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId); 881 882 mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, 883 null, null, Utils.UNDO_DELAY); 884 } 885 886 private void createExceptionResponse(long eventId, long attendeeId, 887 int status) { 888 if (mEventCursor == null || !mEventCursor.moveToFirst()) { 889 return; 890 } 891 892 ContentValues values = new ContentValues(); 893 894 String title = mEventCursor.getString(EVENT_INDEX_TITLE); 895 String timezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); 896 int calendarId = mEventCursor.getInt(EVENT_INDEX_CALENDAR_ID); 897 boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0; 898 String syncId = mEventCursor.getString(EVENT_INDEX_SYNC_ID); 899 900 values.put(Events.TITLE, title); 901 values.put(Events.EVENT_TIMEZONE, timezone); 902 values.put(Events.ALL_DAY, allDay ? 1 : 0); 903 values.put(Events.CALENDAR_ID, calendarId); 904 values.put(Events.DTSTART, mStartMillis); 905 values.put(Events.DTEND, mEndMillis); 906 values.put(Events.ORIGINAL_EVENT, syncId); 907 values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis); 908 values.put(Events.ORIGINAL_ALL_DAY, allDay ? 1 : 0); 909 values.put(Events.STATUS, Events.STATUS_CONFIRMED); 910 values.put(Events.SELF_ATTENDEE_STATUS, status); 911 912 // Create a recurrence exception 913 mHandler.startInsert(mHandler.getNextToken(), null, 914 Events.CONTENT_URI, values, Utils.UNDO_DELAY); 915 } 916 917 private int findResponseIndexFor(int response) { 918 int size = EditEventHelper.ATTENDEE_VALUES.length; 919 for (int index = 0; index < size; index++) { 920 if (EditEventHelper.ATTENDEE_VALUES[index] == response) { 921 return index; 922 } 923 } 924 return 0; 925 } 926 927 private void doEdit() { 928 CalendarController.getInstance(getActivity()).sendEventRelatedEvent( 929 this, EventType.EDIT_EVENT, mEventId, mStartMillis, mEndMillis, 0, 0); 930 } 931 932 private void doDelete() { 933 CalendarController.getInstance(getActivity()).sendEventRelatedEvent( 934 this, EventType.DELETE_EVENT, mEventId, mStartMillis, mEndMillis, 0, 0); 935 } 936 937 private void updateEvent(View view) { 938 if (mEventCursor == null) { 939 return; 940 } 941 942 String eventName = mEventCursor.getString(EVENT_INDEX_TITLE); 943 if (eventName == null || eventName.length() == 0) { 944 eventName = getActivity().getString(R.string.no_title_label); 945 } 946 947 boolean allDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0; 948 String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION); 949 String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION); 950 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); 951 boolean hasAlarm = mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) != 0; 952 String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); 953 mColor = mEventCursor.getInt(EVENT_INDEX_COLOR) & 0xbbffffff; 954 955 view.findViewById(R.id.color).setBackgroundColor(mColor); 956 957 TextView title = (TextView) view.findViewById(R.id.title); 958 title.setTextColor(mColor); 959 960// View divider = view.findViewById(R.id.divider); 961// divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); 962 963 // What 964 if (eventName != null) { 965 setTextCommon(view, R.id.title, eventName); 966 } 967 968 // When 969 String when; 970 int flags; 971 if (allDay) { 972 flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_WEEKDAY 973 | DateUtils.FORMAT_SHOW_DATE; 974 } else { 975 flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE; 976 if (DateFormat.is24HourFormat(getActivity())) { 977 flags |= DateUtils.FORMAT_24HOUR; 978 } 979 } 980 when = DateUtils.formatDateRange(getActivity(), mStartMillis, mEndMillis, flags); 981 setTextCommon(view, R.id.when, when); 982 983//CLEANUP // Show the event timezone if it is different from the local timezone 984// Time time = new Time(); 985// String localTimezone = time.timezone; 986// if (allDay) { 987// localTimezone = Time.TIMEZONE_UTC; 988// } 989// if (eventTimezone != null && !localTimezone.equals(eventTimezone) && !allDay) { 990// String displayName; 991// TimeZone tz = TimeZone.getTimeZone(localTimezone); 992// if (tz == null || tz.getID().equals("GMT")) { 993// displayName = localTimezone; 994// } else { 995// displayName = tz.getDisplayName(); 996// } 997// 998// setTextCommon(view, R.id.timezone, displayName); 999// setVisibilityCommon(view, R.id.timezone_container, View.VISIBLE); 1000// } else { 1001// setVisibilityCommon(view, R.id.timezone_container, View.GONE); 1002// } 1003 1004 // Repeat 1005 if (rRule != null) { 1006 EventRecurrence eventRecurrence = new EventRecurrence(); 1007 eventRecurrence.parse(rRule); 1008 Time date = new Time(); 1009 if (allDay) { 1010 date.timezone = Time.TIMEZONE_UTC; 1011 } 1012 date.set(mStartMillis); 1013 eventRecurrence.setStartDate(date); 1014 String repeatString = EventRecurrenceFormatter.getRepeatString( 1015 getActivity().getResources(), eventRecurrence); 1016 setTextCommon(view, R.id.repeat, repeatString); 1017 setVisibilityCommon(view, R.id.repeat_container, View.VISIBLE); 1018 } else { 1019 setVisibilityCommon(view, R.id.repeat_container, View.GONE); 1020 } 1021 1022 // Where 1023 if (location == null || location.length() == 0) { 1024 setVisibilityCommon(view, R.id.where, View.GONE); 1025 } else { 1026 final TextView textView = (TextView) view.findViewById(R.id.where); 1027 if (textView != null) { 1028 textView.setAutoLinkMask(0); 1029 textView.setText(location); 1030 Linkify.addLinks(textView, mWildcardPattern, "geo:0,0?q="); 1031 textView.setOnTouchListener(new OnTouchListener() { 1032 public boolean onTouch(View v, MotionEvent event) { 1033 try { 1034 return v.onTouchEvent(event); 1035 } catch (ActivityNotFoundException e) { 1036 // ignore 1037 return true; 1038 } 1039 } 1040 }); 1041 } 1042 } 1043 1044 // Description 1045 if (description == null || description.length() == 0) { 1046 setVisibilityCommon(view, R.id.description, View.GONE); 1047 } else { 1048 setTextCommon(view, R.id.description, description); 1049 } 1050 } 1051 1052 private void updateCalendar(View view) { 1053 mCalendarOwnerAccount = ""; 1054 if (mCalendarsCursor != null && mEventCursor != null) { 1055 mCalendarsCursor.moveToFirst(); 1056 String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); 1057 mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount; 1058 mOrganizerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0; 1059 1060 String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); 1061 1062 // start duplicate calendars query 1063 mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, Calendars.CONTENT_URI, 1064 CALENDARS_PROJECTION, CALENDARS_DUPLICATE_NAME_WHERE, 1065 new String[] {displayName}, null); 1066 1067 String eventOrganizer = mEventCursor.getString(EVENT_INDEX_ORGANIZER); 1068 mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(eventOrganizer); 1069 mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; 1070 mOrganizer = eventOrganizer; 1071 mCanModifyCalendar = 1072 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) >= Calendars.CONTRIBUTOR_ACCESS; 1073 mIsBusyFreeCalendar = 1074 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.FREEBUSY_ACCESS; 1075 mCanModifyEvent = mCanModifyCalendar 1076 && (mIsOrganizer || (mEventCursor.getInt(EVENT_INDEX_GUESTS_CAN_MODIFY) != 0)); 1077 if (mCanModifyEvent) { 1078 Button b = (Button) mView.findViewById(R.id.edit); 1079 b.setOnClickListener(new OnClickListener() { 1080 @Override 1081 public void onClick(View v) { 1082 doEdit(); 1083 EventInfoFragment.this.dismiss(); 1084 }}); 1085 b.setVisibility(View.VISIBLE); 1086 } 1087 } else { 1088//CLEANUP setVisibilityCommon(view, R.id.calendar_container, View.GONE); 1089 } 1090 } 1091 1092 private void updateAttendees(View view) { 1093 TextView tv = (TextView) view.findViewById(R.id.attendee_list); 1094 SpannableStringBuilder sb = new SpannableStringBuilder(); 1095 formatAttendees(mAcceptedAttendees, sb, Attendees.ATTENDEE_STATUS_ACCEPTED); 1096 formatAttendees(mDeclinedAttendees, sb, Attendees.ATTENDEE_STATUS_DECLINED); 1097 formatAttendees(mTentativeAttendees, sb, Attendees.ATTENDEE_STATUS_TENTATIVE); 1098 formatAttendees(mNoResponseAttendees, sb, Attendees.ATTENDEE_STATUS_NONE); 1099 tv.setText(sb); 1100 1101//CLEANUP LinearLayout attendeesLayout = (LinearLayout) view.findViewById(R.id.attendee_list); 1102// attendeesLayout.removeAllViewsInLayout(); 1103// ++mUpdateCounts; 1104// if(mAcceptedAttendees.size() == 0 && mDeclinedAttendees.size() == 0 && 1105// mTentativeAttendees.size() == mNoResponseAttendees.size()) { 1106// // If all guests have no response just list them as guests, 1107// CharSequence guestsLabel = 1108// getActivity().getResources().getText(R.string.attendees_label); 1109// addAttendeesToLayout(mNoResponseAttendees, attendeesLayout, guestsLabel); 1110// } else { 1111// // If we have any responses then divide them up by response 1112// CharSequence[] entries; 1113// entries = getActivity().getResources().getTextArray(R.array.response_labels2); 1114// addAttendeesToLayout(mAcceptedAttendees, attendeesLayout, entries[0]); 1115// addAttendeesToLayout(mDeclinedAttendees, attendeesLayout, entries[2]); 1116// addAttendeesToLayout(mTentativeAttendees, attendeesLayout, entries[1]); 1117// } 1118 } 1119 1120 private void formatAttendees(ArrayList<Attendee> attendees, SpannableStringBuilder sb, int type) { 1121 if (attendees.size() <= 0) { 1122 return; 1123 } 1124 1125 int begin = sb.length(); 1126 boolean firstTime = sb.length() == 0; 1127 1128 if (firstTime == false) { 1129 begin += 2; // skip over the ", " for formatting. 1130 } 1131 1132 for (Attendee attendee : attendees) { 1133 if (firstTime) { 1134 firstTime = false; 1135 } else { 1136 sb.append(", "); 1137 } 1138 1139 String name = attendee.getDisplayName(); 1140 sb.append(name); 1141 } 1142 1143 switch (type) { 1144 case Attendees.ATTENDEE_STATUS_ACCEPTED: 1145 break; 1146 case Attendees.ATTENDEE_STATUS_DECLINED: 1147 sb.setSpan(new StrikethroughSpan(), begin, sb.length(), 1148 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1149 // fall through 1150 default: 1151 // The last INCLUSIVE causes the foreground color to be applied 1152 // to the rest of the span. If not, the comma at the end of the 1153 // declined or tentative may be black. 1154 sb.setSpan(new ForegroundColorSpan(0xFF888888), begin, sb.length(), 1155 Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 1156 break; 1157 } 1158 } 1159 1160 private void addAttendeesToLayout(ArrayList<Attendee> attendees, LinearLayout attendeeList, 1161 CharSequence sectionTitle) { 1162 if (attendees.size() == 0) { 1163 return; 1164 } 1165 1166 // Yes/No/Maybe Title 1167 View titleView = mLayoutInflater.inflate(R.layout.contact_item, null); 1168 titleView.findViewById(R.id.badge).setVisibility(View.GONE); 1169 View divider = titleView.findViewById(R.id.separator); 1170 divider.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); 1171 1172 TextView title = (TextView) titleView.findViewById(R.id.name); 1173 title.setText(getActivity().getString(R.string.response_label, sectionTitle, 1174 attendees.size())); 1175 title.setTextAppearance(getActivity(), R.style.TextAppearance_EventInfo_Label); 1176 attendeeList.addView(titleView); 1177 1178 // Attendees 1179 int numOfAttendees = attendees.size(); 1180 StringBuilder selection = new StringBuilder(Email.DATA + " IN ("); 1181 String[] selectionArgs = new String[numOfAttendees]; 1182 1183 for (int i = 0; i < numOfAttendees; ++i) { 1184 Attendee attendee = attendees.get(i); 1185 selectionArgs[i] = attendee.mEmail; 1186 1187 View v = mLayoutInflater.inflate(R.layout.contact_item, null); 1188 v.setTag(attendee); 1189 1190 View separator = v.findViewById(R.id.separator); 1191 separator.getBackground().setColorFilter(mColor, PorterDuff.Mode.SRC_IN); 1192 1193 // Text 1194 TextView tv = (TextView) v.findViewById(R.id.name); 1195 String name = attendee.mName; 1196 if (name == null || name.length() == 0) { 1197 name = attendee.mEmail; 1198 } 1199 tv.setText(name); 1200 1201 ViewHolder vh = new ViewHolder(); 1202 vh.badge = (QuickContactBadge) v.findViewById(R.id.badge); 1203 vh.badge.assignContactFromEmail(attendee.mEmail, true); 1204 vh.presence = (ImageView) v.findViewById(R.id.presence); 1205 mViewHolders.put(attendee.mEmail, vh); 1206 1207 if (i == 0) { 1208 selection.append('?'); 1209 } else { 1210 selection.append(", ?"); 1211 } 1212 1213 attendeeList.addView(v); 1214 } 1215 selection.append(')'); 1216 1217 mPresenceQueryHandler.startQuery(mUpdateCounts, attendees, CONTACT_DATA_WITH_PRESENCE_URI, 1218 PRESENCE_PROJECTION, selection.toString(), selectionArgs, null); 1219 } 1220 1221 private class PresenceQueryHandler extends AsyncQueryHandler { 1222 Context mContext; 1223 1224 public PresenceQueryHandler(Context context, ContentResolver cr) { 1225 super(cr); 1226 mContext = context; 1227 } 1228 1229 @Override 1230 protected void onQueryComplete(int queryIndex, Object cookie, Cursor cursor) { 1231 if (cursor == null) { 1232 if (DEBUG) { 1233 Log.e(TAG, "onQueryComplete: cursor == null"); 1234 } 1235 return; 1236 } 1237 1238 try { 1239 cursor.moveToPosition(-1); 1240 while (cursor.moveToNext()) { 1241 String email = cursor.getString(PRESENCE_PROJECTION_EMAIL_INDEX); 1242 int contactId = cursor.getInt(PRESENCE_PROJECTION_CONTACT_ID_INDEX); 1243 ViewHolder vh = mViewHolders.get(email); 1244 int photoId = cursor.getInt(PRESENCE_PROJECTION_PHOTO_ID_INDEX); 1245 if (DEBUG) { 1246 Log.e(TAG, "onQueryComplete Id: " + contactId + " PhotoId: " + photoId 1247 + " Email: " + email); 1248 } 1249 if (vh == null) { 1250 continue; 1251 } 1252 ImageView presenceView = vh.presence; 1253 if (presenceView != null) { 1254 int status = cursor.getInt(PRESENCE_PROJECTION_PRESENCE_INDEX); 1255 presenceView.setImageResource(Presence.getPresenceIconResourceId(status)); 1256 presenceView.setVisibility(View.VISIBLE); 1257 } 1258 1259 if (photoId > 0 && vh.updateCounts < queryIndex) { 1260 vh.updateCounts = queryIndex; 1261 Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 1262 contactId); 1263 1264 // TODO, modify to batch queries together 1265 ContactsAsyncHelper.updateImageViewWithContactPhotoAsync(mContext, 1266 vh.badge, personUri, R.drawable.ic_contact_picture); 1267 } 1268 } 1269 } finally { 1270 cursor.close(); 1271 } 1272 } 1273 } 1274 1275 void updateResponse(View view) { 1276 // we only let the user accept/reject/etc. a meeting if: 1277 // a) you can edit the event's containing calendar AND 1278 // b) you're not the organizer and only attendee AND 1279 // c) organizerCanRespond is enabled for the calendar 1280 // (if the attendee data has been hidden, the visible number of attendees 1281 // will be 1 -- the calendar owner's). 1282 // (there are more cases involved to be 100% accurate, such as 1283 // paying attention to whether or not an attendee status was 1284 // included in the feed, but we're currently omitting those corner cases 1285 // for simplicity). 1286//CLEANUP 1287 if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) || 1288 (mIsOrganizer && !mOrganizerCanRespond)) { 1289 setVisibilityCommon(view, R.id.response_container, View.GONE); 1290 return; 1291 } 1292 1293 setVisibilityCommon(view, R.id.response_container, View.VISIBLE); 1294 1295 Spinner spinner = (Spinner) view.findViewById(R.id.response_value); 1296 1297 mResponseOffset = 0; 1298 1299 /* If the user has previously responded to this event 1300 * we should not allow them to select no response again. 1301 * Switch the entries to a set of entries without the 1302 * no response option. 1303 */ 1304 if ((mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_INVITED) 1305 && (mOriginalAttendeeResponse != EditEventHelper.ATTENDEE_NO_RESPONSE) 1306 && (mOriginalAttendeeResponse != Attendees.ATTENDEE_STATUS_NONE)) { 1307 CharSequence[] entries; 1308 entries = getActivity().getResources().getTextArray(R.array.response_labels2); 1309 mResponseOffset = -1; 1310 ArrayAdapter<CharSequence> adapter = 1311 new ArrayAdapter<CharSequence>(getActivity(), 1312 android.R.layout.simple_spinner_item, entries); 1313 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 1314 spinner.setAdapter(adapter); 1315 } 1316 1317 int index; 1318 if (mAttendeeResponseFromIntent != EditEventHelper.ATTENDEE_NO_RESPONSE) { 1319 index = findResponseIndexFor(mAttendeeResponseFromIntent); 1320 } else { 1321 index = findResponseIndexFor(mOriginalAttendeeResponse); 1322 } 1323 spinner.setSelection(index + mResponseOffset); 1324 spinner.setOnItemSelectedListener(this); 1325 } 1326 1327 private void setTextCommon(View view, int id, CharSequence text) { 1328 TextView textView = (TextView) view.findViewById(id); 1329 if (textView == null) 1330 return; 1331 textView.setText(text); 1332 } 1333 1334 private void setVisibilityCommon(View view, int id, int visibility) { 1335 View v = view.findViewById(id); 1336 if (v != null) { 1337 v.setVisibility(visibility); 1338 } 1339 return; 1340 } 1341 1342 /** 1343 * Taken from com.google.android.gm.HtmlConversationActivity 1344 * 1345 * Send the intent that shows the Contact info corresponding to the email address. 1346 */ 1347 public void showContactInfo(Attendee attendee, Rect rect) { 1348 // First perform lookup query to find existing contact 1349 final ContentResolver resolver = getActivity().getContentResolver(); 1350 final String address = attendee.mEmail; 1351 final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI, 1352 Uri.encode(address)); 1353 final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri); 1354 1355 if (lookupUri != null) { 1356 // Found matching contact, trigger QuickContact 1357 QuickContact.showQuickContact(getActivity(), rect, lookupUri, 1358 QuickContact.MODE_MEDIUM, null); 1359 } else { 1360 // No matching contact, ask user to create one 1361 final Uri mailUri = Uri.fromParts("mailto", address, null); 1362 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri); 1363 1364 // Pass along full E-mail string for possible create dialog 1365 Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null); 1366 intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString()); 1367 1368 // Only provide personal name hint if we have one 1369 final String senderPersonal = attendee.mName; 1370 if (!TextUtils.isEmpty(senderPersonal)) { 1371 intent.putExtra(Intents.Insert.NAME, senderPersonal); 1372 } 1373 1374 startActivity(intent); 1375 } 1376 } 1377} 1378