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