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