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