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