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