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