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