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 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 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. 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 { 265 long getSupportedEventTypes(); 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 */ 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 */ 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 */ 298 public static void removeInstance(Context context) { 299 instances.remove(context); 300 } 301 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 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 */ 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 */ 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 */ 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 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 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 */ 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 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 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 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 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 */ 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 */ 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 */ 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 */ 677 public long getEventId() { 678 return mEventId; 679 } 680 681 public int getViewType() { 682 return mViewType; 683 } 684 685 public int getPreviousViewType() { 686 return mPreviousViewType; 687 } 688 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 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 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 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 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 743 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { 744 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); 745 } 746 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 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 */ 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. 784 public void setViewType(int viewType) { 785 mViewType = viewType; 786 } 787 788 // Sets the eventId. Should only be used for initialization. 789 public void setEventId(long eventId) { 790 mEventId = eventId; 791 } 792 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