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