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