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