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 17 package com.android.calendar; 18 19 import static android.provider.CalendarContract.EXTRA_EVENT_ALL_DAY; 20 import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; 21 import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME; 22 import static com.android.calendar.CalendarController.EVENT_EDIT_ON_LAUNCH; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorListenerAdapter; 26 import android.animation.ObjectAnimator; 27 import android.app.Activity; 28 import android.app.Dialog; 29 import android.app.DialogFragment; 30 import android.app.FragmentManager; 31 import android.app.Service; 32 import android.content.ActivityNotFoundException; 33 import android.content.ContentProviderOperation; 34 import android.content.ContentResolver; 35 import android.content.ContentUris; 36 import android.content.ContentValues; 37 import android.content.Context; 38 import android.content.DialogInterface; 39 import android.content.Intent; 40 import android.content.SharedPreferences; 41 import android.content.pm.ApplicationInfo; 42 import android.content.pm.PackageManager; 43 import android.content.pm.PackageManager.NameNotFoundException; 44 import android.content.res.Resources; 45 import android.database.Cursor; 46 import android.graphics.Color; 47 import android.graphics.Rect; 48 import android.graphics.drawable.Drawable; 49 import android.net.Uri; 50 import android.os.Bundle; 51 import android.provider.CalendarContract; 52 import android.provider.CalendarContract.Attendees; 53 import android.provider.CalendarContract.Calendars; 54 import android.provider.CalendarContract.Colors; 55 import android.provider.CalendarContract.Events; 56 import android.provider.CalendarContract.Reminders; 57 import android.provider.ContactsContract; 58 import android.provider.ContactsContract.CommonDataKinds; 59 import android.provider.ContactsContract.Intents; 60 import android.provider.ContactsContract.QuickContact; 61 import android.text.Spannable; 62 import android.text.SpannableStringBuilder; 63 import android.text.TextUtils; 64 import android.text.format.Time; 65 import android.text.method.LinkMovementMethod; 66 import android.text.method.MovementMethod; 67 import android.text.style.ForegroundColorSpan; 68 import android.text.util.Rfc822Token; 69 import android.util.Log; 70 import android.util.SparseIntArray; 71 import android.view.Gravity; 72 import android.view.LayoutInflater; 73 import android.view.Menu; 74 import android.view.MenuInflater; 75 import android.view.MenuItem; 76 import android.view.MotionEvent; 77 import android.view.View; 78 import android.view.View.OnClickListener; 79 import android.view.View.OnTouchListener; 80 import android.view.ViewGroup; 81 import android.view.Window; 82 import android.view.WindowManager; 83 import android.view.accessibility.AccessibilityEvent; 84 import android.view.accessibility.AccessibilityManager; 85 import android.widget.AdapterView; 86 import android.widget.AdapterView.OnItemSelectedListener; 87 import android.widget.Button; 88 import android.widget.LinearLayout; 89 import android.widget.RadioButton; 90 import android.widget.RadioGroup; 91 import android.widget.RadioGroup.OnCheckedChangeListener; 92 import android.widget.ScrollView; 93 import android.widget.TextView; 94 import android.widget.Toast; 95 96 import com.android.calendar.CalendarController.EventInfo; 97 import com.android.calendar.CalendarController.EventType; 98 import com.android.calendar.CalendarEventModel.Attendee; 99 import com.android.calendar.CalendarEventModel.ReminderEntry; 100 import com.android.calendar.alerts.QuickResponseActivity; 101 import com.android.calendar.event.AttendeesView; 102 import com.android.calendar.event.EditEventActivity; 103 import com.android.calendar.event.EditEventHelper; 104 import com.android.calendar.event.EventColorPickerDialog; 105 import com.android.calendar.event.EventViewUtils; 106 import com.android.calendarcommon2.DateException; 107 import com.android.calendarcommon2.Duration; 108 import com.android.calendarcommon2.EventRecurrence; 109 import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener; 110 import com.android.colorpicker.HsvColorComparator; 111 112 import java.util.ArrayList; 113 import java.util.Arrays; 114 import java.util.Collections; 115 import java.util.List; 116 117 public class EventInfoFragment extends DialogFragment implements OnCheckedChangeListener, 118 CalendarController.EventHandler, OnClickListener, DeleteEventHelper.DeleteNotifyListener, 119 OnColorSelectedListener { 120 121 public static final boolean DEBUG = false; 122 123 public static final String TAG = "EventInfoFragment"; 124 public static final String COLOR_PICKER_DIALOG_TAG = "EventColorPickerDialog"; 125 126 private static final int REQUEST_CODE_COLOR_PICKER = 0; 127 128 protected static final String BUNDLE_KEY_EVENT_ID = "key_event_id"; 129 protected static final String BUNDLE_KEY_START_MILLIS = "key_start_millis"; 130 protected static final String BUNDLE_KEY_END_MILLIS = "key_end_millis"; 131 protected static final String BUNDLE_KEY_IS_DIALOG = "key_fragment_is_dialog"; 132 protected static final String BUNDLE_KEY_DELETE_DIALOG_VISIBLE = "key_delete_dialog_visible"; 133 protected static final String BUNDLE_KEY_WINDOW_STYLE = "key_window_style"; 134 protected static final String BUNDLE_KEY_CALENDAR_COLOR = "key_calendar_color"; 135 protected static final String BUNDLE_KEY_CALENDAR_COLOR_INIT = "key_calendar_color_init"; 136 protected static final String BUNDLE_KEY_CURRENT_COLOR = "key_current_color"; 137 protected static final String BUNDLE_KEY_CURRENT_COLOR_KEY = "key_current_color_key"; 138 protected static final String BUNDLE_KEY_CURRENT_COLOR_INIT = "key_current_color_init"; 139 protected static final String BUNDLE_KEY_ORIGINAL_COLOR = "key_original_color"; 140 protected static final String BUNDLE_KEY_ORIGINAL_COLOR_INIT = "key_original_color_init"; 141 protected static final String BUNDLE_KEY_ATTENDEE_RESPONSE = "key_attendee_response"; 142 protected static final String BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE = 143 "key_user_set_attendee_response"; 144 protected static final String BUNDLE_KEY_TENTATIVE_USER_RESPONSE = 145 "key_tentative_user_response"; 146 protected static final String BUNDLE_KEY_RESPONSE_WHICH_EVENTS = "key_response_which_events"; 147 protected static final String BUNDLE_KEY_REMINDER_MINUTES = "key_reminder_minutes"; 148 protected static final String BUNDLE_KEY_REMINDER_METHODS = "key_reminder_methods"; 149 150 151 private static final String PERIOD_SPACE = ". "; 152 153 private static final String NO_EVENT_COLOR = ""; 154 155 /** 156 * These are the corresponding indices into the array of strings 157 * "R.array.change_response_labels" in the resource file. 158 */ 159 static final int UPDATE_SINGLE = 0; 160 static final int UPDATE_ALL = 1; 161 162 // Style of view 163 public static final int FULL_WINDOW_STYLE = 0; 164 public static final int DIALOG_WINDOW_STYLE = 1; 165 166 private int mWindowStyle = DIALOG_WINDOW_STYLE; 167 168 // Query tokens for QueryHandler 169 private static final int TOKEN_QUERY_EVENT = 1 << 0; 170 private static final int TOKEN_QUERY_CALENDARS = 1 << 1; 171 private static final int TOKEN_QUERY_ATTENDEES = 1 << 2; 172 private static final int TOKEN_QUERY_DUPLICATE_CALENDARS = 1 << 3; 173 private static final int TOKEN_QUERY_REMINDERS = 1 << 4; 174 private static final int TOKEN_QUERY_VISIBLE_CALENDARS = 1 << 5; 175 private static final int TOKEN_QUERY_COLORS = 1 << 6; 176 177 private static final int TOKEN_QUERY_ALL = TOKEN_QUERY_DUPLICATE_CALENDARS 178 | TOKEN_QUERY_ATTENDEES | TOKEN_QUERY_CALENDARS | TOKEN_QUERY_EVENT 179 | TOKEN_QUERY_REMINDERS | TOKEN_QUERY_VISIBLE_CALENDARS | TOKEN_QUERY_COLORS; 180 181 private int mCurrentQuery = 0; 182 183 private static final String[] EVENT_PROJECTION = new String[] { 184 Events._ID, // 0 do not remove; used in DeleteEventHelper 185 Events.TITLE, // 1 do not remove; used in DeleteEventHelper 186 Events.RRULE, // 2 do not remove; used in DeleteEventHelper 187 Events.ALL_DAY, // 3 do not remove; used in DeleteEventHelper 188 Events.CALENDAR_ID, // 4 do not remove; used in DeleteEventHelper 189 Events.DTSTART, // 5 do not remove; used in DeleteEventHelper 190 Events._SYNC_ID, // 6 do not remove; used in DeleteEventHelper 191 Events.EVENT_TIMEZONE, // 7 do not remove; used in DeleteEventHelper 192 Events.DESCRIPTION, // 8 193 Events.EVENT_LOCATION, // 9 194 Calendars.CALENDAR_ACCESS_LEVEL, // 10 195 Events.CALENDAR_COLOR, // 11 196 Events.EVENT_COLOR, // 12 197 Events.HAS_ATTENDEE_DATA, // 13 198 Events.ORGANIZER, // 14 199 Events.HAS_ALARM, // 15 200 Calendars.MAX_REMINDERS, // 16 201 Calendars.ALLOWED_REMINDERS, // 17 202 Events.CUSTOM_APP_PACKAGE, // 18 203 Events.CUSTOM_APP_URI, // 19 204 Events.DTEND, // 20 205 Events.DURATION, // 21 206 Events.ORIGINAL_SYNC_ID // 22 do not remove; used in DeleteEventHelper 207 }; 208 private static final int EVENT_INDEX_ID = 0; 209 private static final int EVENT_INDEX_TITLE = 1; 210 private static final int EVENT_INDEX_RRULE = 2; 211 private static final int EVENT_INDEX_ALL_DAY = 3; 212 private static final int EVENT_INDEX_CALENDAR_ID = 4; 213 private static final int EVENT_INDEX_DTSTART = 5; 214 private static final int EVENT_INDEX_SYNC_ID = 6; 215 private static final int EVENT_INDEX_EVENT_TIMEZONE = 7; 216 private static final int EVENT_INDEX_DESCRIPTION = 8; 217 private static final int EVENT_INDEX_EVENT_LOCATION = 9; 218 private static final int EVENT_INDEX_ACCESS_LEVEL = 10; 219 private static final int EVENT_INDEX_CALENDAR_COLOR = 11; 220 private static final int EVENT_INDEX_EVENT_COLOR = 12; 221 private static final int EVENT_INDEX_HAS_ATTENDEE_DATA = 13; 222 private static final int EVENT_INDEX_ORGANIZER = 14; 223 private static final int EVENT_INDEX_HAS_ALARM = 15; 224 private static final int EVENT_INDEX_MAX_REMINDERS = 16; 225 private static final int EVENT_INDEX_ALLOWED_REMINDERS = 17; 226 private static final int EVENT_INDEX_CUSTOM_APP_PACKAGE = 18; 227 private static final int EVENT_INDEX_CUSTOM_APP_URI = 19; 228 private static final int EVENT_INDEX_DTEND = 20; 229 private static final int EVENT_INDEX_DURATION = 21; 230 231 private static final String[] ATTENDEES_PROJECTION = new String[] { 232 Attendees._ID, // 0 233 Attendees.ATTENDEE_NAME, // 1 234 Attendees.ATTENDEE_EMAIL, // 2 235 Attendees.ATTENDEE_RELATIONSHIP, // 3 236 Attendees.ATTENDEE_STATUS, // 4 237 Attendees.ATTENDEE_IDENTITY, // 5 238 Attendees.ATTENDEE_ID_NAMESPACE // 6 239 }; 240 private static final int ATTENDEES_INDEX_ID = 0; 241 private static final int ATTENDEES_INDEX_NAME = 1; 242 private static final int ATTENDEES_INDEX_EMAIL = 2; 243 private static final int ATTENDEES_INDEX_RELATIONSHIP = 3; 244 private static final int ATTENDEES_INDEX_STATUS = 4; 245 private static final int ATTENDEES_INDEX_IDENTITY = 5; 246 private static final int ATTENDEES_INDEX_ID_NAMESPACE = 6; 247 248 static { 249 if (!Utils.isJellybeanOrLater()) { 250 EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_PACKAGE] = Events._ID; // dummy value 251 EVENT_PROJECTION[EVENT_INDEX_CUSTOM_APP_URI] = Events._ID; // dummy value 252 253 ATTENDEES_PROJECTION[ATTENDEES_INDEX_IDENTITY] = Attendees._ID; // dummy value 254 ATTENDEES_PROJECTION[ATTENDEES_INDEX_ID_NAMESPACE] = Attendees._ID; // dummy value 255 } 256 } 257 258 private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?"; 259 260 private static final String ATTENDEES_SORT_ORDER = Attendees.ATTENDEE_NAME + " ASC, " 261 + Attendees.ATTENDEE_EMAIL + " ASC"; 262 263 private static final String[] REMINDERS_PROJECTION = new String[] { 264 Reminders._ID, // 0 265 Reminders.MINUTES, // 1 266 Reminders.METHOD // 2 267 }; 268 private static final int REMINDERS_INDEX_ID = 0; 269 private static final int REMINDERS_MINUTES_ID = 1; 270 private static final int REMINDERS_METHOD_ID = 2; 271 272 private static final String REMINDERS_WHERE = Reminders.EVENT_ID + "=?"; 273 274 static final String[] CALENDARS_PROJECTION = new String[] { 275 Calendars._ID, // 0 276 Calendars.CALENDAR_DISPLAY_NAME, // 1 277 Calendars.OWNER_ACCOUNT, // 2 278 Calendars.CAN_ORGANIZER_RESPOND, // 3 279 Calendars.ACCOUNT_NAME, // 4 280 Calendars.ACCOUNT_TYPE // 5 281 }; 282 static final int CALENDARS_INDEX_DISPLAY_NAME = 1; 283 static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2; 284 static final int CALENDARS_INDEX_OWNER_CAN_RESPOND = 3; 285 static final int CALENDARS_INDEX_ACCOUNT_NAME = 4; 286 static final int CALENDARS_INDEX_ACCOUNT_TYPE = 5; 287 288 static final String CALENDARS_WHERE = Calendars._ID + "=?"; 289 static final String CALENDARS_DUPLICATE_NAME_WHERE = Calendars.CALENDAR_DISPLAY_NAME + "=?"; 290 static final String CALENDARS_VISIBLE_WHERE = Calendars.VISIBLE + "=?"; 291 292 static final String[] COLORS_PROJECTION = new String[] { 293 Colors._ID, // 0 294 Colors.COLOR, // 1 295 Colors.COLOR_KEY // 2 296 }; 297 298 static final String COLORS_WHERE = Colors.ACCOUNT_NAME + "=? AND " + Colors.ACCOUNT_TYPE + 299 "=? AND " + Colors.COLOR_TYPE + "=" + Colors.TYPE_EVENT; 300 301 public static final int COLORS_INDEX_COLOR = 1; 302 public static final int COLORS_INDEX_COLOR_KEY = 2; 303 304 private View mView; 305 306 private Uri mUri; 307 private long mEventId; 308 private Cursor mEventCursor; 309 private Cursor mAttendeesCursor; 310 private Cursor mCalendarsCursor; 311 private Cursor mRemindersCursor; 312 313 private static float mScale = 0; // Used for supporting different screen densities 314 315 private static int mCustomAppIconSize = 32; 316 317 private long mStartMillis; 318 private long mEndMillis; 319 private boolean mAllDay; 320 321 private boolean mHasAttendeeData; 322 private String mEventOrganizerEmail; 323 private String mEventOrganizerDisplayName = ""; 324 private boolean mIsOrganizer; 325 private long mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; 326 private boolean mOwnerCanRespond; 327 private String mSyncAccountName; 328 private String mCalendarOwnerAccount; 329 private boolean mCanModifyCalendar; 330 private boolean mCanModifyEvent; 331 private boolean mIsBusyFreeCalendar; 332 private int mNumOfAttendees; 333 private EditResponseHelper mEditResponseHelper; 334 private boolean mDeleteDialogVisible = false; 335 private DeleteEventHelper mDeleteHelper; 336 337 private int mOriginalAttendeeResponse; 338 private int mAttendeeResponseFromIntent = Attendees.ATTENDEE_STATUS_NONE; 339 private int mUserSetResponse = Attendees.ATTENDEE_STATUS_NONE; 340 private int mWhichEvents = -1; 341 // Used as the temporary response until the dialog is confirmed. It is also 342 // able to be used as a state marker for configuration changes. 343 private int mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE; 344 private boolean mIsRepeating; 345 private boolean mHasAlarm; 346 private int mMaxReminders; 347 private String mCalendarAllowedReminders; 348 // Used to prevent saving changes in event if it is being deleted. 349 private boolean mEventDeletionStarted = false; 350 351 private TextView mTitle; 352 private TextView mWhenDateTime; 353 private TextView mWhere; 354 private ExpandableTextView mDesc; 355 private AttendeesView mLongAttendees; 356 private Button emailAttendeesButton; 357 private Menu mMenu = null; 358 private View mHeadlines; 359 private ScrollView mScrollView; 360 private View mLoadingMsgView; 361 private View mErrorMsgView; 362 private ObjectAnimator mAnimateAlpha; 363 private long mLoadingMsgStartTime; 364 365 private EventColorPickerDialog mColorPickerDialog; 366 private SparseIntArray mDisplayColorKeyMap = new SparseIntArray(); 367 private int[] mColors; 368 private int mOriginalColor = -1; 369 private boolean mOriginalColorInitialized = false; 370 private int mCalendarColor = -1; 371 private boolean mCalendarColorInitialized = false; 372 private int mCurrentColor = -1; 373 private boolean mCurrentColorInitialized = false; 374 private int mCurrentColorKey = -1; 375 376 private static final int FADE_IN_TIME = 300; // in milliseconds 377 private static final int LOADING_MSG_DELAY = 600; // in milliseconds 378 private static final int LOADING_MSG_MIN_DISPLAY_TIME = 600; 379 private boolean mNoCrossFade = false; // Used to prevent repeated cross-fade 380 private RadioGroup mResponseRadioGroup; 381 382 ArrayList<Attendee> mAcceptedAttendees = new ArrayList<Attendee>(); 383 ArrayList<Attendee> mDeclinedAttendees = new ArrayList<Attendee>(); 384 ArrayList<Attendee> mTentativeAttendees = new ArrayList<Attendee>(); 385 ArrayList<Attendee> mNoResponseAttendees = new ArrayList<Attendee>(); 386 ArrayList<String> mToEmails = new ArrayList<String>(); 387 ArrayList<String> mCcEmails = new ArrayList<String>(); 388 389 private int mDefaultReminderMinutes; 390 private final ArrayList<LinearLayout> mReminderViews = new ArrayList<LinearLayout>(0); 391 public ArrayList<ReminderEntry> mReminders; 392 public ArrayList<ReminderEntry> mOriginalReminders = new ArrayList<ReminderEntry>(); 393 public ArrayList<ReminderEntry> mUnsupportedReminders = new ArrayList<ReminderEntry>(); 394 private boolean mUserModifiedReminders = false; 395 396 /** 397 * Contents of the "minutes" spinner. This has default values from the XML file, augmented 398 * with any additional values that were already associated with the event. 399 */ 400 private ArrayList<Integer> mReminderMinuteValues; 401 private ArrayList<String> mReminderMinuteLabels; 402 403 /** 404 * Contents of the "methods" spinner. The "values" list specifies the method constant 405 * (e.g. {@link Reminders#METHOD_ALERT}) associated with the labels. Any methods that 406 * aren't allowed by the Calendar will be removed. 407 */ 408 private ArrayList<Integer> mReminderMethodValues; 409 private ArrayList<String> mReminderMethodLabels; 410 411 private QueryHandler mHandler; 412 413 414 private final Runnable mTZUpdater = new Runnable() { 415 @Override 416 public void run() { 417 updateEvent(mView); 418 } 419 }; 420 421 private final Runnable mLoadingMsgAlphaUpdater = new Runnable() { 422 @Override 423 public void run() { 424 // Since this is run after a delay, make sure to only show the message 425 // if the event's data is not shown yet. 426 if (!mAnimateAlpha.isRunning() && mScrollView.getAlpha() == 0) { 427 mLoadingMsgStartTime = System.currentTimeMillis(); 428 mLoadingMsgView.setAlpha(1); 429 } 430 } 431 }; 432 433 private OnItemSelectedListener mReminderChangeListener; 434 435 private static int mDialogWidth = 500; 436 private static int mDialogHeight = 600; 437 private static int DIALOG_TOP_MARGIN = 8; 438 private boolean mIsDialog = false; 439 private boolean mIsPaused = true; 440 private boolean mDismissOnResume = false; 441 private int mX = -1; 442 private int mY = -1; 443 private int mMinTop; // Dialog cannot be above this location 444 private boolean mIsTabletConfig; 445 private Activity mActivity; 446 private Context mContext; 447 448 private CalendarController mController; 449 450 private class QueryHandler extends AsyncQueryService { QueryHandler(Context context)451 public QueryHandler(Context context) { 452 super(context); 453 } 454 455 @Override onQueryComplete(int token, Object cookie, Cursor cursor)456 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 457 // if the activity is finishing, then close the cursor and return 458 final Activity activity = getActivity(); 459 if (activity == null || activity.isFinishing()) { 460 if (cursor != null) { 461 cursor.close(); 462 } 463 return; 464 } 465 466 switch (token) { 467 case TOKEN_QUERY_EVENT: 468 mEventCursor = Utils.matrixCursorFromCursor(cursor); 469 if (!initEventCursor()) { 470 displayEventNotFound(); 471 return; 472 } 473 if (!mCalendarColorInitialized) { 474 mCalendarColor = Utils.getDisplayColorFromColor( 475 mEventCursor.getInt(EVENT_INDEX_CALENDAR_COLOR)); 476 mCalendarColorInitialized = true; 477 } 478 479 if (!mOriginalColorInitialized) { 480 mOriginalColor = mEventCursor.isNull(EVENT_INDEX_EVENT_COLOR) 481 ? mCalendarColor : Utils.getDisplayColorFromColor( 482 mEventCursor.getInt(EVENT_INDEX_EVENT_COLOR)); 483 mOriginalColorInitialized = true; 484 } 485 486 if (!mCurrentColorInitialized) { 487 mCurrentColor = mOriginalColor; 488 mCurrentColorInitialized = true; 489 } 490 491 updateEvent(mView); 492 prepareReminders(); 493 494 // start calendar query 495 Uri uri = Calendars.CONTENT_URI; 496 String[] args = new String[] { 497 Long.toString(mEventCursor.getLong(EVENT_INDEX_CALENDAR_ID))}; 498 startQuery(TOKEN_QUERY_CALENDARS, null, uri, CALENDARS_PROJECTION, 499 CALENDARS_WHERE, args, null); 500 break; 501 case TOKEN_QUERY_CALENDARS: 502 mCalendarsCursor = Utils.matrixCursorFromCursor(cursor); 503 updateCalendar(mView); 504 // FRAG_TODO fragments shouldn't set the title anymore 505 updateTitle(); 506 507 args = new String[] { 508 mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME), 509 mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_TYPE) }; 510 uri = Colors.CONTENT_URI; 511 startQuery(TOKEN_QUERY_COLORS, null, uri, COLORS_PROJECTION, COLORS_WHERE, args, 512 null); 513 514 if (!mIsBusyFreeCalendar) { 515 args = new String[] { Long.toString(mEventId) }; 516 517 // start attendees query 518 uri = Attendees.CONTENT_URI; 519 startQuery(TOKEN_QUERY_ATTENDEES, null, uri, ATTENDEES_PROJECTION, 520 ATTENDEES_WHERE, args, ATTENDEES_SORT_ORDER); 521 } else { 522 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_ATTENDEES); 523 } 524 if (mHasAlarm) { 525 // start reminders query 526 args = new String[] { Long.toString(mEventId) }; 527 uri = Reminders.CONTENT_URI; 528 startQuery(TOKEN_QUERY_REMINDERS, null, uri, 529 REMINDERS_PROJECTION, REMINDERS_WHERE, args, null); 530 } else { 531 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_REMINDERS); 532 } 533 break; 534 case TOKEN_QUERY_COLORS: 535 ArrayList<Integer> colors = new ArrayList<Integer>(); 536 if (cursor.moveToFirst()) { 537 do 538 { 539 int colorKey = cursor.getInt(COLORS_INDEX_COLOR_KEY); 540 int rawColor = cursor.getInt(COLORS_INDEX_COLOR); 541 int displayColor = Utils.getDisplayColorFromColor(rawColor); 542 mDisplayColorKeyMap.put(displayColor, colorKey); 543 colors.add(displayColor); 544 } while (cursor.moveToNext()); 545 } 546 cursor.close(); 547 Integer[] sortedColors = new Integer[colors.size()]; 548 Arrays.sort(colors.toArray(sortedColors), new HsvColorComparator()); 549 mColors = new int[sortedColors.length]; 550 for (int i = 0; i < sortedColors.length; i++) { 551 mColors[i] = sortedColors[i].intValue(); 552 553 float[] hsv = new float[3]; 554 Color.colorToHSV(mColors[i], hsv); 555 if (DEBUG) { 556 Log.d("Color", "H:" + hsv[0] + ",S:" + hsv[1] + ",V:" + hsv[2]); 557 } 558 } 559 if (mCanModifyCalendar) { 560 View button = mView.findViewById(R.id.change_color); 561 if (button != null && mColors.length > 0) { 562 button.setEnabled(true); 563 button.setVisibility(View.VISIBLE); 564 } 565 } 566 updateMenu(); 567 break; 568 case TOKEN_QUERY_ATTENDEES: 569 mAttendeesCursor = Utils.matrixCursorFromCursor(cursor); 570 initAttendeesCursor(mView); 571 updateResponse(mView); 572 break; 573 case TOKEN_QUERY_REMINDERS: 574 mRemindersCursor = Utils.matrixCursorFromCursor(cursor); 575 initReminders(mView, mRemindersCursor); 576 break; 577 case TOKEN_QUERY_VISIBLE_CALENDARS: 578 if (cursor.getCount() > 1) { 579 // Start duplicate calendars query to detect whether to add the calendar 580 // email to the calendar owner display. 581 String displayName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); 582 mHandler.startQuery(TOKEN_QUERY_DUPLICATE_CALENDARS, null, 583 Calendars.CONTENT_URI, CALENDARS_PROJECTION, 584 CALENDARS_DUPLICATE_NAME_WHERE, new String[] {displayName}, null); 585 } else { 586 // Don't need to display the calendar owner when there is only a single 587 // calendar. Skip the duplicate calendars query. 588 setVisibilityCommon(mView, R.id.calendar_container, View.GONE); 589 mCurrentQuery |= TOKEN_QUERY_DUPLICATE_CALENDARS; 590 } 591 break; 592 case TOKEN_QUERY_DUPLICATE_CALENDARS: 593 SpannableStringBuilder sb = new SpannableStringBuilder(); 594 595 // Calendar display name 596 String calendarName = mCalendarsCursor.getString(CALENDARS_INDEX_DISPLAY_NAME); 597 sb.append(calendarName); 598 599 // Show email account if display name is not unique and 600 // display name != email 601 String email = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); 602 if (cursor.getCount() > 1 && !calendarName.equalsIgnoreCase(email) && 603 Utils.isValidEmail(email)) { 604 sb.append(" (").append(email).append(")"); 605 } 606 607 setVisibilityCommon(mView, R.id.calendar_container, View.VISIBLE); 608 setTextCommon(mView, R.id.calendar_name, sb); 609 break; 610 } 611 cursor.close(); 612 sendAccessibilityEventIfQueryDone(token); 613 614 // All queries are done, show the view. 615 if (mCurrentQuery == TOKEN_QUERY_ALL) { 616 if (mLoadingMsgView.getAlpha() == 1) { 617 // Loading message is showing, let it stay a bit more (to prevent 618 // flashing) by adding a start delay to the event animation 619 long timeDiff = LOADING_MSG_MIN_DISPLAY_TIME - (System.currentTimeMillis() - 620 mLoadingMsgStartTime); 621 if (timeDiff > 0) { 622 mAnimateAlpha.setStartDelay(timeDiff); 623 } 624 } 625 if (!mAnimateAlpha.isRunning() &&!mAnimateAlpha.isStarted() && !mNoCrossFade) { 626 mAnimateAlpha.start(); 627 } else { 628 mScrollView.setAlpha(1); 629 mLoadingMsgView.setVisibility(View.GONE); 630 } 631 } 632 } 633 } 634 sendAccessibilityEventIfQueryDone(int token)635 private void sendAccessibilityEventIfQueryDone(int token) { 636 mCurrentQuery |= token; 637 if (mCurrentQuery == TOKEN_QUERY_ALL) { 638 sendAccessibilityEvent(); 639 } 640 } 641 EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis, int attendeeResponse, boolean isDialog, int windowStyle, ArrayList<ReminderEntry> reminders)642 public EventInfoFragment(Context context, Uri uri, long startMillis, long endMillis, 643 int attendeeResponse, boolean isDialog, int windowStyle, 644 ArrayList<ReminderEntry> reminders) { 645 646 Resources r = context.getResources(); 647 if (mScale == 0) { 648 mScale = context.getResources().getDisplayMetrics().density; 649 if (mScale != 1) { 650 mCustomAppIconSize *= mScale; 651 if (isDialog) { 652 DIALOG_TOP_MARGIN *= mScale; 653 } 654 } 655 } 656 if (isDialog) { 657 setDialogSize(r); 658 } 659 mIsDialog = isDialog; 660 661 setStyle(DialogFragment.STYLE_NO_TITLE, 0); 662 mUri = uri; 663 mStartMillis = startMillis; 664 mEndMillis = endMillis; 665 mAttendeeResponseFromIntent = attendeeResponse; 666 mWindowStyle = windowStyle; 667 668 // Pass in null if no reminders are being specified. 669 // This may be used to explicitly show certain reminders already known 670 // about, such as during configuration changes. 671 mReminders = reminders; 672 } 673 674 // This is currently required by the fragment manager. EventInfoFragment()675 public EventInfoFragment() { 676 } 677 EventInfoFragment(Context context, long eventId, long startMillis, long endMillis, int attendeeResponse, boolean isDialog, int windowStyle, ArrayList<ReminderEntry> reminders)678 public EventInfoFragment(Context context, long eventId, long startMillis, long endMillis, 679 int attendeeResponse, boolean isDialog, int windowStyle, 680 ArrayList<ReminderEntry> reminders) { 681 this(context, ContentUris.withAppendedId(Events.CONTENT_URI, eventId), startMillis, 682 endMillis, attendeeResponse, isDialog, windowStyle, reminders); 683 mEventId = eventId; 684 } 685 686 @Override onActivityCreated(Bundle savedInstanceState)687 public void onActivityCreated(Bundle savedInstanceState) { 688 super.onActivityCreated(savedInstanceState); 689 690 mReminderChangeListener = new OnItemSelectedListener() { 691 @Override 692 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 693 Integer prevValue = (Integer) parent.getTag(); 694 if (prevValue == null || prevValue != position) { 695 parent.setTag(position); 696 mUserModifiedReminders = true; 697 } 698 } 699 700 @Override 701 public void onNothingSelected(AdapterView<?> parent) { 702 // do nothing 703 } 704 705 }; 706 707 if (savedInstanceState != null) { 708 mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false); 709 mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE, 710 DIALOG_WINDOW_STYLE); 711 } 712 713 if (mIsDialog) { 714 applyDialogParams(); 715 } 716 717 final Activity activity = getActivity(); 718 mContext = activity; 719 mColorPickerDialog = (EventColorPickerDialog) activity.getFragmentManager() 720 .findFragmentByTag(COLOR_PICKER_DIALOG_TAG); 721 if (mColorPickerDialog != null) { 722 mColorPickerDialog.setOnColorSelectedListener(this); 723 } 724 } 725 applyDialogParams()726 private void applyDialogParams() { 727 Dialog dialog = getDialog(); 728 dialog.setCanceledOnTouchOutside(true); 729 730 Window window = dialog.getWindow(); 731 window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 732 733 WindowManager.LayoutParams a = window.getAttributes(); 734 a.dimAmount = .4f; 735 736 a.width = mDialogWidth; 737 a.height = mDialogHeight; 738 739 740 // On tablets , do smart positioning of dialog 741 // On phones , use the whole screen 742 743 if (mX != -1 || mY != -1) { 744 a.x = mX - mDialogWidth / 2; 745 a.y = mY - mDialogHeight / 2; 746 if (a.y < mMinTop) { 747 a.y = mMinTop + DIALOG_TOP_MARGIN; 748 } 749 a.gravity = Gravity.LEFT | Gravity.TOP; 750 } 751 window.setAttributes(a); 752 } 753 setDialogParams(int x, int y, int minTop)754 public void setDialogParams(int x, int y, int minTop) { 755 mX = x; 756 mY = y; 757 mMinTop = minTop; 758 } 759 760 // Implements OnCheckedChangeListener 761 @Override onCheckedChanged(RadioGroup group, int checkedId)762 public void onCheckedChanged(RadioGroup group, int checkedId) { 763 // If we haven't finished the return from the dialog yet, don't display. 764 if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 765 return; 766 } 767 768 // If this is not a repeating event, then don't display the dialog 769 // asking which events to change. 770 int response = getResponseFromButtonId(checkedId); 771 if (!mIsRepeating) { 772 mUserSetResponse = response; 773 return; 774 } 775 776 // If the selection is the same as the original, then don't display the 777 // dialog asking which events to change. 778 if (checkedId == findButtonIdForResponse(mOriginalAttendeeResponse)) { 779 mUserSetResponse = response; 780 return; 781 } 782 783 // This is a repeating event. We need to ask the user if they mean to 784 // change just this one instance or all instances. 785 mTentativeUserSetResponse = response; 786 mEditResponseHelper.showDialog(mWhichEvents); 787 } 788 onNothingSelected(AdapterView<?> parent)789 public void onNothingSelected(AdapterView<?> parent) { 790 } 791 792 @Override onDetach()793 public void onDetach() { 794 super.onDetach(); 795 mController.deregisterEventHandler(R.layout.event_info); 796 } 797 798 @Override onAttach(Activity activity)799 public void onAttach(Activity activity) { 800 super.onAttach(activity); 801 mActivity = activity; 802 // Ensure that mIsTabletConfig is set before creating the menu. 803 mIsTabletConfig = Utils.getConfigBool(mActivity, R.bool.tablet_config); 804 mController = CalendarController.getInstance(mActivity); 805 mController.registerEventHandler(R.layout.event_info, this); 806 mEditResponseHelper = new EditResponseHelper(activity); 807 mEditResponseHelper.setDismissListener( 808 new DialogInterface.OnDismissListener() { 809 @Override 810 public void onDismiss(DialogInterface dialog) { 811 // If the user dismisses the dialog (without hitting OK), 812 // then we want to revert the selection that opened the dialog. 813 if (mEditResponseHelper.getWhichEvents() != -1) { 814 mUserSetResponse = mTentativeUserSetResponse; 815 mWhichEvents = mEditResponseHelper.getWhichEvents(); 816 } else { 817 // Revert the attending response radio selection to whatever 818 // was selected prior to this selection (possibly nothing). 819 int oldResponse; 820 if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 821 oldResponse = mUserSetResponse; 822 } else { 823 oldResponse = mOriginalAttendeeResponse; 824 } 825 int buttonToCheck = findButtonIdForResponse(oldResponse); 826 827 if (mResponseRadioGroup != null) { 828 mResponseRadioGroup.check(buttonToCheck); 829 } 830 831 // If the radio group is being cleared, also clear the 832 // dialog's selection of which events should be included 833 // in this response. 834 if (buttonToCheck == -1) { 835 mEditResponseHelper.setWhichEvents(-1); 836 } 837 } 838 839 // Since OnPause will force the dialog to dismiss, do 840 // not change the dialog status 841 if (!mIsPaused) { 842 mTentativeUserSetResponse = Attendees.ATTENDEE_STATUS_NONE; 843 } 844 } 845 }); 846 847 if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { 848 mEditResponseHelper.setWhichEvents(UPDATE_ALL); 849 mWhichEvents = mEditResponseHelper.getWhichEvents(); 850 } 851 mHandler = new QueryHandler(activity); 852 if (!mIsDialog) { 853 setHasOptionsMenu(true); 854 } 855 } 856 857 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)858 public View onCreateView(LayoutInflater inflater, ViewGroup container, 859 Bundle savedInstanceState) { 860 861 if (savedInstanceState != null) { 862 mIsDialog = savedInstanceState.getBoolean(BUNDLE_KEY_IS_DIALOG, false); 863 mWindowStyle = savedInstanceState.getInt(BUNDLE_KEY_WINDOW_STYLE, 864 DIALOG_WINDOW_STYLE); 865 mDeleteDialogVisible = 866 savedInstanceState.getBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE,false); 867 mCalendarColor = savedInstanceState.getInt(BUNDLE_KEY_CALENDAR_COLOR); 868 mCalendarColorInitialized = 869 savedInstanceState.getBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT); 870 mOriginalColor = savedInstanceState.getInt(BUNDLE_KEY_ORIGINAL_COLOR); 871 mOriginalColorInitialized = savedInstanceState.getBoolean( 872 BUNDLE_KEY_ORIGINAL_COLOR_INIT); 873 mCurrentColor = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR); 874 mCurrentColorInitialized = savedInstanceState.getBoolean( 875 BUNDLE_KEY_CURRENT_COLOR_INIT); 876 mCurrentColorKey = savedInstanceState.getInt(BUNDLE_KEY_CURRENT_COLOR_KEY); 877 878 mTentativeUserSetResponse = savedInstanceState.getInt( 879 BUNDLE_KEY_TENTATIVE_USER_RESPONSE, 880 Attendees.ATTENDEE_STATUS_NONE); 881 if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE && 882 mEditResponseHelper != null) { 883 // If the edit response helper dialog is open, we'll need to 884 // know if either of the choices were selected. 885 mEditResponseHelper.setWhichEvents(savedInstanceState.getInt( 886 BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1)); 887 } 888 mUserSetResponse = savedInstanceState.getInt( 889 BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, 890 Attendees.ATTENDEE_STATUS_NONE); 891 if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 892 // If the response was set by the user before a configuration 893 // change, we'll need to know which choice was selected. 894 mWhichEvents = savedInstanceState.getInt( 895 BUNDLE_KEY_RESPONSE_WHICH_EVENTS, -1); 896 } 897 898 mReminders = Utils.readRemindersFromBundle(savedInstanceState); 899 } 900 901 if (mWindowStyle == DIALOG_WINDOW_STYLE) { 902 mView = inflater.inflate(R.layout.event_info_dialog, container, false); 903 } else { 904 mView = inflater.inflate(R.layout.event_info, container, false); 905 } 906 mScrollView = (ScrollView) mView.findViewById(R.id.event_info_scroll_view); 907 mLoadingMsgView = mView.findViewById(R.id.event_info_loading_msg); 908 mErrorMsgView = mView.findViewById(R.id.event_info_error_msg); 909 mTitle = (TextView) mView.findViewById(R.id.title); 910 mWhenDateTime = (TextView) mView.findViewById(R.id.when_datetime); 911 mWhere = (TextView) mView.findViewById(R.id.where); 912 mDesc = (ExpandableTextView) mView.findViewById(R.id.description); 913 mHeadlines = mView.findViewById(R.id.event_info_headline); 914 mLongAttendees = (AttendeesView) mView.findViewById(R.id.long_attendee_list); 915 916 mResponseRadioGroup = (RadioGroup) mView.findViewById(R.id.response_value); 917 918 if (mUri == null) { 919 // restore event ID from bundle 920 mEventId = savedInstanceState.getLong(BUNDLE_KEY_EVENT_ID); 921 mUri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 922 mStartMillis = savedInstanceState.getLong(BUNDLE_KEY_START_MILLIS); 923 mEndMillis = savedInstanceState.getLong(BUNDLE_KEY_END_MILLIS); 924 } 925 926 mAnimateAlpha = ObjectAnimator.ofFloat(mScrollView, "Alpha", 0, 1); 927 mAnimateAlpha.setDuration(FADE_IN_TIME); 928 mAnimateAlpha.addListener(new AnimatorListenerAdapter() { 929 int defLayerType; 930 931 @Override 932 public void onAnimationStart(Animator animation) { 933 // Use hardware layer for better performance during animation 934 defLayerType = mScrollView.getLayerType(); 935 mScrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 936 // Ensure that the loading message is gone before showing the 937 // event info 938 mLoadingMsgView.removeCallbacks(mLoadingMsgAlphaUpdater); 939 mLoadingMsgView.setVisibility(View.GONE); 940 } 941 942 @Override 943 public void onAnimationCancel(Animator animation) { 944 mScrollView.setLayerType(defLayerType, null); 945 } 946 947 @Override 948 public void onAnimationEnd(Animator animation) { 949 mScrollView.setLayerType(defLayerType, null); 950 // Do not cross fade after the first time 951 mNoCrossFade = true; 952 } 953 }); 954 955 mLoadingMsgView.setAlpha(0); 956 mScrollView.setAlpha(0); 957 mErrorMsgView.setVisibility(View.INVISIBLE); 958 mLoadingMsgView.postDelayed(mLoadingMsgAlphaUpdater, LOADING_MSG_DELAY); 959 960 // start loading the data 961 962 mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, 963 null, null, null); 964 965 View b = mView.findViewById(R.id.delete); 966 b.setOnClickListener(new OnClickListener() { 967 @Override 968 public void onClick(View v) { 969 if (!mCanModifyCalendar) { 970 return; 971 } 972 mDeleteHelper = 973 new DeleteEventHelper(mContext, mActivity, !mIsDialog && !mIsTabletConfig /* exitWhenDone */); 974 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this); 975 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); 976 mDeleteDialogVisible = true; 977 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); 978 } 979 }); 980 981 b = mView.findViewById(R.id.change_color); 982 b.setOnClickListener(new OnClickListener() { 983 @Override 984 public void onClick(View v) { 985 if (!mCanModifyCalendar) { 986 return; 987 } 988 showEventColorPickerDialog(); 989 } 990 }); 991 992 // Hide Edit/Delete buttons if in full screen mode on a phone 993 if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) { 994 mView.findViewById(R.id.event_info_buttons_container).setVisibility(View.GONE); 995 } 996 997 // Create a listener for the email guests button 998 emailAttendeesButton = (Button) mView.findViewById(R.id.email_attendees_button); 999 if (emailAttendeesButton != null) { 1000 emailAttendeesButton.setOnClickListener(new View.OnClickListener() { 1001 @Override 1002 public void onClick(View v) { 1003 emailAttendees(); 1004 } 1005 }); 1006 } 1007 1008 // Create a listener for the add reminder button 1009 View reminderAddButton = mView.findViewById(R.id.reminder_add); 1010 View.OnClickListener addReminderOnClickListener = new View.OnClickListener() { 1011 @Override 1012 public void onClick(View v) { 1013 addReminder(); 1014 mUserModifiedReminders = true; 1015 } 1016 }; 1017 reminderAddButton.setOnClickListener(addReminderOnClickListener); 1018 1019 // Set reminders variables 1020 1021 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(mActivity); 1022 String defaultReminderString = prefs.getString( 1023 GeneralPreferences.KEY_DEFAULT_REMINDER, GeneralPreferences.NO_REMINDER_STRING); 1024 mDefaultReminderMinutes = Integer.parseInt(defaultReminderString); 1025 prepareReminders(); 1026 1027 return mView; 1028 } 1029 1030 private final Runnable onDeleteRunnable = new Runnable() { 1031 @Override 1032 public void run() { 1033 if (EventInfoFragment.this.mIsPaused) { 1034 mDismissOnResume = true; 1035 return; 1036 } 1037 if (EventInfoFragment.this.isVisible()) { 1038 EventInfoFragment.this.dismiss(); 1039 } 1040 } 1041 }; 1042 updateTitle()1043 private void updateTitle() { 1044 Resources res = getActivity().getResources(); 1045 if (mCanModifyCalendar && !mIsOrganizer) { 1046 getActivity().setTitle(res.getString(R.string.event_info_title_invite)); 1047 } else { 1048 getActivity().setTitle(res.getString(R.string.event_info_title)); 1049 } 1050 } 1051 1052 /** 1053 * Initializes the event cursor, which is expected to point to the first 1054 * (and only) result from a query. 1055 * @return false if the cursor is empty, true otherwise 1056 */ initEventCursor()1057 private boolean initEventCursor() { 1058 if ((mEventCursor == null) || (mEventCursor.getCount() == 0)) { 1059 return false; 1060 } 1061 mEventCursor.moveToFirst(); 1062 mEventId = mEventCursor.getInt(EVENT_INDEX_ID); 1063 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); 1064 mIsRepeating = !TextUtils.isEmpty(rRule); 1065 // mHasAlarm will be true if it was saved in the event already, or if 1066 // we've explicitly been provided reminders (e.g. during rotation). 1067 mHasAlarm = (mEventCursor.getInt(EVENT_INDEX_HAS_ALARM) == 1)? true : 1068 (mReminders != null && mReminders.size() > 0); 1069 mMaxReminders = mEventCursor.getInt(EVENT_INDEX_MAX_REMINDERS); 1070 mCalendarAllowedReminders = mEventCursor.getString(EVENT_INDEX_ALLOWED_REMINDERS); 1071 return true; 1072 } 1073 1074 @SuppressWarnings("fallthrough") initAttendeesCursor(View view)1075 private void initAttendeesCursor(View view) { 1076 mOriginalAttendeeResponse = Attendees.ATTENDEE_STATUS_NONE; 1077 mCalendarOwnerAttendeeId = EditEventHelper.ATTENDEE_ID_NONE; 1078 mNumOfAttendees = 0; 1079 if (mAttendeesCursor != null) { 1080 mNumOfAttendees = mAttendeesCursor.getCount(); 1081 if (mAttendeesCursor.moveToFirst()) { 1082 mAcceptedAttendees.clear(); 1083 mDeclinedAttendees.clear(); 1084 mTentativeAttendees.clear(); 1085 mNoResponseAttendees.clear(); 1086 1087 do { 1088 int status = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 1089 String name = mAttendeesCursor.getString(ATTENDEES_INDEX_NAME); 1090 String email = mAttendeesCursor.getString(ATTENDEES_INDEX_EMAIL); 1091 1092 if (mAttendeesCursor.getInt(ATTENDEES_INDEX_RELATIONSHIP) == 1093 Attendees.RELATIONSHIP_ORGANIZER) { 1094 1095 // Overwrites the one from Event table if available 1096 if (!TextUtils.isEmpty(name)) { 1097 mEventOrganizerDisplayName = name; 1098 if (!mIsOrganizer) { 1099 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE); 1100 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName); 1101 } 1102 } 1103 } 1104 1105 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE && 1106 mCalendarOwnerAccount.equalsIgnoreCase(email)) { 1107 mCalendarOwnerAttendeeId = mAttendeesCursor.getInt(ATTENDEES_INDEX_ID); 1108 mOriginalAttendeeResponse = mAttendeesCursor.getInt(ATTENDEES_INDEX_STATUS); 1109 } else { 1110 String identity = null; 1111 String idNamespace = null; 1112 1113 if (Utils.isJellybeanOrLater()) { 1114 identity = mAttendeesCursor.getString(ATTENDEES_INDEX_IDENTITY); 1115 idNamespace = mAttendeesCursor.getString(ATTENDEES_INDEX_ID_NAMESPACE); 1116 } 1117 1118 // Don't show your own status in the list because: 1119 // 1) it doesn't make sense for event without other guests. 1120 // 2) there's a spinner for that for events with guests. 1121 switch(status) { 1122 case Attendees.ATTENDEE_STATUS_ACCEPTED: 1123 mAcceptedAttendees.add(new Attendee(name, email, 1124 Attendees.ATTENDEE_STATUS_ACCEPTED, identity, 1125 idNamespace)); 1126 break; 1127 case Attendees.ATTENDEE_STATUS_DECLINED: 1128 mDeclinedAttendees.add(new Attendee(name, email, 1129 Attendees.ATTENDEE_STATUS_DECLINED, identity, 1130 idNamespace)); 1131 break; 1132 case Attendees.ATTENDEE_STATUS_TENTATIVE: 1133 mTentativeAttendees.add(new Attendee(name, email, 1134 Attendees.ATTENDEE_STATUS_TENTATIVE, identity, 1135 idNamespace)); 1136 break; 1137 default: 1138 mNoResponseAttendees.add(new Attendee(name, email, 1139 Attendees.ATTENDEE_STATUS_NONE, identity, 1140 idNamespace)); 1141 } 1142 } 1143 } while (mAttendeesCursor.moveToNext()); 1144 mAttendeesCursor.moveToFirst(); 1145 1146 updateAttendees(view); 1147 } 1148 } 1149 } 1150 1151 @Override onSaveInstanceState(Bundle outState)1152 public void onSaveInstanceState(Bundle outState) { 1153 super.onSaveInstanceState(outState); 1154 outState.putLong(BUNDLE_KEY_EVENT_ID, mEventId); 1155 outState.putLong(BUNDLE_KEY_START_MILLIS, mStartMillis); 1156 outState.putLong(BUNDLE_KEY_END_MILLIS, mEndMillis); 1157 outState.putBoolean(BUNDLE_KEY_IS_DIALOG, mIsDialog); 1158 outState.putInt(BUNDLE_KEY_WINDOW_STYLE, mWindowStyle); 1159 outState.putBoolean(BUNDLE_KEY_DELETE_DIALOG_VISIBLE, mDeleteDialogVisible); 1160 outState.putInt(BUNDLE_KEY_CALENDAR_COLOR, mCalendarColor); 1161 outState.putBoolean(BUNDLE_KEY_CALENDAR_COLOR_INIT, mCalendarColorInitialized); 1162 outState.putInt(BUNDLE_KEY_ORIGINAL_COLOR, mOriginalColor); 1163 outState.putBoolean(BUNDLE_KEY_ORIGINAL_COLOR_INIT, mOriginalColorInitialized); 1164 outState.putInt(BUNDLE_KEY_CURRENT_COLOR, mCurrentColor); 1165 outState.putBoolean(BUNDLE_KEY_CURRENT_COLOR_INIT, mCurrentColorInitialized); 1166 outState.putInt(BUNDLE_KEY_CURRENT_COLOR_KEY, mCurrentColorKey); 1167 1168 // We'll need the temporary response for configuration changes. 1169 outState.putInt(BUNDLE_KEY_TENTATIVE_USER_RESPONSE, mTentativeUserSetResponse); 1170 if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE && 1171 mEditResponseHelper != null) { 1172 outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, 1173 mEditResponseHelper.getWhichEvents()); 1174 } 1175 1176 // Save the current response. 1177 int response; 1178 if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { 1179 response = mAttendeeResponseFromIntent; 1180 } else { 1181 response = mOriginalAttendeeResponse; 1182 } 1183 outState.putInt(BUNDLE_KEY_ATTENDEE_RESPONSE, response); 1184 if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 1185 response = mUserSetResponse; 1186 outState.putInt(BUNDLE_KEY_USER_SET_ATTENDEE_RESPONSE, response); 1187 outState.putInt(BUNDLE_KEY_RESPONSE_WHICH_EVENTS, mWhichEvents); 1188 } 1189 1190 // Save the reminders. 1191 mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews, 1192 mReminderMinuteValues, mReminderMethodValues); 1193 int numReminders = mReminders.size(); 1194 ArrayList<Integer> reminderMinutes = 1195 new ArrayList<Integer>(numReminders); 1196 ArrayList<Integer> reminderMethods = 1197 new ArrayList<Integer>(numReminders); 1198 for (ReminderEntry reminder : mReminders) { 1199 reminderMinutes.add(reminder.getMinutes()); 1200 reminderMethods.add(reminder.getMethod()); 1201 } 1202 outState.putIntegerArrayList( 1203 BUNDLE_KEY_REMINDER_MINUTES, reminderMinutes); 1204 outState.putIntegerArrayList( 1205 BUNDLE_KEY_REMINDER_METHODS, reminderMethods); 1206 } 1207 1208 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)1209 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 1210 super.onCreateOptionsMenu(menu, inflater); 1211 // Show color/edit/delete buttons only in non-dialog configuration 1212 if (!mIsDialog && !mIsTabletConfig || mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) { 1213 inflater.inflate(R.menu.event_info_title_bar, menu); 1214 mMenu = menu; 1215 updateMenu(); 1216 } 1217 } 1218 1219 @Override onOptionsItemSelected(MenuItem item)1220 public boolean onOptionsItemSelected(MenuItem item) { 1221 1222 // If we're a dialog we don't want to handle menu buttons 1223 if (mIsDialog) { 1224 return false; 1225 } 1226 // Handles option menu selections: 1227 // Home button - close event info activity and start the main calendar 1228 // one 1229 // Edit button - start the event edit activity and close the info 1230 // activity 1231 // Delete button - start a delete query that calls a runnable that close 1232 // the info activity 1233 1234 final int itemId = item.getItemId(); 1235 if (itemId == android.R.id.home) { 1236 Utils.returnToCalendarHome(mContext); 1237 mActivity.finish(); 1238 return true; 1239 } else if (itemId == R.id.info_action_edit) { 1240 doEdit(); 1241 mActivity.finish(); 1242 } else if (itemId == R.id.info_action_delete) { 1243 mDeleteHelper = 1244 new DeleteEventHelper(mActivity, mActivity, true /* exitWhenDone */); 1245 mDeleteHelper.setDeleteNotificationListener(EventInfoFragment.this); 1246 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); 1247 mDeleteDialogVisible = true; 1248 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); 1249 } else if (itemId == R.id.info_action_change_color) { 1250 showEventColorPickerDialog(); 1251 } 1252 return super.onOptionsItemSelected(item); 1253 } 1254 showEventColorPickerDialog()1255 private void showEventColorPickerDialog() { 1256 if (mColorPickerDialog == null) { 1257 mColorPickerDialog = EventColorPickerDialog.newInstance(mColors, mCurrentColor, 1258 mCalendarColor, mIsTabletConfig); 1259 mColorPickerDialog.setOnColorSelectedListener(this); 1260 } 1261 final FragmentManager fragmentManager = getFragmentManager(); 1262 fragmentManager.executePendingTransactions(); 1263 if (!mColorPickerDialog.isAdded()) { 1264 mColorPickerDialog.show(fragmentManager, COLOR_PICKER_DIALOG_TAG); 1265 } 1266 } 1267 saveEventColor()1268 private boolean saveEventColor() { 1269 if (mCurrentColor == mOriginalColor) { 1270 return false; 1271 } 1272 1273 ContentValues values = new ContentValues(); 1274 if (mCurrentColor != mCalendarColor) { 1275 values.put(Events.EVENT_COLOR_KEY, mCurrentColorKey); 1276 } else { 1277 values.put(Events.EVENT_COLOR_KEY, NO_EVENT_COLOR); 1278 } 1279 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 1280 mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, 1281 null, null, Utils.UNDO_DELAY); 1282 return true; 1283 } 1284 1285 @Override onStop()1286 public void onStop() { 1287 Activity act = getActivity(); 1288 if (!mEventDeletionStarted && act != null && !act.isChangingConfigurations()) { 1289 1290 boolean responseSaved = saveResponse(); 1291 boolean eventColorSaved = saveEventColor(); 1292 if (saveReminders() || responseSaved || eventColorSaved) { 1293 Toast.makeText(getActivity(), R.string.saving_event, Toast.LENGTH_SHORT).show(); 1294 } 1295 } 1296 super.onStop(); 1297 } 1298 1299 @Override onDestroy()1300 public void onDestroy() { 1301 if (mEventCursor != null) { 1302 mEventCursor.close(); 1303 } 1304 if (mCalendarsCursor != null) { 1305 mCalendarsCursor.close(); 1306 } 1307 if (mAttendeesCursor != null) { 1308 mAttendeesCursor.close(); 1309 } 1310 super.onDestroy(); 1311 } 1312 1313 /** 1314 * Asynchronously saves the response to an invitation if the user changed 1315 * the response. Returns true if the database will be updated. 1316 * 1317 * @return true if the database will be changed 1318 */ saveResponse()1319 private boolean saveResponse() { 1320 if (mAttendeesCursor == null || mEventCursor == null) { 1321 return false; 1322 } 1323 1324 int status = getResponseFromButtonId( 1325 mResponseRadioGroup.getCheckedRadioButtonId()); 1326 if (status == Attendees.ATTENDEE_STATUS_NONE) { 1327 return false; 1328 } 1329 1330 // If the status has not changed, then don't update the database 1331 if (status == mOriginalAttendeeResponse) { 1332 return false; 1333 } 1334 1335 // If we never got an owner attendee id we can't set the status 1336 if (mCalendarOwnerAttendeeId == EditEventHelper.ATTENDEE_ID_NONE) { 1337 return false; 1338 } 1339 1340 if (!mIsRepeating) { 1341 // This is a non-repeating event 1342 updateResponse(mEventId, mCalendarOwnerAttendeeId, status); 1343 mOriginalAttendeeResponse = status; 1344 return true; 1345 } 1346 1347 if (DEBUG) { 1348 Log.d(TAG, "Repeating event: mWhichEvents=" + mWhichEvents); 1349 } 1350 // This is a repeating event 1351 switch (mWhichEvents) { 1352 case -1: 1353 return false; 1354 case UPDATE_SINGLE: 1355 createExceptionResponse(mEventId, status); 1356 mOriginalAttendeeResponse = status; 1357 return true; 1358 case UPDATE_ALL: 1359 updateResponse(mEventId, mCalendarOwnerAttendeeId, status); 1360 mOriginalAttendeeResponse = status; 1361 return true; 1362 default: 1363 Log.e(TAG, "Unexpected choice for updating invitation response"); 1364 break; 1365 } 1366 return false; 1367 } 1368 updateResponse(long eventId, long attendeeId, int status)1369 private void updateResponse(long eventId, long attendeeId, int status) { 1370 // Update the attendee status in the attendees table. the provider 1371 // takes care of updating the self attendance status. 1372 ContentValues values = new ContentValues(); 1373 1374 if (!TextUtils.isEmpty(mCalendarOwnerAccount)) { 1375 values.put(Attendees.ATTENDEE_EMAIL, mCalendarOwnerAccount); 1376 } 1377 values.put(Attendees.ATTENDEE_STATUS, status); 1378 values.put(Attendees.EVENT_ID, eventId); 1379 1380 Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, attendeeId); 1381 1382 mHandler.startUpdate(mHandler.getNextToken(), null, uri, values, 1383 null, null, Utils.UNDO_DELAY); 1384 } 1385 1386 /** 1387 * Creates an exception to a recurring event. The only change we're making is to the 1388 * "self attendee status" value. The provider will take care of updating the corresponding 1389 * Attendees.attendeeStatus entry. 1390 * 1391 * @param eventId The recurring event. 1392 * @param status The new value for selfAttendeeStatus. 1393 */ createExceptionResponse(long eventId, int status)1394 private void createExceptionResponse(long eventId, int status) { 1395 ContentValues values = new ContentValues(); 1396 values.put(Events.ORIGINAL_INSTANCE_TIME, mStartMillis); 1397 values.put(Events.SELF_ATTENDEE_STATUS, status); 1398 values.put(Events.STATUS, Events.STATUS_CONFIRMED); 1399 1400 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1401 Uri exceptionUri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI, 1402 String.valueOf(eventId)); 1403 ops.add(ContentProviderOperation.newInsert(exceptionUri).withValues(values).build()); 1404 1405 mHandler.startBatch(mHandler.getNextToken(), null, CalendarContract.AUTHORITY, ops, 1406 Utils.UNDO_DELAY); 1407 } 1408 getResponseFromButtonId(int buttonId)1409 public static int getResponseFromButtonId(int buttonId) { 1410 int response; 1411 if (buttonId == R.id.response_yes) { 1412 response = Attendees.ATTENDEE_STATUS_ACCEPTED; 1413 } else if (buttonId == R.id.response_maybe) { 1414 response = Attendees.ATTENDEE_STATUS_TENTATIVE; 1415 } else if (buttonId == R.id.response_no) { 1416 response = Attendees.ATTENDEE_STATUS_DECLINED; 1417 } else { 1418 response = Attendees.ATTENDEE_STATUS_NONE; 1419 } 1420 return response; 1421 } 1422 findButtonIdForResponse(int response)1423 public static int findButtonIdForResponse(int response) { 1424 int buttonId; 1425 switch (response) { 1426 case Attendees.ATTENDEE_STATUS_ACCEPTED: 1427 buttonId = R.id.response_yes; 1428 break; 1429 case Attendees.ATTENDEE_STATUS_TENTATIVE: 1430 buttonId = R.id.response_maybe; 1431 break; 1432 case Attendees.ATTENDEE_STATUS_DECLINED: 1433 buttonId = R.id.response_no; 1434 break; 1435 default: 1436 buttonId = -1; 1437 } 1438 return buttonId; 1439 } 1440 doEdit()1441 private void doEdit() { 1442 Context c = getActivity(); 1443 // This ensures that we aren't in the process of closing and have been 1444 // unattached already 1445 if (c != null) { 1446 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 1447 Intent intent = new Intent(Intent.ACTION_EDIT, uri); 1448 intent.setClass(mActivity, EditEventActivity.class); 1449 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis); 1450 intent.putExtra(EXTRA_EVENT_END_TIME, mEndMillis); 1451 intent.putExtra(EXTRA_EVENT_ALL_DAY, mAllDay); 1452 intent.putExtra(EditEventActivity.EXTRA_EVENT_COLOR, mCurrentColor); 1453 intent.putExtra(EditEventActivity.EXTRA_EVENT_REMINDERS, EventViewUtils 1454 .reminderItemsToReminders(mReminderViews, mReminderMinuteValues, 1455 mReminderMethodValues)); 1456 intent.putExtra(EVENT_EDIT_ON_LAUNCH, true); 1457 startActivity(intent); 1458 } 1459 } 1460 displayEventNotFound()1461 private void displayEventNotFound() { 1462 mErrorMsgView.setVisibility(View.VISIBLE); 1463 mScrollView.setVisibility(View.GONE); 1464 mLoadingMsgView.setVisibility(View.GONE); 1465 } 1466 updateEvent(View view)1467 private void updateEvent(View view) { 1468 if (mEventCursor == null || view == null) { 1469 return; 1470 } 1471 1472 Context context = view.getContext(); 1473 if (context == null) { 1474 return; 1475 } 1476 1477 String eventName = mEventCursor.getString(EVENT_INDEX_TITLE); 1478 if (eventName == null || eventName.length() == 0) { 1479 eventName = getActivity().getString(R.string.no_title_label); 1480 } 1481 1482 // 3rd parties might not have specified the start/end time when firing the 1483 // Events.CONTENT_URI intent. Update these with values read from the db. 1484 if (mStartMillis == 0 && mEndMillis == 0) { 1485 mStartMillis = mEventCursor.getLong(EVENT_INDEX_DTSTART); 1486 mEndMillis = mEventCursor.getLong(EVENT_INDEX_DTEND); 1487 if (mEndMillis == 0) { 1488 String duration = mEventCursor.getString(EVENT_INDEX_DURATION); 1489 if (!TextUtils.isEmpty(duration)) { 1490 try { 1491 Duration d = new Duration(); 1492 d.parse(duration); 1493 long endMillis = mStartMillis + d.getMillis(); 1494 if (endMillis >= mStartMillis) { 1495 mEndMillis = endMillis; 1496 } else { 1497 Log.d(TAG, "Invalid duration string: " + duration); 1498 } 1499 } catch (DateException e) { 1500 Log.d(TAG, "Error parsing duration string " + duration, e); 1501 } 1502 } 1503 if (mEndMillis == 0) { 1504 mEndMillis = mStartMillis; 1505 } 1506 } 1507 } 1508 1509 mAllDay = mEventCursor.getInt(EVENT_INDEX_ALL_DAY) != 0; 1510 String location = mEventCursor.getString(EVENT_INDEX_EVENT_LOCATION); 1511 String description = mEventCursor.getString(EVENT_INDEX_DESCRIPTION); 1512 String rRule = mEventCursor.getString(EVENT_INDEX_RRULE); 1513 String eventTimezone = mEventCursor.getString(EVENT_INDEX_EVENT_TIMEZONE); 1514 1515 mHeadlines.setBackgroundColor(mCurrentColor); 1516 1517 // What 1518 if (eventName != null) { 1519 setTextCommon(view, R.id.title, eventName); 1520 } 1521 1522 // When 1523 // Set the date and repeats (if any) 1524 String localTimezone = Utils.getTimeZone(mActivity, mTZUpdater); 1525 1526 Resources resources = context.getResources(); 1527 String displayedDatetime = Utils.getDisplayedDatetime(mStartMillis, mEndMillis, 1528 System.currentTimeMillis(), localTimezone, mAllDay, context); 1529 1530 String displayedTimezone = null; 1531 if (!mAllDay) { 1532 displayedTimezone = Utils.getDisplayedTimezone(mStartMillis, localTimezone, 1533 eventTimezone); 1534 } 1535 // Display the datetime. Make the timezone (if any) transparent. 1536 if (displayedTimezone == null) { 1537 setTextCommon(view, R.id.when_datetime, displayedDatetime); 1538 } else { 1539 int timezoneIndex = displayedDatetime.length(); 1540 displayedDatetime += " " + displayedTimezone; 1541 SpannableStringBuilder sb = new SpannableStringBuilder(displayedDatetime); 1542 ForegroundColorSpan transparentColorSpan = new ForegroundColorSpan( 1543 resources.getColor(R.color.event_info_headline_transparent_color)); 1544 sb.setSpan(transparentColorSpan, timezoneIndex, displayedDatetime.length(), 1545 Spannable.SPAN_INCLUSIVE_INCLUSIVE); 1546 setTextCommon(view, R.id.when_datetime, sb); 1547 } 1548 1549 // Display the repeat string (if any) 1550 String repeatString = null; 1551 if (!TextUtils.isEmpty(rRule)) { 1552 EventRecurrence eventRecurrence = new EventRecurrence(); 1553 eventRecurrence.parse(rRule); 1554 Time date = new Time(localTimezone); 1555 date.set(mStartMillis); 1556 if (mAllDay) { 1557 date.timezone = Time.TIMEZONE_UTC; 1558 } 1559 eventRecurrence.setStartDate(date); 1560 repeatString = EventRecurrenceFormatter.getRepeatString(mContext, resources, 1561 eventRecurrence, true); 1562 } 1563 if (repeatString == null) { 1564 view.findViewById(R.id.when_repeat).setVisibility(View.GONE); 1565 } else { 1566 setTextCommon(view, R.id.when_repeat, repeatString); 1567 } 1568 1569 // Organizer view is setup in the updateCalendar method 1570 1571 1572 // Where 1573 if (location == null || location.trim().length() == 0) { 1574 setVisibilityCommon(view, R.id.where, View.GONE); 1575 } else { 1576 final TextView textView = mWhere; 1577 if (textView != null) { 1578 textView.setAutoLinkMask(0); 1579 textView.setText(location.trim()); 1580 try { 1581 textView.setText(Utils.extendedLinkify(textView.getText().toString(), true)); 1582 1583 // Linkify.addLinks() sets the TextView movement method if it finds any links. 1584 // We must do the same here, in case linkify by itself did not find any. 1585 // (This is cloned from Linkify.addLinkMovementMethod().) 1586 MovementMethod mm = textView.getMovementMethod(); 1587 if ((mm == null) || !(mm instanceof LinkMovementMethod)) { 1588 if (textView.getLinksClickable()) { 1589 textView.setMovementMethod(LinkMovementMethod.getInstance()); 1590 } 1591 } 1592 } catch (Exception ex) { 1593 // unexpected 1594 Log.e(TAG, "Linkification failed", ex); 1595 } 1596 1597 textView.setOnTouchListener(new OnTouchListener() { 1598 @Override 1599 public boolean onTouch(View v, MotionEvent event) { 1600 try { 1601 return v.onTouchEvent(event); 1602 } catch (ActivityNotFoundException e) { 1603 // ignore 1604 return true; 1605 } 1606 } 1607 }); 1608 } 1609 } 1610 1611 // Description 1612 if (description != null && description.length() != 0) { 1613 mDesc.setText(description); 1614 } 1615 1616 // Launch Custom App 1617 if (Utils.isJellybeanOrLater()) { 1618 updateCustomAppButton(); 1619 } 1620 } 1621 updateCustomAppButton()1622 private void updateCustomAppButton() { 1623 buttonSetup: { 1624 final Button launchButton = (Button) mView.findViewById(R.id.launch_custom_app_button); 1625 if (launchButton == null) 1626 break buttonSetup; 1627 1628 final String customAppPackage = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_PACKAGE); 1629 final String customAppUri = mEventCursor.getString(EVENT_INDEX_CUSTOM_APP_URI); 1630 1631 if (TextUtils.isEmpty(customAppPackage) || TextUtils.isEmpty(customAppUri)) 1632 break buttonSetup; 1633 1634 PackageManager pm = mContext.getPackageManager(); 1635 if (pm == null) 1636 break buttonSetup; 1637 1638 ApplicationInfo info; 1639 try { 1640 info = pm.getApplicationInfo(customAppPackage, 0); 1641 if (info == null) 1642 break buttonSetup; 1643 } catch (NameNotFoundException e) { 1644 break buttonSetup; 1645 } 1646 1647 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 1648 final Intent intent = new Intent(CalendarContract.ACTION_HANDLE_CUSTOM_EVENT, uri); 1649 intent.setPackage(customAppPackage); 1650 intent.putExtra(CalendarContract.EXTRA_CUSTOM_APP_URI, customAppUri); 1651 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, mStartMillis); 1652 1653 // See if we have a taker for our intent 1654 if (pm.resolveActivity(intent, 0) == null) 1655 break buttonSetup; 1656 1657 Drawable icon = pm.getApplicationIcon(info); 1658 if (icon != null) { 1659 1660 Drawable[] d = launchButton.getCompoundDrawables(); 1661 icon.setBounds(0, 0, mCustomAppIconSize, mCustomAppIconSize); 1662 launchButton.setCompoundDrawables(icon, d[1], d[2], d[3]); 1663 } 1664 1665 CharSequence label = pm.getApplicationLabel(info); 1666 if (label != null && label.length() != 0) { 1667 launchButton.setText(label); 1668 } else if (icon == null) { 1669 // No icon && no label. Hide button? 1670 break buttonSetup; 1671 } 1672 1673 // Launch custom app 1674 launchButton.setOnClickListener(new View.OnClickListener() { 1675 @Override 1676 public void onClick(View v) { 1677 try { 1678 startActivityForResult(intent, 0); 1679 } catch (ActivityNotFoundException e) { 1680 // Shouldn't happen as we checked it already 1681 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE); 1682 } 1683 } 1684 }); 1685 1686 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.VISIBLE); 1687 return; 1688 1689 } 1690 1691 setVisibilityCommon(mView, R.id.launch_custom_app_container, View.GONE); 1692 return; 1693 } 1694 sendAccessibilityEvent()1695 private void sendAccessibilityEvent() { 1696 AccessibilityManager am = 1697 (AccessibilityManager) getActivity().getSystemService(Service.ACCESSIBILITY_SERVICE); 1698 if (!am.isEnabled()) { 1699 return; 1700 } 1701 1702 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED); 1703 event.setClassName(EventInfoFragment.class.getName()); 1704 event.setPackageName(getActivity().getPackageName()); 1705 List<CharSequence> text = event.getText(); 1706 1707 addFieldToAccessibilityEvent(text, mTitle, null); 1708 addFieldToAccessibilityEvent(text, mWhenDateTime, null); 1709 addFieldToAccessibilityEvent(text, mWhere, null); 1710 addFieldToAccessibilityEvent(text, null, mDesc); 1711 1712 if (mResponseRadioGroup.getVisibility() == View.VISIBLE) { 1713 int id = mResponseRadioGroup.getCheckedRadioButtonId(); 1714 if (id != View.NO_ID) { 1715 text.add(((TextView) getView().findViewById(R.id.response_label)).getText()); 1716 text.add((((RadioButton) (mResponseRadioGroup.findViewById(id))) 1717 .getText() + PERIOD_SPACE)); 1718 } 1719 } 1720 1721 am.sendAccessibilityEvent(event); 1722 } 1723 addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv, ExpandableTextView etv)1724 private void addFieldToAccessibilityEvent(List<CharSequence> text, TextView tv, 1725 ExpandableTextView etv) { 1726 CharSequence cs; 1727 if (tv != null) { 1728 cs = tv.getText(); 1729 } else if (etv != null) { 1730 cs = etv.getText(); 1731 } else { 1732 return; 1733 } 1734 1735 if (!TextUtils.isEmpty(cs)) { 1736 cs = cs.toString().trim(); 1737 if (cs.length() > 0) { 1738 text.add(cs); 1739 text.add(PERIOD_SPACE); 1740 } 1741 } 1742 } 1743 updateCalendar(View view)1744 private void updateCalendar(View view) { 1745 1746 mCalendarOwnerAccount = ""; 1747 if (mCalendarsCursor != null && mEventCursor != null) { 1748 mCalendarsCursor.moveToFirst(); 1749 String tempAccount = mCalendarsCursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT); 1750 mCalendarOwnerAccount = (tempAccount == null) ? "" : tempAccount; 1751 mOwnerCanRespond = mCalendarsCursor.getInt(CALENDARS_INDEX_OWNER_CAN_RESPOND) != 0; 1752 mSyncAccountName = mCalendarsCursor.getString(CALENDARS_INDEX_ACCOUNT_NAME); 1753 1754 // start visible calendars query 1755 mHandler.startQuery(TOKEN_QUERY_VISIBLE_CALENDARS, null, Calendars.CONTENT_URI, 1756 CALENDARS_PROJECTION, CALENDARS_VISIBLE_WHERE, new String[] {"1"}, null); 1757 1758 mEventOrganizerEmail = mEventCursor.getString(EVENT_INDEX_ORGANIZER); 1759 mIsOrganizer = mCalendarOwnerAccount.equalsIgnoreCase(mEventOrganizerEmail); 1760 1761 if (!TextUtils.isEmpty(mEventOrganizerEmail) && 1762 !mEventOrganizerEmail.endsWith(Utils.MACHINE_GENERATED_ADDRESS)) { 1763 mEventOrganizerDisplayName = mEventOrganizerEmail; 1764 } 1765 1766 if (!mIsOrganizer && !TextUtils.isEmpty(mEventOrganizerDisplayName)) { 1767 setTextCommon(view, R.id.organizer, mEventOrganizerDisplayName); 1768 setVisibilityCommon(view, R.id.organizer_container, View.VISIBLE); 1769 } else { 1770 setVisibilityCommon(view, R.id.organizer_container, View.GONE); 1771 } 1772 mHasAttendeeData = mEventCursor.getInt(EVENT_INDEX_HAS_ATTENDEE_DATA) != 0; 1773 mCanModifyCalendar = mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) 1774 >= Calendars.CAL_ACCESS_CONTRIBUTOR; 1775 // TODO add "|| guestCanModify" after b/1299071 is fixed 1776 mCanModifyEvent = mCanModifyCalendar && mIsOrganizer; 1777 mIsBusyFreeCalendar = 1778 mEventCursor.getInt(EVENT_INDEX_ACCESS_LEVEL) == Calendars.CAL_ACCESS_FREEBUSY; 1779 1780 if (!mIsBusyFreeCalendar) { 1781 1782 View b = mView.findViewById(R.id.edit); 1783 b.setEnabled(true); 1784 b.setOnClickListener(new OnClickListener() { 1785 @Override 1786 public void onClick(View v) { 1787 doEdit(); 1788 // For dialogs, just close the fragment 1789 // For full screen, close activity on phone, leave it for tablet 1790 if (mIsDialog) { 1791 EventInfoFragment.this.dismiss(); 1792 } 1793 else if (!mIsTabletConfig){ 1794 getActivity().finish(); 1795 } 1796 } 1797 }); 1798 } 1799 View button; 1800 if (mCanModifyCalendar) { 1801 button = mView.findViewById(R.id.delete); 1802 if (button != null) { 1803 button.setEnabled(true); 1804 button.setVisibility(View.VISIBLE); 1805 } 1806 } 1807 if (mCanModifyEvent) { 1808 button = mView.findViewById(R.id.edit); 1809 if (button != null) { 1810 button.setEnabled(true); 1811 button.setVisibility(View.VISIBLE); 1812 } 1813 } 1814 if ((!mIsDialog && !mIsTabletConfig || 1815 mWindowStyle == EventInfoFragment.FULL_WINDOW_STYLE) && mMenu != null) { 1816 mActivity.invalidateOptionsMenu(); 1817 } 1818 } else { 1819 setVisibilityCommon(view, R.id.calendar, View.GONE); 1820 sendAccessibilityEventIfQueryDone(TOKEN_QUERY_DUPLICATE_CALENDARS); 1821 } 1822 } 1823 1824 /** 1825 * 1826 */ updateMenu()1827 private void updateMenu() { 1828 if (mMenu == null) { 1829 return; 1830 } 1831 MenuItem delete = mMenu.findItem(R.id.info_action_delete); 1832 MenuItem edit = mMenu.findItem(R.id.info_action_edit); 1833 MenuItem changeColor = mMenu.findItem(R.id.info_action_change_color); 1834 if (delete != null) { 1835 delete.setVisible(mCanModifyCalendar); 1836 delete.setEnabled(mCanModifyCalendar); 1837 } 1838 if (edit != null) { 1839 edit.setVisible(mCanModifyEvent); 1840 edit.setEnabled(mCanModifyEvent); 1841 } 1842 if (changeColor != null && mColors != null && mColors.length > 0) { 1843 changeColor.setVisible(mCanModifyCalendar); 1844 changeColor.setEnabled(mCanModifyCalendar); 1845 } 1846 } 1847 updateAttendees(View view)1848 private void updateAttendees(View view) { 1849 if (mAcceptedAttendees.size() + mDeclinedAttendees.size() + 1850 mTentativeAttendees.size() + mNoResponseAttendees.size() > 0) { 1851 mLongAttendees.clearAttendees(); 1852 (mLongAttendees).addAttendees(mAcceptedAttendees); 1853 (mLongAttendees).addAttendees(mDeclinedAttendees); 1854 (mLongAttendees).addAttendees(mTentativeAttendees); 1855 (mLongAttendees).addAttendees(mNoResponseAttendees); 1856 mLongAttendees.setEnabled(false); 1857 mLongAttendees.setVisibility(View.VISIBLE); 1858 } else { 1859 mLongAttendees.setVisibility(View.GONE); 1860 } 1861 1862 if (hasEmailableAttendees()) { 1863 setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE); 1864 if (emailAttendeesButton != null) { 1865 emailAttendeesButton.setText(R.string.email_guests_label); 1866 } 1867 } else if (hasEmailableOrganizer()) { 1868 setVisibilityCommon(mView, R.id.email_attendees_container, View.VISIBLE); 1869 if (emailAttendeesButton != null) { 1870 emailAttendeesButton.setText(R.string.email_organizer_label); 1871 } 1872 } else { 1873 setVisibilityCommon(mView, R.id.email_attendees_container, View.GONE); 1874 } 1875 } 1876 1877 /** 1878 * Returns true if there is at least 1 attendee that is not the viewer. 1879 */ hasEmailableAttendees()1880 private boolean hasEmailableAttendees() { 1881 for (Attendee attendee : mAcceptedAttendees) { 1882 if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { 1883 return true; 1884 } 1885 } 1886 for (Attendee attendee : mTentativeAttendees) { 1887 if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { 1888 return true; 1889 } 1890 } 1891 for (Attendee attendee : mNoResponseAttendees) { 1892 if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { 1893 return true; 1894 } 1895 } 1896 for (Attendee attendee : mDeclinedAttendees) { 1897 if (Utils.isEmailableFrom(attendee.mEmail, mSyncAccountName)) { 1898 return true; 1899 } 1900 } 1901 return false; 1902 } 1903 hasEmailableOrganizer()1904 private boolean hasEmailableOrganizer() { 1905 return mEventOrganizerEmail != null && 1906 Utils.isEmailableFrom(mEventOrganizerEmail, mSyncAccountName); 1907 } 1908 initReminders(View view, Cursor cursor)1909 public void initReminders(View view, Cursor cursor) { 1910 1911 // Add reminders 1912 mOriginalReminders.clear(); 1913 mUnsupportedReminders.clear(); 1914 while (cursor.moveToNext()) { 1915 int minutes = cursor.getInt(EditEventHelper.REMINDERS_INDEX_MINUTES); 1916 int method = cursor.getInt(EditEventHelper.REMINDERS_INDEX_METHOD); 1917 1918 if (method != Reminders.METHOD_DEFAULT && !mReminderMethodValues.contains(method)) { 1919 // Stash unsupported reminder types separately so we don't alter 1920 // them in the UI 1921 mUnsupportedReminders.add(ReminderEntry.valueOf(minutes, method)); 1922 } else { 1923 mOriginalReminders.add(ReminderEntry.valueOf(minutes, method)); 1924 } 1925 } 1926 // Sort appropriately for display (by time, then type) 1927 Collections.sort(mOriginalReminders); 1928 1929 if (mUserModifiedReminders) { 1930 // If the user has changed the list of reminders don't change what's 1931 // shown. 1932 return; 1933 } 1934 1935 LinearLayout parent = (LinearLayout) mScrollView 1936 .findViewById(R.id.reminder_items_container); 1937 if (parent != null) { 1938 parent.removeAllViews(); 1939 } 1940 if (mReminderViews != null) { 1941 mReminderViews.clear(); 1942 } 1943 1944 if (mHasAlarm) { 1945 ArrayList<ReminderEntry> reminders; 1946 // If applicable, use reminders saved in the bundle. 1947 if (mReminders != null) { 1948 reminders = mReminders; 1949 } else { 1950 reminders = mOriginalReminders; 1951 } 1952 // Insert any minute values that aren't represented in the minutes list. 1953 for (ReminderEntry re : reminders) { 1954 EventViewUtils.addMinutesToList( 1955 mActivity, mReminderMinuteValues, mReminderMinuteLabels, re.getMinutes()); 1956 } 1957 // Create a UI element for each reminder. We display all of the reminders we get 1958 // from the provider, even if the count exceeds the calendar maximum. (Also, for 1959 // a new event, we won't have a maxReminders value available.) 1960 for (ReminderEntry re : reminders) { 1961 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, 1962 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, 1963 mReminderMethodLabels, re, Integer.MAX_VALUE, mReminderChangeListener); 1964 } 1965 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); 1966 // TODO show unsupported reminder types in some fashion. 1967 } 1968 } 1969 updateResponse(View view)1970 void updateResponse(View view) { 1971 // we only let the user accept/reject/etc. a meeting if: 1972 // a) you can edit the event's containing calendar AND 1973 // b) you're not the organizer and only attendee AND 1974 // c) organizerCanRespond is enabled for the calendar 1975 // (if the attendee data has been hidden, the visible number of attendees 1976 // will be 1 -- the calendar owner's). 1977 // (there are more cases involved to be 100% accurate, such as 1978 // paying attention to whether or not an attendee status was 1979 // included in the feed, but we're currently omitting those corner cases 1980 // for simplicity). 1981 1982 // TODO Switch to EditEventHelper.canRespond when this class uses CalendarEventModel. 1983 if (!mCanModifyCalendar || (mHasAttendeeData && mIsOrganizer && mNumOfAttendees <= 1) || 1984 (mIsOrganizer && !mOwnerCanRespond)) { 1985 setVisibilityCommon(view, R.id.response_container, View.GONE); 1986 return; 1987 } 1988 1989 setVisibilityCommon(view, R.id.response_container, View.VISIBLE); 1990 1991 1992 int response; 1993 if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 1994 response = mTentativeUserSetResponse; 1995 } else if (mUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 1996 response = mUserSetResponse; 1997 } else if (mAttendeeResponseFromIntent != Attendees.ATTENDEE_STATUS_NONE) { 1998 response = mAttendeeResponseFromIntent; 1999 } else { 2000 response = mOriginalAttendeeResponse; 2001 } 2002 2003 int buttonToCheck = findButtonIdForResponse(response); 2004 mResponseRadioGroup.check(buttonToCheck); // -1 clear all radio buttons 2005 mResponseRadioGroup.setOnCheckedChangeListener(this); 2006 } 2007 setTextCommon(View view, int id, CharSequence text)2008 private void setTextCommon(View view, int id, CharSequence text) { 2009 TextView textView = (TextView) view.findViewById(id); 2010 if (textView == null) 2011 return; 2012 textView.setText(text); 2013 } 2014 setVisibilityCommon(View view, int id, int visibility)2015 private void setVisibilityCommon(View view, int id, int visibility) { 2016 View v = view.findViewById(id); 2017 if (v != null) { 2018 v.setVisibility(visibility); 2019 } 2020 return; 2021 } 2022 2023 /** 2024 * Taken from com.google.android.gm.HtmlConversationActivity 2025 * 2026 * Send the intent that shows the Contact info corresponding to the email address. 2027 */ showContactInfo(Attendee attendee, Rect rect)2028 public void showContactInfo(Attendee attendee, Rect rect) { 2029 // First perform lookup query to find existing contact 2030 final ContentResolver resolver = getActivity().getContentResolver(); 2031 final String address = attendee.mEmail; 2032 final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI, 2033 Uri.encode(address)); 2034 final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri); 2035 2036 if (lookupUri != null) { 2037 // Found matching contact, trigger QuickContact 2038 QuickContact.showQuickContact(getActivity(), rect, lookupUri, 2039 QuickContact.MODE_MEDIUM, null); 2040 } else { 2041 // No matching contact, ask user to create one 2042 final Uri mailUri = Uri.fromParts("mailto", address, null); 2043 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, mailUri); 2044 2045 // Pass along full E-mail string for possible create dialog 2046 Rfc822Token sender = new Rfc822Token(attendee.mName, attendee.mEmail, null); 2047 intent.putExtra(Intents.EXTRA_CREATE_DESCRIPTION, sender.toString()); 2048 2049 // Only provide personal name hint if we have one 2050 final String senderPersonal = attendee.mName; 2051 if (!TextUtils.isEmpty(senderPersonal)) { 2052 intent.putExtra(Intents.Insert.NAME, senderPersonal); 2053 } 2054 2055 startActivity(intent); 2056 } 2057 } 2058 2059 @Override onPause()2060 public void onPause() { 2061 mIsPaused = true; 2062 mHandler.removeCallbacks(onDeleteRunnable); 2063 super.onPause(); 2064 // Remove event deletion alert box since it is being rebuild in the OnResume 2065 // This is done to get the same behavior on OnResume since the AlertDialog is gone on 2066 // rotation but not if you press the HOME key 2067 if (mDeleteDialogVisible && mDeleteHelper != null) { 2068 mDeleteHelper.dismissAlertDialog(); 2069 mDeleteHelper = null; 2070 } 2071 if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE 2072 && mEditResponseHelper != null) { 2073 mEditResponseHelper.dismissAlertDialog(); 2074 } 2075 } 2076 2077 @Override onResume()2078 public void onResume() { 2079 super.onResume(); 2080 if (mIsDialog) { 2081 setDialogSize(getActivity().getResources()); 2082 applyDialogParams(); 2083 } 2084 mIsPaused = false; 2085 if (mDismissOnResume) { 2086 mHandler.post(onDeleteRunnable); 2087 } 2088 // Display the "delete confirmation" or "edit response helper" dialog if needed 2089 if (mDeleteDialogVisible) { 2090 mDeleteHelper = new DeleteEventHelper( 2091 mContext, mActivity, 2092 !mIsDialog && !mIsTabletConfig /* exitWhenDone */); 2093 mDeleteHelper.setOnDismissListener(createDeleteOnDismissListener()); 2094 mDeleteHelper.delete(mStartMillis, mEndMillis, mEventId, -1, onDeleteRunnable); 2095 } else if (mTentativeUserSetResponse != Attendees.ATTENDEE_STATUS_NONE) { 2096 int buttonId = findButtonIdForResponse(mTentativeUserSetResponse); 2097 mResponseRadioGroup.check(buttonId); 2098 mEditResponseHelper.showDialog(mEditResponseHelper.getWhichEvents()); 2099 } 2100 } 2101 2102 @Override eventsChanged()2103 public void eventsChanged() { 2104 } 2105 2106 @Override getSupportedEventTypes()2107 public long getSupportedEventTypes() { 2108 return EventType.EVENTS_CHANGED; 2109 } 2110 2111 @Override handleEvent(EventInfo event)2112 public void handleEvent(EventInfo event) { 2113 reloadEvents(); 2114 } 2115 reloadEvents()2116 public void reloadEvents() { 2117 if (mHandler != null) { 2118 mHandler.startQuery(TOKEN_QUERY_EVENT, null, mUri, EVENT_PROJECTION, 2119 null, null, null); 2120 } 2121 } 2122 2123 @Override onClick(View view)2124 public void onClick(View view) { 2125 2126 // This must be a click on one of the "remove reminder" buttons 2127 LinearLayout reminderItem = (LinearLayout) view.getParent(); 2128 LinearLayout parent = (LinearLayout) reminderItem.getParent(); 2129 parent.removeView(reminderItem); 2130 mReminderViews.remove(reminderItem); 2131 mUserModifiedReminders = true; 2132 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); 2133 } 2134 2135 2136 /** 2137 * Add a new reminder when the user hits the "add reminder" button. We use the default 2138 * reminder time and method. 2139 */ addReminder()2140 private void addReminder() { 2141 // TODO: when adding a new reminder, make it different from the 2142 // last one in the list (if any). 2143 if (mDefaultReminderMinutes == GeneralPreferences.NO_REMINDER) { 2144 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, 2145 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, 2146 mReminderMethodLabels, 2147 ReminderEntry.valueOf(GeneralPreferences.REMINDER_DEFAULT_TIME), mMaxReminders, 2148 mReminderChangeListener); 2149 } else { 2150 EventViewUtils.addReminder(mActivity, mScrollView, this, mReminderViews, 2151 mReminderMinuteValues, mReminderMinuteLabels, mReminderMethodValues, 2152 mReminderMethodLabels, ReminderEntry.valueOf(mDefaultReminderMinutes), 2153 mMaxReminders, mReminderChangeListener); 2154 } 2155 2156 EventViewUtils.updateAddReminderButton(mView, mReminderViews, mMaxReminders); 2157 } 2158 prepareReminders()2159 synchronized private void prepareReminders() { 2160 // Nothing to do if we've already built these lists _and_ we aren't 2161 // removing not allowed methods 2162 if (mReminderMinuteValues != null && mReminderMinuteLabels != null 2163 && mReminderMethodValues != null && mReminderMethodLabels != null 2164 && mCalendarAllowedReminders == null) { 2165 return; 2166 } 2167 // Load the labels and corresponding numeric values for the minutes and methods lists 2168 // from the assets. If we're switching calendars, we need to clear and re-populate the 2169 // lists (which may have elements added and removed based on calendar properties). This 2170 // is mostly relevant for "methods", since we shouldn't have any "minutes" values in a 2171 // new event that aren't in the default set. 2172 Resources r = mActivity.getResources(); 2173 mReminderMinuteValues = loadIntegerArray(r, R.array.reminder_minutes_values); 2174 mReminderMinuteLabels = loadStringArray(r, R.array.reminder_minutes_labels); 2175 mReminderMethodValues = loadIntegerArray(r, R.array.reminder_methods_values); 2176 mReminderMethodLabels = loadStringArray(r, R.array.reminder_methods_labels); 2177 2178 // Remove any reminder methods that aren't allowed for this calendar. If this is 2179 // a new event, mCalendarAllowedReminders may not be set the first time we're called. 2180 if (mCalendarAllowedReminders != null) { 2181 EventViewUtils.reduceMethodList(mReminderMethodValues, mReminderMethodLabels, 2182 mCalendarAllowedReminders); 2183 } 2184 if (mView != null) { 2185 mView.invalidate(); 2186 } 2187 } 2188 2189 saveReminders()2190 private boolean saveReminders() { 2191 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(3); 2192 2193 // Read reminders from UI 2194 mReminders = EventViewUtils.reminderItemsToReminders(mReminderViews, 2195 mReminderMinuteValues, mReminderMethodValues); 2196 mOriginalReminders.addAll(mUnsupportedReminders); 2197 Collections.sort(mOriginalReminders); 2198 mReminders.addAll(mUnsupportedReminders); 2199 Collections.sort(mReminders); 2200 2201 // Check if there are any changes in the reminder 2202 boolean changed = EditEventHelper.saveReminders(ops, mEventId, mReminders, 2203 mOriginalReminders, false /* no force save */); 2204 2205 if (!changed) { 2206 return false; 2207 } 2208 2209 // save new reminders 2210 AsyncQueryService service = new AsyncQueryService(getActivity()); 2211 service.startBatch(0, null, Calendars.CONTENT_URI.getAuthority(), ops, 0); 2212 mOriginalReminders = mReminders; 2213 // Update the "hasAlarm" field for the event 2214 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, mEventId); 2215 int len = mReminders.size(); 2216 boolean hasAlarm = len > 0; 2217 if (hasAlarm != mHasAlarm) { 2218 ContentValues values = new ContentValues(); 2219 values.put(Events.HAS_ALARM, hasAlarm ? 1 : 0); 2220 service.startUpdate(0, null, uri, values, null, null, 0); 2221 } 2222 return true; 2223 } 2224 2225 /** 2226 * Email all the attendees of the event, except for the viewer (so as to not email 2227 * himself) and resources like conference rooms. 2228 */ emailAttendees()2229 private void emailAttendees() { 2230 Intent i = new Intent(getActivity(), QuickResponseActivity.class); 2231 i.putExtra(QuickResponseActivity.EXTRA_EVENT_ID, mEventId); 2232 i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 2233 startActivity(i); 2234 } 2235 2236 /** 2237 * Loads an integer array asset into a list. 2238 */ loadIntegerArray(Resources r, int resNum)2239 private static ArrayList<Integer> loadIntegerArray(Resources r, int resNum) { 2240 int[] vals = r.getIntArray(resNum); 2241 int size = vals.length; 2242 ArrayList<Integer> list = new ArrayList<Integer>(size); 2243 2244 for (int i = 0; i < size; i++) { 2245 list.add(vals[i]); 2246 } 2247 2248 return list; 2249 } 2250 /** 2251 * Loads a String array asset into a list. 2252 */ loadStringArray(Resources r, int resNum)2253 private static ArrayList<String> loadStringArray(Resources r, int resNum) { 2254 String[] labels = r.getStringArray(resNum); 2255 ArrayList<String> list = new ArrayList<String>(Arrays.asList(labels)); 2256 return list; 2257 } 2258 2259 @Override onDeleteStarted()2260 public void onDeleteStarted() { 2261 mEventDeletionStarted = true; 2262 } 2263 createDeleteOnDismissListener()2264 private Dialog.OnDismissListener createDeleteOnDismissListener() { 2265 return new Dialog.OnDismissListener() { 2266 @Override 2267 public void onDismiss(DialogInterface dialog) { 2268 // Since OnPause will force the dialog to dismiss , do 2269 // not change the dialog status 2270 if (!mIsPaused) { 2271 mDeleteDialogVisible = false; 2272 } 2273 } 2274 }; 2275 } 2276 2277 public long getEventId() { 2278 return mEventId; 2279 } 2280 2281 public long getStartMillis() { 2282 return mStartMillis; 2283 } 2284 public long getEndMillis() { 2285 return mEndMillis; 2286 } 2287 private void setDialogSize(Resources r) { 2288 mDialogWidth = (int)r.getDimension(R.dimen.event_info_dialog_width); 2289 mDialogHeight = (int)r.getDimension(R.dimen.event_info_dialog_height); 2290 } 2291 2292 @Override 2293 public void onColorSelected(int color) { 2294 mCurrentColor = color; 2295 mCurrentColorKey = mDisplayColorKeyMap.get(color); 2296 mHeadlines.setBackgroundColor(color); 2297 } 2298 } 2299