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