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