CalendarController.java revision c7003b457950130d31c1a4f30370edb782d3666a
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 17package com.android.calendar; 18 19import static android.provider.Calendar.EVENT_BEGIN_TIME; 20import static android.provider.Calendar.EVENT_END_TIME; 21 22import android.accounts.Account; 23import android.app.Activity; 24import android.content.ContentResolver; 25import android.content.ContentUris; 26import android.content.Context; 27import android.content.Intent; 28import android.database.Cursor; 29import android.net.Uri; 30import android.os.AsyncTask; 31import android.os.Bundle; 32import android.provider.Calendar.Calendars; 33import android.provider.Calendar.Events; 34import android.text.TextUtils; 35import android.text.format.Time; 36import android.util.Log; 37 38import java.util.ArrayList; 39import java.util.LinkedList; 40import java.util.WeakHashMap; 41 42public class CalendarController { 43 private static final String TAG = "CalendarController"; 44 private static final String REFRESH_SELECTION = Calendars.SYNC_EVENTS + "=?"; 45 private static final String[] REFRESH_ARGS = new String[] { "1" }; 46 private static final String REFRESH_ORDER = Calendars._SYNC_ACCOUNT + "," 47 + Calendars._SYNC_ACCOUNT_TYPE; 48 49 private Context mContext; 50 51 private ArrayList<EventHandler> eventHandlers = new ArrayList<EventHandler>(5); 52 private LinkedList<EventHandler> mToBeRemovedEventHandlers = new LinkedList<EventHandler>(); 53 private boolean mDispatchInProgress; 54 55 private static WeakHashMap<Context, CalendarController> instances = 56 new WeakHashMap<Context, CalendarController>(); 57 58 private WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1); 59 60 private int mViewType = -1; 61 private int mDetailViewType = -1; 62 private int mPreviousViewType = -1; 63 private Time mTime = new Time(); 64 65 private AsyncQueryService mService; 66 67 /** 68 * One of the event types that are sent to or from the controller 69 */ 70 public interface EventType { 71 final long CREATE_EVENT = 1L; 72 final long VIEW_EVENT = 1L << 1; 73 final long EDIT_EVENT = 1L << 2; 74 final long DELETE_EVENT = 1L << 3; 75 76 final long GO_TO = 1L << 4; 77 78 final long LAUNCH_MANAGE_CALENDARS = 1L << 5; 79 final long LAUNCH_SETTINGS = 1L << 6; 80 } 81 82 /** 83 * One of the Agenda/Day/Week/Month view types 84 */ 85 public interface ViewType { 86 final int DETAIL = -1; 87 final int CURRENT = 0; 88 final int AGENDA = 1; 89 final int DAY = 2; 90 final int WEEK = 3; 91 final int MONTH = 4; 92 } 93 94 public static class EventInfo { 95 long eventType; // one of the EventType 96 int viewType; // one of the ViewType 97 long id; // event id 98 Time selectedTime; // the selected time in focus 99 Time startTime; // start of a range of time. 100 Time endTime; // end of a range of time. 101 int x; // x coordinate in the activity space 102 int y; // y coordinate in the activity space 103 } 104 105 // FRAG_TODO remove unneeded api's 106 public interface EventHandler { 107 long getSupportedEventTypes(); 108 void handleEvent(EventInfo event); 109 110 /** 111 * Returns the time in millis of the selected event in this view. 112 * @return the selected time in UTC milliseconds. 113 */ 114 long getSelectedTime(); 115 116 /** 117 * Changes the view to include the given time. 118 * @param time the desired time to view. 119 * @animate enable animation 120 */ 121 void goTo(Time time, boolean animate); 122 123 /** 124 * Changes the view to include today's date. 125 */ 126 void goToToday(); 127 128 /** 129 * This is called when the user wants to create a new event and returns 130 * true if the new event should default to an all-day event. 131 * @return true if the new event should be an all-day event. 132 */ 133 boolean getAllDay(); 134 135 /** 136 * TODO comment 137 */ 138 void eventsChanged(); 139 140 } 141 142 /** 143 * Creates and/or returns an instance of CalendarController associated with 144 * the supplied context. It is best to pass in the current Activity. 145 * 146 * @param context The activity if at all possible. 147 */ 148 public static CalendarController getInstance(Context context) { 149 synchronized (instances) { 150 CalendarController controller = instances.get(context); 151 if (controller == null) { 152 controller = new CalendarController(context); 153 instances.put(context, controller); 154 } 155 return controller; 156 } 157 } 158 159 private CalendarController(Context context) { 160 mContext = context; 161 mTime.setToNow(); 162 mDetailViewType = Utils.getSharedPreference(mContext, 163 CalendarPreferenceActivity.KEY_DETAILED_VIEW, 164 CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW); 165 mService = new AsyncQueryService(context) { 166 @Override 167 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 168 new RefreshInBackground().execute(cursor); 169 } 170 }; 171 } 172 173 /** 174 * Helper for sending New/View/Edit/Delete events 175 * 176 * @param sender object of the caller 177 * @param eventType one of {@link EventType} 178 * @param eventId event id 179 * @param startMillis start time 180 * @param endMillis end time 181 * @param x x coordinate in the activity space 182 * @param y y coordinate in the activity space 183 */ 184 public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis, 185 long endMillis, int x, int y) { 186 EventInfo info = new EventInfo(); 187 info.eventType = eventType; 188 info.id = eventId; 189 info.startTime = new Time(); 190 info.startTime.set(startMillis); 191 info.endTime = new Time(); 192 info.endTime.set(endMillis); 193 info.x = x; 194 info.y = y; 195 this.sendEvent(sender, info); 196 } 197 198 /** 199 * Helper for sending non-calendar-event events 200 * 201 * @param sender object of the caller 202 * @param eventType one of {@link EventType} 203 * @param start start time 204 * @param end end time 205 * @param eventId event id 206 * @param viewType {@link ViewType} 207 */ 208 public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId, 209 int viewType) { 210 EventInfo info = new EventInfo(); 211 info.eventType = eventType; 212 info.startTime = start; 213 info.endTime = end; 214 info.id = eventId; 215 info.viewType = viewType; 216 this.sendEvent(sender, info); 217 } 218 219 public void sendEvent(Object sender, final EventInfo event) { 220 // TODO Throw exception on invalid events 221 222 Log.d(TAG, eventInfoToString(event)); 223 224 Long filteredTypes = filters.get(sender); 225 if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) { 226 // Suppress event per filter 227 Log.d(TAG, "Event suppressed"); 228 return; 229 } 230 231 // Launch Calendars, and Settings 232 if (event.eventType == EventType.LAUNCH_MANAGE_CALENDARS) { 233 launchManageCalendars(); 234 return; 235 } else if (event.eventType == EventType.LAUNCH_SETTINGS) { 236 launchSettings(); 237 return; 238 } 239 240 if (event.startTime != null && event.startTime.toMillis(false) != 0) { 241 mTime.set(event.startTime); 242 } 243 event.startTime = mTime; 244 245 // Create/View/Edit/Delete Event 246 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false); 247 if (event.eventType == EventType.CREATE_EVENT) { 248 launchCreateEvent(event.startTime.toMillis(false), endTime); 249 return; 250 } else if (event.eventType == EventType.VIEW_EVENT) { 251 launchViewEvent(event.id, event.startTime.toMillis(false), endTime); 252 return; 253 } else if (event.eventType == EventType.EDIT_EVENT) { 254 launchEditEvent(event.id, event.startTime.toMillis(false), endTime); 255 return; 256 } else if (event.eventType == EventType.DELETE_EVENT) { 257 launchDeleteEvent(event.id, event.startTime.toMillis(false), endTime); 258 return; 259 } 260 261 mPreviousViewType = mViewType; 262 263 // Fix up view if not specified 264 if (event.viewType == ViewType.DETAIL) { 265 event.viewType = mDetailViewType; 266 mViewType = mDetailViewType; 267 } else if (event.viewType == ViewType.CURRENT) { 268 event.viewType = mViewType; 269 } else { 270 mViewType = event.viewType; 271 272 if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY) { 273 mDetailViewType = mViewType; 274 } 275 } 276 277 synchronized (this) { 278 mDispatchInProgress = true; 279 280 // Dispatch to event handler(s) 281 for (int i = 0; i < eventHandlers.size(); ++i) { 282 EventHandler eventHandler = eventHandlers.get(i); 283 if (eventHandler != null 284 && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) { 285 if (mToBeRemovedEventHandlers.contains(eventHandler)) { 286 continue; 287 } 288 eventHandler.handleEvent(event); 289 } 290 } 291 292 // Deregister removed handlers 293 if (mToBeRemovedEventHandlers.size() > 0) { 294 for (EventHandler zombie : mToBeRemovedEventHandlers) { 295 eventHandlers.remove(zombie); 296 } 297 mToBeRemovedEventHandlers.clear(); 298 } 299 mDispatchInProgress = false; 300 } 301 } 302 303 public void registerEventHandler(EventHandler eventHandler) { 304 synchronized (this) { 305 eventHandlers.add(eventHandler); 306 } 307 } 308 309 public void deregisterEventHandler(EventHandler eventHandler) { 310 synchronized (this) { 311 if (mDispatchInProgress) { 312 // To avoid ConcurrencyException, stash away the event handler for now. 313 mToBeRemovedEventHandlers.add(eventHandler); 314 } else { 315 eventHandlers.remove(eventHandler); 316 } 317 } 318 } 319 320 // FRAG_TODO doesn't work yet 321 public void filterBroadcasts(Object sender, long eventTypes) { 322 filters.put(sender, eventTypes); 323 } 324 325 /** 326 * @return the time that this controller is currently pointed at 327 */ 328 public long getTime() { 329 return mTime.toMillis(false); 330 } 331 332 public int getViewType() { 333 return mViewType; 334 } 335 336 public int getPreviousViewType() { 337 return mPreviousViewType; 338 } 339 340 private void launchManageCalendars() { 341 Intent intent = new Intent(Intent.ACTION_VIEW); 342 intent.setClass(mContext, SelectCalendarsActivity.class); 343 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); 344 mContext.startActivity(intent); 345 } 346 347 private void launchSettings() { 348 Intent intent = new Intent(Intent.ACTION_VIEW); 349 intent.setClassName(mContext, CalendarPreferenceActivity.class.getName()); 350 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP); 351 mContext.startActivity(intent); 352 } 353 354 private void launchCreateEvent(long startMillis, long endMillis) { 355 Intent intent = new Intent(Intent.ACTION_VIEW); 356 intent.setClassName(mContext, EditEventActivity.class.getName()); 357 intent.putExtra(EVENT_BEGIN_TIME, startMillis); 358 intent.putExtra(EVENT_END_TIME, endMillis); 359 mContext.startActivity(intent); 360 } 361 362 private void launchViewEvent(long eventId, long startMillis, long endMillis) { 363 Intent intent = new Intent(Intent.ACTION_VIEW); 364 Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); 365 intent.setData(eventUri); 366 intent.setClassName(mContext, EventInfoActivity.class.getName()); 367 intent.putExtra(EVENT_BEGIN_TIME, startMillis); 368 intent.putExtra(EVENT_END_TIME, endMillis); 369 mContext.startActivity(intent); 370 } 371 372 private void launchEditEvent(long eventId, long startMillis, long endMillis) { 373 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId); 374 Intent intent = new Intent(Intent.ACTION_EDIT, uri); 375 intent.putExtra(EVENT_BEGIN_TIME, startMillis); 376 intent.putExtra(EVENT_END_TIME, endMillis); 377 intent.setClass(mContext, EditEventActivity.class); 378 mContext.startActivity(intent); 379 } 380 381 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) { 382 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1); 383 } 384 385 private void launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis, 386 long endMillis, int deleteWhich) { 387 DeleteEventHelper deleteEventHelper = new DeleteEventHelper(mContext, parentActivity, 388 parentActivity != null /* exit when done */); 389 deleteEventHelper.delete(startMillis, endMillis, eventId, deleteWhich); 390 } 391 392 public void refreshCalendars() { 393 Log.d(TAG, "RefreshCalendars starting"); 394 // get the account, url, and current sync state 395 mService.startQuery(mService.getNextToken(), null, Calendars.CONTENT_URI, 396 new String[] {Calendars._ID, // 0 397 Calendars._SYNC_ACCOUNT, // 1 398 Calendars._SYNC_ACCOUNT_TYPE, // 2 399 }, 400 REFRESH_SELECTION, REFRESH_ARGS, REFRESH_ORDER); 401 402 } 403 404 private class RefreshInBackground extends AsyncTask<Cursor, Integer, Integer> { 405 /* (non-Javadoc) 406 * @see android.os.AsyncTask#doInBackground(Params[]) 407 */ 408 @Override 409 protected Integer doInBackground(Cursor... params) { 410 if (params.length != 1) { 411 return null; 412 } 413 Cursor cursor = params[0]; 414 if (cursor == null) { 415 return null; 416 } 417 418 String previousAccount = null; 419 String previousType = null; 420 Log.d(TAG, "Refreshing " + cursor.getCount() + " calendars"); 421 try { 422 while (cursor.moveToNext()) { 423 Account account = null; 424 String accountName = cursor.getString(1); 425 String accountType = cursor.getString(2); 426 // Only need to schedule one sync per account and they're 427 // ordered by account,type 428 if (TextUtils.equals(accountName, previousAccount) && 429 TextUtils.equals(accountType, previousType)) { 430 continue; 431 } 432 previousAccount = accountName; 433 previousType = accountType; 434 account = new Account(accountName, accountType); 435 scheduleSync(account, false /* two-way sync */, null); 436 } 437 } finally { 438 cursor.close(); 439 } 440 return null; 441 } 442 443 /** 444 * Schedule a calendar sync for the account. 445 * @param account the account for which to schedule a sync 446 * @param uploadChangesOnly if set, specify that the sync should only send 447 * up local changes. This is typically used for a local sync, a user override of 448 * too many deletions, or a sync after a calendar is unselected. 449 * @param url the url feed for the calendar to sync (may be null, in which case a poll of 450 * all feeds is done.) 451 */ 452 void scheduleSync(Account account, boolean uploadChangesOnly, String url) { 453 Bundle extras = new Bundle(); 454 if (uploadChangesOnly) { 455 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly); 456 } 457 if (url != null) { 458 extras.putString("feed", url); 459 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 460 } 461 ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras); 462 } 463 } 464 465 private String eventInfoToString(EventInfo eventInfo) { 466 String tmp = "Unknown"; 467 468 StringBuilder builder = new StringBuilder(); 469 if ((eventInfo.eventType & EventType.GO_TO) != 0) { 470 tmp = "Go to time/event"; 471 } else if ((eventInfo.eventType & EventType.CREATE_EVENT) != 0) { 472 tmp = "New event"; 473 } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) { 474 tmp = "View event"; 475 } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) { 476 tmp = "Edit event"; 477 } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) { 478 tmp = "Delete event"; 479 } else if ((eventInfo.eventType & EventType.LAUNCH_MANAGE_CALENDARS) != 0) { 480 tmp = "Launch select calendar"; 481 } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) { 482 tmp = "Launch settings"; 483 } 484 builder.append(tmp); 485 builder.append(": id="); 486 builder.append(eventInfo.id); 487 builder.append(", selected="); 488 builder.append(eventInfo.selectedTime); 489 builder.append(", start="); 490 builder.append(eventInfo.startTime); 491 builder.append(", end="); 492 builder.append(eventInfo.endTime); 493 builder.append(", viewType="); 494 builder.append(eventInfo.viewType); 495 builder.append(", x="); 496 builder.append(eventInfo.x); 497 builder.append(", y="); 498 builder.append(eventInfo.y); 499 return builder.toString(); 500 } 501} 502