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 isAllDay()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 getResponse()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. buildViewExtraLong(int response, boolean allDay)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 { getSupportedEventTypes()266 long getSupportedEventTypes(); handleEvent(EventInfo event)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 */ eventsChanged()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 */ getInstance(Context context)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 */ removeInstance(Context context)299 public static void removeInstance(Context context) { 300 instances.remove(context); 301 } 302 CalendarController(Context context)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 sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long selectedMillis)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 */ sendEventRelatedEventWithExtra(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis)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 */ sendEventRelatedEventWithExtraWithTitleWithCalendarId(Object sender, long eventType, long eventId, long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis, String title, long calendarId)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 */ sendEvent(Object sender, long eventType, Time start, Time end, long eventId, int viewType)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 */ sendEvent(Object sender, long eventType, Time start, Time end, long eventId, int viewType, long extraLong, String query, ComponentName componentName)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 sendEvent(Object sender, long eventType, Time start, Time end, Time selected, long eventId, int viewType, long extraLong, String query, ComponentName componentName)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 sendEvent(Object sender, final EventInfo event)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 */ registerEventHandler(int key, EventHandler eventHandler)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 registerFirstEventHandler(int key, EventHandler eventHandler)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 deregisterEventHandler(Integer key)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 deregisterAllEventHandlers()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 filterBroadcasts(Object sender, long eventTypes)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 */ getTime()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 */ getDateFlags()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 */ setTime(long millisTime)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 */ getEventId()704 public long getEventId() { 705 return mEventId; 706 } 707 getViewType()708 public int getViewType() { 709 return mViewType; 710 } 711 getPreviousViewType()712 public int getPreviousViewType() { 713 return mPreviousViewType; 714 } 715 launchSelectVisibleCalendars()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 launchSettings()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 launchCreateEvent(long startMillis, long endMillis, boolean allDayEvent, String title, long calendarId)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 generateCreateEventIntent(long startMillis, long endMillis, boolean allDayEvent, String title, long calendarId)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 launchViewEvent(long eventId, long startMillis, long endMillis, int response)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 launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit)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 launchDeleteEvent(long eventId, long startMillis, long endMillis)780 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { 781 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); 782 } 783 launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis, long endMillis, int deleteWhich)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 launchSearch(long eventId, String query, ComponentName componentName)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 */ refreshCalendars()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. setViewType(int viewType)821 public void setViewType(int viewType) { 822 mViewType = viewType; 823 } 824 825 // Sets the eventId. Should only be used for initialization. setEventId(long eventId)826 public void setEventId(long eventId) { 827 mEventId = eventId; 828 } 829 eventInfoToString(EventInfo eventInfo)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