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