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 android.provider.CalendarContract.Attendees.ATTENDEE_STATUS; 23 24 import android.accounts.Account; 25 import android.accounts.AccountManager; 26 import android.app.Activity; 27 import android.app.SearchManager; 28 import android.app.SearchableInfo; 29 import android.content.ComponentName; 30 import android.content.ContentResolver; 31 import android.content.ContentUris; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.provider.CalendarContract.Attendees; 37 import android.provider.CalendarContract.Calendars; 38 import android.provider.CalendarContract.Events; 39 import android.text.format.Time; 40 import android.util.Log; 41 import android.util.Pair; 42 43 import com.android.calendar.event.EditEventActivity; 44 import com.android.calendar.selectcalendars.SelectVisibleCalendarsActivity; 45 46 import java.lang.ref.WeakReference; 47 import java.util.Iterator; 48 import java.util.LinkedHashMap; 49 import java.util.LinkedList; 50 import java.util.Map.Entry; 51 import java.util.WeakHashMap; 52 53 public class CalendarController { 54 private static final boolean DEBUG = false; 55 private static final String TAG = "CalendarController"; 56 57 public static final String EVENT_EDIT_ON_LAUNCH = "editMode"; 58 59 public static final int MIN_CALENDAR_YEAR = 1970; 60 public static final int MAX_CALENDAR_YEAR = 2036; 61 public static final int MIN_CALENDAR_WEEK = 0; 62 public static final int MAX_CALENDAR_WEEK = 3497; // weeks between 1/1/1970 and 1/1/2037 63 64 private final Context mContext; 65 66 // This uses a LinkedHashMap so that we can replace fragments based on the 67 // view id they are being expanded into since we can't guarantee a reference 68 // to the handler will be findable 69 private final LinkedHashMap<Integer,EventHandler> eventHandlers = 70 new LinkedHashMap<Integer,EventHandler>(5); 71 private final LinkedList<Integer> mToBeRemovedEventHandlers = new LinkedList<Integer>(); 72 private final LinkedHashMap<Integer, EventHandler> mToBeAddedEventHandlers = new LinkedHashMap< 73 Integer, EventHandler>(); 74 private Pair<Integer, EventHandler> mFirstEventHandler; 75 private Pair<Integer, EventHandler> mToBeAddedFirstEventHandler; 76 private volatile int mDispatchInProgressCounter = 0; 77 78 private static WeakHashMap<Context, WeakReference<CalendarController>> instances = 79 new WeakHashMap<Context, WeakReference<CalendarController>>(); 80 81 private final WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1); 82 83 private int mViewType = -1; 84 private int mDetailViewType = -1; 85 private int mPreviousViewType = -1; 86 private long mEventId = -1; 87 private final Time mTime = new Time(); 88 private long mDateFlags = 0; 89 90 private final Runnable mUpdateTimezone = new Runnable() { 91 @Override 92 public void run() { 93 mTime.switchTimezone(Utils.getTimeZone(mContext, this)); 94 } 95 }; 96 97 /** 98 * One of the event types that are sent to or from the controller 99 */ 100 public interface EventType { 101 final long CREATE_EVENT = 1L; 102 103 // Simple view of an event 104 final long VIEW_EVENT = 1L << 1; 105 106 // Full detail view in read only mode 107 final long VIEW_EVENT_DETAILS = 1L << 2; 108 109 // full detail view in edit mode 110 final long EDIT_EVENT = 1L << 3; 111 112 final long DELETE_EVENT = 1L << 4; 113 114 final long GO_TO = 1L << 5; 115 116 final long LAUNCH_SETTINGS = 1L << 6; 117 118 final long EVENTS_CHANGED = 1L << 7; 119 120 final long SEARCH = 1L << 8; 121 122 // User has pressed the home key 123 final long USER_HOME = 1L << 9; 124 125 // date range has changed, update the title 126 final long UPDATE_TITLE = 1L << 10; 127 128 // select which calendars to display 129 final long LAUNCH_SELECT_VISIBLE_CALENDARS = 1L << 11; 130 } 131 132 /** 133 * One of the Agenda/Day/Week/Month view types 134 */ 135 public interface ViewType { 136 final int DETAIL = -1; 137 final int CURRENT = 0; 138 final int AGENDA = 1; 139 final int DAY = 2; 140 final int WEEK = 3; 141 final int MONTH = 4; 142 final int EDIT = 5; 143 final int MAX_VALUE = 5; 144 } 145 146 public static class EventInfo { 147 148 private static final long ATTENTEE_STATUS_MASK = 0xFF; 149 private static final long ALL_DAY_MASK = 0x100; 150 private static final int ATTENDEE_STATUS_NONE_MASK = 0x01; 151 private static final int ATTENDEE_STATUS_ACCEPTED_MASK = 0x02; 152 private static final int ATTENDEE_STATUS_DECLINED_MASK = 0x04; 153 private static final int ATTENDEE_STATUS_TENTATIVE_MASK = 0x08; 154 155 public long eventType; // one of the EventType 156 public int viewType; // one of the ViewType 157 public long id; // event id 158 public Time selectedTime; // the selected time in focus 159 160 // Event start and end times. All-day events are represented in: 161 // - local time for GO_TO commands 162 // - UTC time for VIEW_EVENT and other event-related commands 163 public Time startTime; 164 public Time endTime; 165 166 public int x; // x coordinate in the activity space 167 public int y; // y coordinate in the activity space 168 public String query; // query for a user search 169 public ComponentName componentName; // used in combination with query 170 public String eventTitle; 171 public long calendarId; 172 173 /** 174 * For EventType.VIEW_EVENT: 175 * It is the default attendee response and an all day event indicator. 176 * Set to Attendees.ATTENDEE_STATUS_NONE, Attendees.ATTENDEE_STATUS_ACCEPTED, 177 * Attendees.ATTENDEE_STATUS_DECLINED, or Attendees.ATTENDEE_STATUS_TENTATIVE. 178 * To signal the event is an all-day event, "or" ALL_DAY_MASK with the response. 179 * Alternatively, use buildViewExtraLong(), getResponse(), and isAllDay(). 180 * <p> 181 * For EventType.CREATE_EVENT: 182 * Set to {@link #EXTRA_CREATE_ALL_DAY} for creating an all-day event. 183 * <p> 184 * For EventType.GO_TO: 185 * Set to {@link #EXTRA_GOTO_TIME} to go to the specified date/time. 186 * Set to {@link #EXTRA_GOTO_DATE} to consider the date but ignore the time. 187 * Set to {@link #EXTRA_GOTO_BACK_TO_PREVIOUS} if back should bring back previous view. 188 * Set to {@link #EXTRA_GOTO_TODAY} if this is a user request to go to the current time. 189 * <p> 190 * For EventType.UPDATE_TITLE: 191 * Set formatting flags for Utils.formatDateRange 192 */ 193 public long extraLong; 194 isAllDay()195 public boolean isAllDay() { 196 if (eventType != EventType.VIEW_EVENT) { 197 Log.wtf(TAG, "illegal call to isAllDay , wrong event type " + eventType); 198 return false; 199 } 200 return ((extraLong & ALL_DAY_MASK) != 0) ? true : false; 201 } 202 getResponse()203 public int getResponse() { 204 if (eventType != EventType.VIEW_EVENT) { 205 Log.wtf(TAG, "illegal call to getResponse , wrong event type " + eventType); 206 return Attendees.ATTENDEE_STATUS_NONE; 207 } 208 209 int response = (int)(extraLong & ATTENTEE_STATUS_MASK); 210 switch (response) { 211 case ATTENDEE_STATUS_NONE_MASK: 212 return Attendees.ATTENDEE_STATUS_NONE; 213 case ATTENDEE_STATUS_ACCEPTED_MASK: 214 return Attendees.ATTENDEE_STATUS_ACCEPTED; 215 case ATTENDEE_STATUS_DECLINED_MASK: 216 return Attendees.ATTENDEE_STATUS_DECLINED; 217 case ATTENDEE_STATUS_TENTATIVE_MASK: 218 return Attendees.ATTENDEE_STATUS_TENTATIVE; 219 default: 220 Log.wtf(TAG,"Unknown attendee response " + response); 221 } 222 return ATTENDEE_STATUS_NONE_MASK; 223 } 224 225 // Used to build the extra long for a VIEW event. buildViewExtraLong(int response, boolean allDay)226 public static long buildViewExtraLong(int response, boolean allDay) { 227 long extra = allDay ? ALL_DAY_MASK : 0; 228 229 switch (response) { 230 case Attendees.ATTENDEE_STATUS_NONE: 231 extra |= ATTENDEE_STATUS_NONE_MASK; 232 break; 233 case Attendees.ATTENDEE_STATUS_ACCEPTED: 234 extra |= ATTENDEE_STATUS_ACCEPTED_MASK; 235 break; 236 case Attendees.ATTENDEE_STATUS_DECLINED: 237 extra |= ATTENDEE_STATUS_DECLINED_MASK; 238 break; 239 case Attendees.ATTENDEE_STATUS_TENTATIVE: 240 extra |= ATTENDEE_STATUS_TENTATIVE_MASK; 241 break; 242 default: 243 Log.wtf(TAG,"Unknown attendee response " + response); 244 extra |= ATTENDEE_STATUS_NONE_MASK; 245 break; 246 } 247 return extra; 248 } 249 } 250 251 /** 252 * Pass to the ExtraLong parameter for EventType.CREATE_EVENT to create 253 * an all-day event 254 */ 255 public static final long EXTRA_CREATE_ALL_DAY = 0x10; 256 257 /** 258 * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time 259 * can be ignored 260 */ 261 public static final long EXTRA_GOTO_DATE = 1; 262 public static final long EXTRA_GOTO_TIME = 2; 263 public static final long EXTRA_GOTO_BACK_TO_PREVIOUS = 4; 264 public static final long EXTRA_GOTO_TODAY = 8; 265 266 public interface EventHandler { getSupportedEventTypes()267 long getSupportedEventTypes(); handleEvent(EventInfo event)268 void handleEvent(EventInfo event); 269 270 /** 271 * This notifies the handler that the database has changed and it should 272 * update its view. 273 */ eventsChanged()274 void eventsChanged(); 275 } 276 277 /** 278 * Creates and/or returns an instance of CalendarController associated with 279 * the supplied context. It is best to pass in the current Activity. 280 * 281 * @param context The activity if at all possible. 282 */ getInstance(Context context)283 public static CalendarController getInstance(Context context) { 284 synchronized (instances) { 285 CalendarController controller = null; 286 WeakReference<CalendarController> weakController = instances.get(context); 287 if (weakController != null) { 288 controller = weakController.get(); 289 } 290 291 if (controller == null) { 292 controller = new CalendarController(context); 293 instances.put(context, new WeakReference(controller)); 294 } 295 return controller; 296 } 297 } 298 299 /** 300 * Removes an instance when it is no longer needed. This should be called in 301 * an activity's onDestroy method. 302 * 303 * @param context The activity used to create the controller 304 */ removeInstance(Context context)305 public static void removeInstance(Context context) { 306 instances.remove(context); 307 } 308 CalendarController(Context context)309 private CalendarController(Context context) { 310 mContext = context; 311 mUpdateTimezone.run(); 312 mTime.setToNow(); 313 mDetailViewType = Utils.getSharedPreference(mContext, 314 GeneralPreferences.KEY_DETAILED_VIEW, 315 GeneralPreferences.DEFAULT_DETAILED_VIEW); 316 } 317 sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long selectedMillis)318 public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis, 319 long endMillis, int x, int y, long selectedMillis) { 320 // TODO: pass the real allDay status or at least a status that says we don't know the 321 // status and have the receiver query the data. 322 // The current use of this method for VIEW_EVENT is by the day view to show an EventInfo 323 // so currently the missing allDay status has no effect. 324 sendEventRelatedEventWithExtra(sender, eventType, eventId, startMillis, endMillis, x, y, 325 EventInfo.buildViewExtraLong(Attendees.ATTENDEE_STATUS_NONE, false), 326 selectedMillis); 327 } 328 329 /** 330 * Helper for sending New/View/Edit/Delete events 331 * 332 * @param sender object of the caller 333 * @param eventType one of {@link EventType} 334 * @param eventId event id 335 * @param startMillis start time 336 * @param endMillis end time 337 * @param x x coordinate in the activity space 338 * @param y y coordinate in the activity space 339 * @param extraLong default response value for the "simple event view" and all day indication. 340 * Use Attendees.ATTENDEE_STATUS_NONE for no response. 341 * @param selectedMillis The time to specify as selected 342 */ sendEventRelatedEventWithExtra(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis)343 public void sendEventRelatedEventWithExtra(Object sender, long eventType, long eventId, 344 long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis) { 345 sendEventRelatedEventWithExtraWithTitleWithCalendarId(sender, eventType, eventId, 346 startMillis, endMillis, x, y, extraLong, selectedMillis, null, -1); 347 } 348 349 /** 350 * Helper for sending New/View/Edit/Delete events 351 * 352 * @param sender object of the caller 353 * @param eventType one of {@link EventType} 354 * @param eventId event id 355 * @param startMillis start time 356 * @param endMillis end time 357 * @param x x coordinate in the activity space 358 * @param y y coordinate in the activity space 359 * @param extraLong default response value for the "simple event view" and all day indication. 360 * Use Attendees.ATTENDEE_STATUS_NONE for no response. 361 * @param selectedMillis The time to specify as selected 362 * @param title The title of the event 363 * @param calendarId The id of the calendar which the event belongs to 364 */ sendEventRelatedEventWithExtraWithTitleWithCalendarId(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis, String title, long calendarId)365 public void sendEventRelatedEventWithExtraWithTitleWithCalendarId(Object sender, long eventType, 366 long eventId, long startMillis, long endMillis, int x, int y, long extraLong, 367 long selectedMillis, String title, long calendarId) { 368 EventInfo info = new EventInfo(); 369 info.eventType = eventType; 370 if (eventType == EventType.EDIT_EVENT || eventType == EventType.VIEW_EVENT_DETAILS) { 371 info.viewType = ViewType.CURRENT; 372 } 373 374 info.id = eventId; 375 info.startTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone)); 376 info.startTime.set(startMillis); 377 if (selectedMillis != -1) { 378 info.selectedTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone)); 379 info.selectedTime.set(selectedMillis); 380 } else { 381 info.selectedTime = info.startTime; 382 } 383 info.endTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone)); 384 info.endTime.set(endMillis); 385 info.x = x; 386 info.y = y; 387 info.extraLong = extraLong; 388 info.eventTitle = title; 389 info.calendarId = calendarId; 390 this.sendEvent(sender, info); 391 } 392 /** 393 * Helper for sending non-calendar-event events 394 * 395 * @param sender object of the caller 396 * @param eventType one of {@link EventType} 397 * @param start start time 398 * @param end end time 399 * @param eventId event id 400 * @param viewType {@link ViewType} 401 */ sendEvent(Object sender, long eventType, Time start, Time end, long eventId, int viewType)402 public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId, 403 int viewType) { 404 sendEvent(sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null, 405 null); 406 } 407 408 /** 409 * sendEvent() variant with extraLong, search query, and search component name. 410 */ sendEvent(Object sender, long eventType, Time start, Time end, long eventId, int viewType, long extraLong, String query, ComponentName componentName)411 public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId, 412 int viewType, long extraLong, String query, ComponentName componentName) { 413 sendEvent(sender, eventType, start, end, start, eventId, viewType, extraLong, query, 414 componentName); 415 } 416 sendEvent(Object sender, long eventType, Time start, Time end, Time selected, long eventId, int viewType, long extraLong, String query, ComponentName componentName)417 public void sendEvent(Object sender, long eventType, Time start, Time end, Time selected, 418 long eventId, int viewType, long extraLong, String query, ComponentName componentName) { 419 EventInfo info = new EventInfo(); 420 info.eventType = eventType; 421 info.startTime = start; 422 info.selectedTime = selected; 423 info.endTime = end; 424 info.id = eventId; 425 info.viewType = viewType; 426 info.query = query; 427 info.componentName = componentName; 428 info.extraLong = extraLong; 429 this.sendEvent(sender, info); 430 } 431 sendEvent(Object sender, final EventInfo event)432 public void sendEvent(Object sender, final EventInfo event) { 433 // TODO Throw exception on invalid events 434 435 if (DEBUG) { 436 Log.d(TAG, eventInfoToString(event)); 437 } 438 439 Long filteredTypes = filters.get(sender); 440 if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) { 441 // Suppress event per filter 442 if (DEBUG) { 443 Log.d(TAG, "Event suppressed"); 444 } 445 return; 446 } 447 448 mPreviousViewType = mViewType; 449 450 // Fix up view if not specified 451 if (event.viewType == ViewType.DETAIL) { 452 event.viewType = mDetailViewType; 453 mViewType = mDetailViewType; 454 } else if (event.viewType == ViewType.CURRENT) { 455 event.viewType = mViewType; 456 } else if (event.viewType != ViewType.EDIT) { 457 mViewType = event.viewType; 458 459 if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY 460 || (Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK)) { 461 mDetailViewType = mViewType; 462 } 463 } 464 465 if (DEBUG) { 466 Log.d(TAG, "vvvvvvvvvvvvvvv"); 467 Log.d(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString())); 468 Log.d(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString())); 469 Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString())); 470 Log.d(TAG, "mTime " + (mTime == null ? "null" : mTime.toString())); 471 } 472 473 long startMillis = 0; 474 if (event.startTime != null) { 475 startMillis = event.startTime.toMillis(false); 476 } 477 478 // Set mTime if selectedTime is set 479 if (event.selectedTime != null && event.selectedTime.toMillis(false) != 0) { 480 mTime.set(event.selectedTime); 481 } else { 482 if (startMillis != 0) { 483 // selectedTime is not set so set mTime to startTime iff it is not 484 // within start and end times 485 long mtimeMillis = mTime.toMillis(false); 486 if (mtimeMillis < startMillis 487 || (event.endTime != null && mtimeMillis > event.endTime.toMillis(false))) { 488 mTime.set(event.startTime); 489 } 490 } 491 event.selectedTime = mTime; 492 } 493 // Store the formatting flags if this is an update to the title 494 if (event.eventType == EventType.UPDATE_TITLE) { 495 mDateFlags = event.extraLong; 496 } 497 498 // Fix up start time if not specified 499 if (startMillis == 0) { 500 event.startTime = mTime; 501 } 502 if (DEBUG) { 503 Log.d(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString())); 504 Log.d(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString())); 505 Log.d(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString())); 506 Log.d(TAG, "mTime " + (mTime == null ? "null" : mTime.toString())); 507 Log.d(TAG, "^^^^^^^^^^^^^^^"); 508 } 509 510 // Store the eventId if we're entering edit event 511 if ((event.eventType 512 & (EventType.CREATE_EVENT | EventType.EDIT_EVENT | EventType.VIEW_EVENT_DETAILS)) 513 != 0) { 514 if (event.id > 0) { 515 mEventId = event.id; 516 } else { 517 mEventId = -1; 518 } 519 } 520 521 boolean handled = false; 522 synchronized (this) { 523 mDispatchInProgressCounter ++; 524 525 if (DEBUG) { 526 Log.d(TAG, "sendEvent: Dispatching to " + eventHandlers.size() + " handlers"); 527 } 528 // Dispatch to event handler(s) 529 if (mFirstEventHandler != null) { 530 // Handle the 'first' one before handling the others 531 EventHandler handler = mFirstEventHandler.second; 532 if (handler != null && (handler.getSupportedEventTypes() & event.eventType) != 0 533 && !mToBeRemovedEventHandlers.contains(mFirstEventHandler.first)) { 534 handler.handleEvent(event); 535 handled = true; 536 } 537 } 538 for (Iterator<Entry<Integer, EventHandler>> handlers = 539 eventHandlers.entrySet().iterator(); handlers.hasNext();) { 540 Entry<Integer, EventHandler> entry = handlers.next(); 541 int key = entry.getKey(); 542 if (mFirstEventHandler != null && key == mFirstEventHandler.first) { 543 // If this was the 'first' handler it was already handled 544 continue; 545 } 546 EventHandler eventHandler = entry.getValue(); 547 if (eventHandler != null 548 && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) { 549 if (mToBeRemovedEventHandlers.contains(key)) { 550 continue; 551 } 552 eventHandler.handleEvent(event); 553 handled = true; 554 } 555 } 556 557 mDispatchInProgressCounter --; 558 559 if (mDispatchInProgressCounter == 0) { 560 561 // Deregister removed handlers 562 if (mToBeRemovedEventHandlers.size() > 0) { 563 for (Integer zombie : mToBeRemovedEventHandlers) { 564 eventHandlers.remove(zombie); 565 if (mFirstEventHandler != null && zombie.equals(mFirstEventHandler.first)) { 566 mFirstEventHandler = null; 567 } 568 } 569 mToBeRemovedEventHandlers.clear(); 570 } 571 // Add new handlers 572 if (mToBeAddedFirstEventHandler != null) { 573 mFirstEventHandler = mToBeAddedFirstEventHandler; 574 mToBeAddedFirstEventHandler = null; 575 } 576 if (mToBeAddedEventHandlers.size() > 0) { 577 for (Entry<Integer, EventHandler> food : mToBeAddedEventHandlers.entrySet()) { 578 eventHandlers.put(food.getKey(), food.getValue()); 579 } 580 } 581 } 582 } 583 584 if (!handled) { 585 // Launch Settings 586 if (event.eventType == EventType.LAUNCH_SETTINGS) { 587 launchSettings(); 588 return; 589 } 590 591 // Launch Calendar Visible Selector 592 if (event.eventType == EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) { 593 launchSelectVisibleCalendars(); 594 return; 595 } 596 597 // Create/View/Edit/Delete Event 598 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); 599 if (event.eventType == EventType.CREATE_EVENT) { 600 launchCreateEvent(event.startTime.toMillis(false), endTime, 601 event.extraLong == EXTRA_CREATE_ALL_DAY, event.eventTitle, 602 event.calendarId); 603 return; 604 } else if (event.eventType == EventType.VIEW_EVENT) { 605 launchViewEvent(event.id, event.startTime.toMillis(false), endTime, 606 event.getResponse()); 607 return; 608 } else if (event.eventType == EventType.EDIT_EVENT) { 609 launchEditEvent(event.id, event.startTime.toMillis(false), endTime, true); 610 return; 611 } else if (event.eventType == EventType.VIEW_EVENT_DETAILS) { 612 launchEditEvent(event.id, event.startTime.toMillis(false), endTime, false); 613 return; 614 } else if (event.eventType == EventType.DELETE_EVENT) { 615 launchDeleteEvent(event.id, event.startTime.toMillis(false), endTime); 616 return; 617 } else if (event.eventType == EventType.SEARCH) { 618 launchSearch(event.id, event.query, event.componentName); 619 return; 620 } 621 } 622 } 623 624 /** 625 * Adds or updates an event handler. This uses a LinkedHashMap so that we can 626 * replace fragments based on the view id they are being expanded into. 627 * 628 * @param key The view id or placeholder for this handler 629 * @param eventHandler Typically a fragment or activity in the calendar app 630 */ registerEventHandler(int key, EventHandler eventHandler)631 public void registerEventHandler(int key, EventHandler eventHandler) { 632 synchronized (this) { 633 if (mDispatchInProgressCounter > 0) { 634 mToBeAddedEventHandlers.put(key, eventHandler); 635 } else { 636 eventHandlers.put(key, eventHandler); 637 } 638 } 639 } 640 registerFirstEventHandler(int key, EventHandler eventHandler)641 public void registerFirstEventHandler(int key, EventHandler eventHandler) { 642 synchronized (this) { 643 registerEventHandler(key, eventHandler); 644 if (mDispatchInProgressCounter > 0) { 645 mToBeAddedFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler); 646 } else { 647 mFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler); 648 } 649 } 650 } 651 deregisterEventHandler(Integer key)652 public void deregisterEventHandler(Integer key) { 653 synchronized (this) { 654 if (mDispatchInProgressCounter > 0) { 655 // To avoid ConcurrencyException, stash away the event handler for now. 656 mToBeRemovedEventHandlers.add(key); 657 } else { 658 eventHandlers.remove(key); 659 if (mFirstEventHandler != null && mFirstEventHandler.first == key) { 660 mFirstEventHandler = null; 661 } 662 } 663 } 664 } 665 deregisterAllEventHandlers()666 public void deregisterAllEventHandlers() { 667 synchronized (this) { 668 if (mDispatchInProgressCounter > 0) { 669 // To avoid ConcurrencyException, stash away the event handler for now. 670 mToBeRemovedEventHandlers.addAll(eventHandlers.keySet()); 671 } else { 672 eventHandlers.clear(); 673 mFirstEventHandler = null; 674 } 675 } 676 } 677 678 // FRAG_TODO doesn't work yet filterBroadcasts(Object sender, long eventTypes)679 public void filterBroadcasts(Object sender, long eventTypes) { 680 filters.put(sender, eventTypes); 681 } 682 683 /** 684 * @return the time that this controller is currently pointed at 685 */ getTime()686 public long getTime() { 687 return mTime.toMillis(false); 688 } 689 690 /** 691 * @return the last set of date flags sent with 692 * {@link EventType#UPDATE_TITLE} 693 */ getDateFlags()694 public long getDateFlags() { 695 return mDateFlags; 696 } 697 698 /** 699 * Set the time this controller is currently pointed at 700 * 701 * @param millisTime Time since epoch in millis 702 */ setTime(long millisTime)703 public void setTime(long millisTime) { 704 mTime.set(millisTime); 705 } 706 707 /** 708 * @return the last event ID the edit view was launched with 709 */ getEventId()710 public long getEventId() { 711 return mEventId; 712 } 713 getViewType()714 public int getViewType() { 715 return mViewType; 716 } 717 getPreviousViewType()718 public int getPreviousViewType() { 719 return mPreviousViewType; 720 } 721 launchSelectVisibleCalendars()722 private void launchSelectVisibleCalendars() { 723 Intent intent = new Intent(Intent.ACTION_VIEW); 724 intent.setClass(mContext, SelectVisibleCalendarsActivity.class); 725 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); 726 mContext.startActivity(intent); 727 } 728 launchSettings()729 private void launchSettings() { 730 Intent intent = new Intent(Intent.ACTION_VIEW); 731 intent.setClass(mContext, CalendarSettingsActivity.class); 732 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); 733 mContext.startActivity(intent); 734 } 735 launchCreateEvent(long startMillis, long endMillis, boolean allDayEvent, String title, long calendarId)736 private void launchCreateEvent(long startMillis, long endMillis, boolean allDayEvent, 737 String title, long calendarId) { 738 Intent intent = generateCreateEventIntent(startMillis, endMillis, allDayEvent, title, 739 calendarId); 740 mEventId = -1; 741 mContext.startActivity(intent); 742 } 743 generateCreateEventIntent(long startMillis, long endMillis, boolean allDayEvent, String title, long calendarId)744 public Intent generateCreateEventIntent(long startMillis, long endMillis, 745 boolean allDayEvent, String title, long calendarId) { 746 Intent intent = new Intent(Intent.ACTION_VIEW); 747 intent.setClass(mContext, EditEventActivity.class); 748 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); 749 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); 750 intent.putExtra(EXTRA_EVENT_ALL_DAY, allDayEvent); 751 intent.putExtra(Events.CALENDAR_ID, calendarId); 752 intent.putExtra(Events.TITLE, title); 753 return intent; 754 } 755 launchViewEvent(long eventId, long startMillis, long endMillis, int response)756 public void launchViewEvent(long eventId, long startMillis, long endMillis, int response) { 757 Intent intent = new Intent(Intent.ACTION_VIEW); 758 Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); 759 intent.setData(eventUri); 760 intent.setClass(mContext, AllInOneActivity.class); 761 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); 762 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); 763 intent.putExtra(ATTENDEE_STATUS, response); 764 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 765 mContext.startActivity(intent); 766 } 767 launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit)768 private void launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit) { 769 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); 770 Intent intent = new Intent(Intent.ACTION_EDIT, uri); 771 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis); 772 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis); 773 intent.setClass(mContext, EditEventActivity.class); 774 intent.putExtra(EVENT_EDIT_ON_LAUNCH, edit); 775 mEventId = eventId; 776 mContext.startActivity(intent); 777 } 778 779 // private void launchAlerts() { 780 // Intent intent = new Intent(); 781 // intent.setClass(mContext, AlertActivity.class); 782 // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 783 // mContext.startActivity(intent); 784 // } 785 launchDeleteEvent(long eventId, long startMillis, long endMillis)786 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { 787 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); 788 } 789 launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis, long endMillis, int deleteWhich)790 private void launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis, 791 long endMillis, int deleteWhich) { 792 DeleteEventHelper deleteEventHelper = new DeleteEventHelper(mContext, parentActivity, 793 parentActivity != null /* exit when done */); 794 deleteEventHelper.delete(startMillis, endMillis, eventId, deleteWhich); 795 } 796 launchSearch(long eventId, String query, ComponentName componentName)797 private void launchSearch(long eventId, String query, ComponentName componentName) { 798 final SearchManager searchManager = 799 (SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE); 800 final SearchableInfo searchableInfo = searchManager.getSearchableInfo(componentName); 801 final Intent intent = new Intent(Intent.ACTION_SEARCH); 802 intent.putExtra(SearchManager.QUERY, query); 803 intent.setComponent(searchableInfo.getSearchActivity()); 804 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 805 mContext.startActivity(intent); 806 } 807 808 /** 809 * Performs a manual refresh of calendars in all known accounts. 810 */ refreshCalendars()811 public void refreshCalendars() { 812 Account[] accounts = AccountManager.get(mContext).getAccounts(); 813 Log.d(TAG, "Refreshing " + accounts.length + " accounts"); 814 815 String authority = Calendars.CONTENT_URI.getAuthority(); 816 for (int i = 0; i < accounts.length; i++) { 817 if (Log.isLoggable(TAG, Log.DEBUG)) { 818 Log.d(TAG, "Refreshing calendars for: " + accounts[i]); 819 } 820 Bundle extras = new Bundle(); 821 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 822 ContentResolver.requestSync(accounts[i], authority, extras); 823 } 824 } 825 826 // Forces the viewType. Should only be used for initialization. setViewType(int viewType)827 public void setViewType(int viewType) { 828 mViewType = viewType; 829 } 830 831 // Sets the eventId. Should only be used for initialization. setEventId(long eventId)832 public void setEventId(long eventId) { 833 mEventId = eventId; 834 } 835 eventInfoToString(EventInfo eventInfo)836 private String eventInfoToString(EventInfo eventInfo) { 837 String tmp = "Unknown"; 838 839 StringBuilder builder = new StringBuilder(); 840 if ((eventInfo.eventType & EventType.GO_TO) != 0) { 841 tmp = "Go to time/event"; 842 } else if ((eventInfo.eventType & EventType.CREATE_EVENT) != 0) { 843 tmp = "New event"; 844 } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) { 845 tmp = "View event"; 846 } else if ((eventInfo.eventType & EventType.VIEW_EVENT_DETAILS) != 0) { 847 tmp = "View details"; 848 } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) { 849 tmp = "Edit event"; 850 } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) { 851 tmp = "Delete event"; 852 } else if ((eventInfo.eventType & EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) != 0) { 853 tmp = "Launch select visible calendars"; 854 } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) { 855 tmp = "Launch settings"; 856 } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) { 857 tmp = "Refresh events"; 858 } else if ((eventInfo.eventType & EventType.SEARCH) != 0) { 859 tmp = "Search"; 860 } else if ((eventInfo.eventType & EventType.USER_HOME) != 0) { 861 tmp = "Gone home"; 862 } else if ((eventInfo.eventType & EventType.UPDATE_TITLE) != 0) { 863 tmp = "Update title"; 864 } 865 builder.append(tmp); 866 builder.append(": id="); 867 builder.append(eventInfo.id); 868 builder.append(", selected="); 869 builder.append(eventInfo.selectedTime); 870 builder.append(", start="); 871 builder.append(eventInfo.startTime); 872 builder.append(", end="); 873 builder.append(eventInfo.endTime); 874 builder.append(", viewType="); 875 builder.append(eventInfo.viewType); 876 builder.append(", x="); 877 builder.append(eventInfo.x); 878 builder.append(", y="); 879 builder.append(eventInfo.y); 880 return builder.toString(); 881 } 882 } 883