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