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 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 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. 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 { 267 long getSupportedEventTypes(); 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 */ 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 */ 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 */ 305 public static void removeInstance(Context context) { 306 instances.remove(context); 307 } 308 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 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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 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 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 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 */ 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 */ 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 */ 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 */ 710 public long getEventId() { 711 return mEventId; 712 } 713 714 public int getViewType() { 715 return mViewType; 716 } 717 718 public int getPreviousViewType() { 719 return mPreviousViewType; 720 } 721 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 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 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 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 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 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 786 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { 787 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); 788 } 789 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 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 */ 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. 827 public void setViewType(int viewType) { 828 mViewType = viewType; 829 } 830 831 // Sets the eventId. Should only be used for initialization. 832 public void setEventId(long eventId) { 833 mEventId = eventId; 834 } 835 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