blob: fd144bc46f6fe2c49e7bcf258b0d11de613ae122 [file] [log] [blame]
Michael Chan49701592010-06-30 11:04:03 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calendar;
18
RoboErika27a8862011-06-23 15:26:23 -070019import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
20import static android.provider.CalendarContract.EXTRA_EVENT_END_TIME;
Michael Chan9e89dca2010-07-13 17:56:09 -070021
Erik5f620792010-10-27 12:42:01 -070022import com.android.calendar.event.EditEventActivity;
Michael Chan2aeb8d92011-07-10 13:32:09 -070023import com.android.calendar.selectcalendars.SelectVisibleCalendarsActivity;
Erik5f620792010-10-27 12:42:01 -070024
Erikba1b94a2010-07-20 17:50:50 -070025import android.accounts.Account;
Michael Chan83b0fe32010-07-08 16:46:26 -070026import android.app.Activity;
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -070027import android.app.SearchManager;
28import android.app.SearchableInfo;
29import android.content.ComponentName;
Erikba1b94a2010-07-20 17:50:50 -070030import android.content.ContentResolver;
Michael Chan9e89dca2010-07-13 17:56:09 -070031import android.content.ContentUris;
Michael Chanab29d9e2010-07-21 06:08:47 -070032import android.content.Context;
Michael Chan9e89dca2010-07-13 17:56:09 -070033import android.content.Intent;
Erikba1b94a2010-07-20 17:50:50 -070034import android.database.Cursor;
Michael Chan9e89dca2010-07-13 17:56:09 -070035import android.net.Uri;
Erik7116ba42010-07-23 10:13:36 -070036import android.os.AsyncTask;
Erikba1b94a2010-07-20 17:50:50 -070037import android.os.Bundle;
RoboErika7c03902011-06-14 11:06:44 -070038import android.provider.CalendarContract.Calendars;
39import android.provider.CalendarContract.Events;
Erikba1b94a2010-07-20 17:50:50 -070040import android.text.TextUtils;
Michael Chan49701592010-06-30 11:04:03 -070041import android.text.format.Time;
Michael Chan83b0fe32010-07-08 16:46:26 -070042import android.util.Log;
RoboErik3864be02011-07-25 15:56:50 -070043import android.util.Pair;
Michael Chan49701592010-06-30 11:04:03 -070044
Erik3f348f32010-08-10 13:17:19 -070045import java.util.Iterator;
46import java.util.LinkedHashMap;
Michael Chanab29d9e2010-07-21 06:08:47 -070047import java.util.LinkedList;
Erik3f348f32010-08-10 13:17:19 -070048import java.util.Map.Entry;
Michael Chan83b0fe32010-07-08 16:46:26 -070049import java.util.WeakHashMap;
50
Erik25251192010-07-12 15:30:14 -070051public class CalendarController {
Michael Chanf0868f62011-01-11 19:37:35 -080052 private static final boolean DEBUG = false;
Michael Chan83b0fe32010-07-08 16:46:26 -070053 private static final String TAG = "CalendarController";
Erikba1b94a2010-07-20 17:50:50 -070054 private static final String REFRESH_SELECTION = Calendars.SYNC_EVENTS + "=?";
55 private static final String[] REFRESH_ARGS = new String[] { "1" };
RoboErik0e1e6242011-05-05 15:25:02 -070056 private static final String REFRESH_ORDER = Calendars.ACCOUNT_NAME + ","
57 + Calendars.ACCOUNT_TYPE;
Michael Chan83b0fe32010-07-08 16:46:26 -070058
Erika7694ee2010-12-07 15:46:18 -080059 public static final String EVENT_EDIT_ON_LAUNCH = "editMode";
60
Erik981874e2010-10-05 16:52:52 -070061 public static final int MIN_CALENDAR_YEAR = 1970;
62 public static final int MAX_CALENDAR_YEAR = 2036;
63 public static final int MIN_CALENDAR_WEEK = 0;
64 public static final int MAX_CALENDAR_WEEK = 3497; // weeks between 1/1/1970 and 1/1/2037
65
Isaac Katzenelson60f01c22011-06-03 15:42:01 -070066 public static final String EVENT_ATTENDEE_RESPONSE = "attendeeResponse";
Michael Chandeced892010-12-10 15:59:35 -080067 public static final int ATTENDEE_NO_RESPONSE = -1;
68
Michael Chanab29d9e2010-07-21 06:08:47 -070069 private Context mContext;
70
Erik3f348f32010-08-10 13:17:19 -070071 // This uses a LinkedHashMap so that we can replace fragments based on the
72 // view id they are being expanded into since we can't guarantee a reference
73 // to the handler will be findable
74 private LinkedHashMap<Integer,EventHandler> eventHandlers =
75 new LinkedHashMap<Integer,EventHandler>(5);
76 private LinkedList<Integer> mToBeRemovedEventHandlers = new LinkedList<Integer>();
Erikcb811892010-09-28 13:44:19 -070077 private LinkedHashMap<Integer, EventHandler> mToBeAddedEventHandlers = new LinkedHashMap<
78 Integer, EventHandler>();
RoboErik3864be02011-07-25 15:56:50 -070079 private Pair<Integer, EventHandler> mFirstEventHandler;
80 private Pair<Integer, EventHandler> mToBeAddedFirstEventHandler;
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -070081 private volatile int mDispatchInProgressCounter = 0;
Michael Chanab29d9e2010-07-21 06:08:47 -070082
Michael Chan0558def2010-07-22 21:30:32 -070083 private static WeakHashMap<Context, CalendarController> instances =
84 new WeakHashMap<Context, CalendarController>();
85
Michael Chan83b0fe32010-07-08 16:46:26 -070086 private WeakHashMap<Object, Long> filters = new WeakHashMap<Object, Long>(1);
87
Michael Chan3458a172010-07-13 17:58:21 -070088 private int mViewType = -1;
Michael Chan85e55092010-07-27 15:18:40 -070089 private int mDetailViewType = -1;
Michael Chanab29d9e2010-07-21 06:08:47 -070090 private int mPreviousViewType = -1;
Erik7b92da22010-09-23 14:55:22 -070091 private long mEventId = -1;
Michael Chan3458a172010-07-13 17:58:21 -070092 private Time mTime = new Time();
93
Erikba1b94a2010-07-20 17:50:50 -070094 private AsyncQueryService mService;
95
RoboErik4679f602011-03-24 15:39:40 -070096 private Runnable mUpdateTimezone = new Runnable() {
97 @Override
98 public void run() {
99 mTime.switchTimezone(Utils.getTimeZone(mContext, this));
100 }
101 };
102
Michael Chan49701592010-06-30 11:04:03 -0700103 /**
Michael Chan83b0fe32010-07-08 16:46:26 -0700104 * One of the event types that are sent to or from the controller
Michael Chan49701592010-06-30 11:04:03 -0700105 */
Erik25251192010-07-12 15:30:14 -0700106 public interface EventType {
Michael Chan9e89dca2010-07-13 17:56:09 -0700107 final long CREATE_EVENT = 1L;
Michael Chandeced892010-12-10 15:59:35 -0800108
109 // Simple view of an event
Michael Chan83b0fe32010-07-08 16:46:26 -0700110 final long VIEW_EVENT = 1L << 1;
Michael Chandeced892010-12-10 15:59:35 -0800111
112 // Full detail view in read only mode
113 final long VIEW_EVENT_DETAILS = 1L << 2;
114
Erika7694ee2010-12-07 15:46:18 -0800115 // full detail view in edit mode
Michael Chandeced892010-12-10 15:59:35 -0800116 final long EDIT_EVENT = 1L << 3;
Michael Chan83b0fe32010-07-08 16:46:26 -0700117
Michael Chandeced892010-12-10 15:59:35 -0800118 final long DELETE_EVENT = 1L << 4;
Michael Chan83b0fe32010-07-08 16:46:26 -0700119
Michael Chandeced892010-12-10 15:59:35 -0800120 final long GO_TO = 1L << 5;
Erik954c8712010-08-06 10:12:34 -0700121
Michael Chandeced892010-12-10 15:59:35 -0800122 final long LAUNCH_SETTINGS = 1L << 6;
Mason Tang4003d1c2010-08-17 13:50:45 -0700123
Michael Chandeced892010-12-10 15:59:35 -0800124 final long EVENTS_CHANGED = 1L << 7;
125
126 final long SEARCH = 1L << 8;
Erik7b92da22010-09-23 14:55:22 -0700127
128 // User has pressed the home key
Michael Chandeced892010-12-10 15:59:35 -0800129 final long USER_HOME = 1L << 9;
Erikeaafa2b2010-12-23 14:30:37 -0800130
131 // date range has changed, update the title
132 final long UPDATE_TITLE = 1L << 10;
Michael Chan2aeb8d92011-07-10 13:32:09 -0700133
134 // select which calendars to display
135 final long LAUNCH_SELECT_VISIBLE_CALENDARS = 1L << 11;
Michael Chan83b0fe32010-07-08 16:46:26 -0700136 }
Michael Chan49701592010-06-30 11:04:03 -0700137
138 /**
Michael Chan83b0fe32010-07-08 16:46:26 -0700139 * One of the Agenda/Day/Week/Month view types
Michael Chan49701592010-06-30 11:04:03 -0700140 */
Erik25251192010-07-12 15:30:14 -0700141 public interface ViewType {
Michael Chand6734db2010-07-22 00:48:08 -0700142 final int DETAIL = -1;
Michael Chan3458a172010-07-13 17:58:21 -0700143 final int CURRENT = 0;
144 final int AGENDA = 1;
145 final int DAY = 2;
146 final int WEEK = 3;
147 final int MONTH = 4;
Erikdd95df52010-08-27 09:31:18 -0700148 final int EDIT = 5;
Michael Chan83b0fe32010-07-08 16:46:26 -0700149 }
150
Erik25251192010-07-12 15:30:14 -0700151 public static class EventInfo {
Mason Tang00b8c1a2010-08-23 12:02:00 -0700152 public long eventType; // one of the EventType
153 public int viewType; // one of the ViewType
154 public long id; // event id
155 public Time selectedTime; // the selected time in focus
156 public Time startTime; // start of a range of time.
157 public Time endTime; // end of a range of time.
158 public int x; // x coordinate in the activity space
159 public int y; // y coordinate in the activity space
160 public String query; // query for a user search
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700161 public ComponentName componentName; // used in combination with query
Michael Chan46a8b112010-12-14 16:36:27 -0800162
163 /**
164 * For EventType.VIEW_EVENT:
165 * It is the default attendee response.
166 * Set to {@link #ATTENDEE_NO_RESPONSE}, Calendar.ATTENDEE_STATUS_ACCEPTED,
167 * Calendar.ATTENDEE_STATUS_DECLINED, or Calendar.ATTENDEE_STATUS_TENTATIVE.
168 * <p>
169 * For EventType.GO_TO:
170 * Set to {@link #EXTRA_GOTO_TIME} to go to the specified date/time.
171 * Set to {@link #EXTRA_GOTO_DATE} to consider the date but ignore the time.
172 */
173 public long extraLong;
Michael Chan83b0fe32010-07-08 16:46:26 -0700174 }
175
Michael Chan46a8b112010-12-14 16:36:27 -0800176 /**
177 * Pass to the ExtraLong parameter for EventType.GO_TO to signal the time
178 * can be ignored
179 */
180 public static final long EXTRA_GOTO_DATE = 1;
181 public static final long EXTRA_GOTO_TIME = -1;
182
Erik25251192010-07-12 15:30:14 -0700183 public interface EventHandler {
Michael Chan83b0fe32010-07-08 16:46:26 -0700184 long getSupportedEventTypes();
185 void handleEvent(EventInfo event);
186
187 /**
Erik954c8712010-08-06 10:12:34 -0700188 * This notifies the handler that the database has changed and it should
189 * update its view.
Michael Chan83b0fe32010-07-08 16:46:26 -0700190 */
191 void eventsChanged();
Michael Chan83b0fe32010-07-08 16:46:26 -0700192 }
193
Michael Chan0558def2010-07-22 21:30:32 -0700194 /**
195 * Creates and/or returns an instance of CalendarController associated with
196 * the supplied context. It is best to pass in the current Activity.
197 *
198 * @param context The activity if at all possible.
199 */
200 public static CalendarController getInstance(Context context) {
201 synchronized (instances) {
202 CalendarController controller = instances.get(context);
203 if (controller == null) {
204 controller = new CalendarController(context);
205 instances.put(context, controller);
206 }
207 return controller;
208 }
209 }
210
Eriked61b482010-08-13 13:59:50 -0700211 /**
212 * Removes an instance when it is no longer needed. This should be called in
213 * an activity's onDestroy method.
214 *
215 * @param context The activity used to create the controller
216 */
217 public static void removeInstance(Context context) {
218 instances.remove(context);
219 }
220
Michael Chan0558def2010-07-22 21:30:32 -0700221 private CalendarController(Context context) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700222 mContext = context;
RoboErik4679f602011-03-24 15:39:40 -0700223 mUpdateTimezone.run();
Michael Chan3458a172010-07-13 17:58:21 -0700224 mTime.setToNow();
Michael Chan85e55092010-07-27 15:18:40 -0700225 mDetailViewType = Utils.getSharedPreference(mContext,
Daisuke Miyakawa4b441bd2010-09-16 14:55:36 -0700226 GeneralPreferences.KEY_DETAILED_VIEW,
227 GeneralPreferences.DEFAULT_DETAILED_VIEW);
Erikba1b94a2010-07-20 17:50:50 -0700228 mService = new AsyncQueryService(context) {
229 @Override
230 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
Erik7116ba42010-07-23 10:13:36 -0700231 new RefreshInBackground().execute(cursor);
Erikba1b94a2010-07-20 17:50:50 -0700232 }
233 };
Michael Chan83b0fe32010-07-08 16:46:26 -0700234 }
Michael Chan49701592010-06-30 11:04:03 -0700235
Michael Chandeced892010-12-10 15:59:35 -0800236 public void sendEventRelatedEvent(Object sender, long eventType, long eventId, long startMillis,
RoboErik87f993f2011-02-03 17:47:20 -0800237 long endMillis, int x, int y, long selectedMillis) {
Michael Chanf0868f62011-01-11 19:37:35 -0800238 sendEventRelatedEventWithResponse(sender, eventType, eventId, startMillis, endMillis, x, y,
RoboErik87f993f2011-02-03 17:47:20 -0800239 CalendarController.ATTENDEE_NO_RESPONSE, selectedMillis);
Michael Chandeced892010-12-10 15:59:35 -0800240 }
241
Michael Chan49701592010-06-30 11:04:03 -0700242 /**
Michael Chan83b0fe32010-07-08 16:46:26 -0700243 * Helper for sending New/View/Edit/Delete events
244 *
245 * @param sender object of the caller
246 * @param eventType one of {@link EventType}
247 * @param eventId event id
Michael Chan3458a172010-07-13 17:58:21 -0700248 * @param startMillis start time
249 * @param endMillis end time
Michael Chan83b0fe32010-07-08 16:46:26 -0700250 * @param x x coordinate in the activity space
251 * @param y y coordinate in the activity space
Michael Chan46a8b112010-12-14 16:36:27 -0800252 * @param extraLong default response value for the "simple event view". Use
253 * CalendarController.ATTENDEE_NO_RESPONSE for no response.
RoboErik87f993f2011-02-03 17:47:20 -0800254 * @param selectedMillis The time to specify as selected
Michael Chan49701592010-06-30 11:04:03 -0700255 */
Michael Chanf0868f62011-01-11 19:37:35 -0800256 public void sendEventRelatedEventWithResponse(Object sender, long eventType, long eventId,
RoboErik87f993f2011-02-03 17:47:20 -0800257 long startMillis, long endMillis, int x, int y, long extraLong, long selectedMillis) {
Michael Chan83b0fe32010-07-08 16:46:26 -0700258 EventInfo info = new EventInfo();
259 info.eventType = eventType;
Erika7694ee2010-12-07 15:46:18 -0800260 if (eventType == EventType.EDIT_EVENT || eventType == EventType.VIEW_EVENT_DETAILS) {
Erik5f620792010-10-27 12:42:01 -0700261 info.viewType = ViewType.CURRENT;
Erikdd95df52010-08-27 09:31:18 -0700262 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700263 info.id = eventId;
RoboErik4679f602011-03-24 15:39:40 -0700264 info.startTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
Michael Chan9e89dca2010-07-13 17:56:09 -0700265 info.startTime.set(startMillis);
RoboErik87f993f2011-02-03 17:47:20 -0800266 if (selectedMillis != -1) {
RoboErik4679f602011-03-24 15:39:40 -0700267 info.selectedTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
RoboErik87f993f2011-02-03 17:47:20 -0800268 info.selectedTime.set(selectedMillis);
269 } else {
270 info.selectedTime = info.startTime;
271 }
RoboErik4679f602011-03-24 15:39:40 -0700272 info.endTime = new Time(Utils.getTimeZone(mContext, mUpdateTimezone));
Michael Chan9e89dca2010-07-13 17:56:09 -0700273 info.endTime.set(endMillis);
Michael Chan83b0fe32010-07-08 16:46:26 -0700274 info.x = x;
275 info.y = y;
Michael Chandeced892010-12-10 15:59:35 -0800276 info.extraLong = extraLong;
Michael Chan83b0fe32010-07-08 16:46:26 -0700277 this.sendEvent(sender, info);
278 }
Michael Chan49701592010-06-30 11:04:03 -0700279
280 /**
Michael Chan83b0fe32010-07-08 16:46:26 -0700281 * Helper for sending non-calendar-event events
282 *
283 * @param sender object of the caller
284 * @param eventType one of {@link EventType}
Michael Chan83b0fe32010-07-08 16:46:26 -0700285 * @param start start time
286 * @param end end time
Michael Chand6734db2010-07-22 00:48:08 -0700287 * @param eventId event id
Michael Chan83b0fe32010-07-08 16:46:26 -0700288 * @param viewType {@link ViewType}
Michael Chan49701592010-06-30 11:04:03 -0700289 */
Erik25251192010-07-12 15:30:14 -0700290 public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
Michael Chan3458a172010-07-13 17:58:21 -0700291 int viewType) {
Michael Chanf0868f62011-01-11 19:37:35 -0800292 sendEvent(sender, eventType, start, end, start, eventId, viewType, EXTRA_GOTO_TIME, null,
293 null);
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700294 }
295
296 /**
Michael Chan46a8b112010-12-14 16:36:27 -0800297 * sendEvent() variant with extraLong, search query, and search component name.
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700298 */
299 public void sendEvent(Object sender, long eventType, Time start, Time end, long eventId,
Michael Chan46a8b112010-12-14 16:36:27 -0800300 int viewType, long extraLong, String query, ComponentName componentName) {
Michael Chanf0868f62011-01-11 19:37:35 -0800301 sendEvent(sender, eventType, start, end, start, eventId, viewType, extraLong, query,
302 componentName);
303 }
304
305 public void sendEvent(Object sender, long eventType, Time start, Time end, Time selected,
306 long eventId, int viewType, long extraLong, String query, ComponentName componentName) {
Michael Chan83b0fe32010-07-08 16:46:26 -0700307 EventInfo info = new EventInfo();
308 info.eventType = eventType;
309 info.startTime = start;
Michael Chanf0868f62011-01-11 19:37:35 -0800310 info.selectedTime = selected;
Michael Chan83b0fe32010-07-08 16:46:26 -0700311 info.endTime = end;
312 info.id = eventId;
313 info.viewType = viewType;
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700314 info.query = query;
315 info.componentName = componentName;
Michael Chan46a8b112010-12-14 16:36:27 -0800316 info.extraLong = extraLong;
Michael Chan83b0fe32010-07-08 16:46:26 -0700317 this.sendEvent(sender, info);
318 }
319
Erik25251192010-07-12 15:30:14 -0700320 public void sendEvent(Object sender, final EventInfo event) {
Michael Chan83b0fe32010-07-08 16:46:26 -0700321 // TODO Throw exception on invalid events
322
Erik3f348f32010-08-10 13:17:19 -0700323 if (DEBUG) {
324 Log.d(TAG, eventInfoToString(event));
325 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700326
327 Long filteredTypes = filters.get(sender);
328 if (filteredTypes != null && (filteredTypes.longValue() & event.eventType) != 0) {
329 // Suppress event per filter
Erik3f348f32010-08-10 13:17:19 -0700330 if (DEBUG) {
331 Log.d(TAG, "Event suppressed");
332 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700333 return;
334 }
335
Michael Chanab29d9e2010-07-21 06:08:47 -0700336 mPreviousViewType = mViewType;
Michael Chan83b0fe32010-07-08 16:46:26 -0700337
Michael Chan3458a172010-07-13 17:58:21 -0700338 // Fix up view if not specified
Michael Chand6734db2010-07-22 00:48:08 -0700339 if (event.viewType == ViewType.DETAIL) {
Michael Chan85e55092010-07-27 15:18:40 -0700340 event.viewType = mDetailViewType;
341 mViewType = mDetailViewType;
Michael Chand6734db2010-07-22 00:48:08 -0700342 } else if (event.viewType == ViewType.CURRENT) {
Michael Chan3458a172010-07-13 17:58:21 -0700343 event.viewType = mViewType;
Michael Chanf0868f62011-01-11 19:37:35 -0800344 } else if (event.viewType != ViewType.EDIT) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700345 mViewType = event.viewType;
Michael Chan85e55092010-07-27 15:18:40 -0700346
Erik63cd0532011-01-26 14:16:03 -0800347 if (event.viewType == ViewType.AGENDA || event.viewType == ViewType.DAY
348 || (Utils.getAllowWeekForDetailView() && event.viewType == ViewType.WEEK)) {
Michael Chan85e55092010-07-27 15:18:40 -0700349 mDetailViewType = mViewType;
350 }
Michael Chan3458a172010-07-13 17:58:21 -0700351 }
352
Michael Chanf0868f62011-01-11 19:37:35 -0800353 if (DEBUG) {
354 Log.e(TAG, "vvvvvvvvvvvvvvv");
355 Log.e(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString()));
356 Log.e(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString()));
357 Log.e(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
358 Log.e(TAG, "mTime " + (mTime == null ? "null" : mTime.toString()));
Mason Tang4003d1c2010-08-17 13:50:45 -0700359 }
Michael Chanf0868f62011-01-11 19:37:35 -0800360
361 long startMillis = 0;
362 if (event.startTime != null) {
363 startMillis = event.startTime.toMillis(false);
364 }
365
366 // Set mTime if selectedTime is set
367 if (event.selectedTime != null && event.selectedTime.toMillis(false) != 0) {
368 mTime.set(event.selectedTime);
369 } else {
370 if (startMillis != 0) {
371 // selectedTime is not set so set mTime to startTime iff it is not
372 // within start and end times
373 long mtimeMillis = mTime.toMillis(false);
374 if (mtimeMillis < startMillis
375 || (event.endTime != null && mtimeMillis > event.endTime.toMillis(false))) {
376 mTime.set(event.startTime);
377 }
378 }
379 event.selectedTime = mTime;
380 }
381
382 // Fix up start time if not specified
383 if (startMillis == 0) {
384 event.startTime = mTime;
385 }
386 if (DEBUG) {
387 Log.e(TAG, "Start " + (event.startTime == null ? "null" : event.startTime.toString()));
388 Log.e(TAG, "End " + (event.endTime == null ? "null" : event.endTime.toString()));
389 Log.e(TAG, "Select " + (event.selectedTime == null ? "null" : event.selectedTime.toString()));
390 Log.e(TAG, "mTime " + (mTime == null ? "null" : mTime.toString()));
391 Log.e(TAG, "^^^^^^^^^^^^^^^");
392 }
Mason Tang4003d1c2010-08-17 13:50:45 -0700393
Erik7b92da22010-09-23 14:55:22 -0700394 // Store the eventId if we're entering edit event
Erika7694ee2010-12-07 15:46:18 -0800395 if ((event.eventType
396 & (EventType.CREATE_EVENT | EventType.EDIT_EVENT | EventType.VIEW_EVENT_DETAILS))
397 != 0) {
Erik7b92da22010-09-23 14:55:22 -0700398 if (event.id > 0) {
399 mEventId = event.id;
400 } else {
401 mEventId = -1;
402 }
403 }
404
Mason Tang4003d1c2010-08-17 13:50:45 -0700405 boolean handled = false;
Michael Chanab29d9e2010-07-21 06:08:47 -0700406 synchronized (this) {
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700407 mDispatchInProgressCounter ++;
Michael Chan3458a172010-07-13 17:58:21 -0700408
Erik3f348f32010-08-10 13:17:19 -0700409 if (DEBUG) {
410 Log.d(TAG, "sendEvent: Dispatching to " + eventHandlers.size() + " handlers");
411 }
Michael Chanab29d9e2010-07-21 06:08:47 -0700412 // Dispatch to event handler(s)
RoboErik3864be02011-07-25 15:56:50 -0700413 if (mFirstEventHandler != null) {
414 // Handle the 'first' one before handling the others
415 EventHandler handler = mFirstEventHandler.second;
416 if (handler != null && (handler.getSupportedEventTypes() & event.eventType) != 0
417 && !mToBeRemovedEventHandlers.contains(mFirstEventHandler.first)) {
418 handler.handleEvent(event);
419 handled = true;
420 }
421 }
Erik3f348f32010-08-10 13:17:19 -0700422 for (Iterator<Entry<Integer, EventHandler>> handlers =
423 eventHandlers.entrySet().iterator(); handlers.hasNext();) {
424 Entry<Integer, EventHandler> entry = handlers.next();
425 int key = entry.getKey();
RoboErik3864be02011-07-25 15:56:50 -0700426 if (mFirstEventHandler != null && key == mFirstEventHandler.first) {
427 // If this was the 'first' handler it was already handled
428 continue;
429 }
Erik3f348f32010-08-10 13:17:19 -0700430 EventHandler eventHandler = entry.getValue();
Michael Chanab29d9e2010-07-21 06:08:47 -0700431 if (eventHandler != null
432 && (eventHandler.getSupportedEventTypes() & event.eventType) != 0) {
Erik3f348f32010-08-10 13:17:19 -0700433 if (mToBeRemovedEventHandlers.contains(key)) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700434 continue;
435 }
436 eventHandler.handleEvent(event);
Mason Tang4003d1c2010-08-17 13:50:45 -0700437 handled = true;
Michael Chan83b0fe32010-07-08 16:46:26 -0700438 }
439 }
Michael Chanab29d9e2010-07-21 06:08:47 -0700440
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700441 mDispatchInProgressCounter --;
Erikcb811892010-09-28 13:44:19 -0700442
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700443 if (mDispatchInProgressCounter == 0) {
444
445 // Deregister removed handlers
446 if (mToBeRemovedEventHandlers.size() > 0) {
447 for (Integer zombie : mToBeRemovedEventHandlers) {
448 eventHandlers.remove(zombie);
RoboErik3864be02011-07-25 15:56:50 -0700449 if (mFirstEventHandler != null && zombie.equals(mFirstEventHandler.first)) {
450 mFirstEventHandler = null;
451 }
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700452 }
453 mToBeRemovedEventHandlers.clear();
Michael Chanab29d9e2010-07-21 06:08:47 -0700454 }
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700455 // Add new handlers
RoboErik3864be02011-07-25 15:56:50 -0700456 if (mToBeAddedFirstEventHandler != null) {
457 mFirstEventHandler = mToBeAddedFirstEventHandler;
458 mToBeAddedFirstEventHandler = null;
459 }
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700460 if (mToBeAddedEventHandlers.size() > 0) {
461 for (Entry<Integer, EventHandler> food : mToBeAddedEventHandlers.entrySet()) {
462 eventHandlers.put(food.getKey(), food.getValue());
463 }
Erikcb811892010-09-28 13:44:19 -0700464 }
465 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700466 }
Mason Tang4003d1c2010-08-17 13:50:45 -0700467
468 if (!handled) {
Erik6b858fc2010-09-15 18:59:04 -0700469 // Launch Settings
470 if (event.eventType == EventType.LAUNCH_SETTINGS) {
Mason Tang4003d1c2010-08-17 13:50:45 -0700471 launchSettings();
472 return;
473 }
474
Michael Chan2aeb8d92011-07-10 13:32:09 -0700475 // Launch Calendar Visible Selector
476 if (event.eventType == EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) {
477 launchSelectVisibleCalendars();
478 return;
479 }
480
Mason Tang4003d1c2010-08-17 13:50:45 -0700481 // Create/View/Edit/Delete Event
482 long endTime = (event.endTime == null) ? -1 : event.endTime.toMillis(false);
483 if (event.eventType == EventType.CREATE_EVENT) {
484 launchCreateEvent(event.startTime.toMillis(false), endTime);
485 return;
486 } else if (event.eventType == EventType.VIEW_EVENT) {
487 launchViewEvent(event.id, event.startTime.toMillis(false), endTime);
488 return;
489 } else if (event.eventType == EventType.EDIT_EVENT) {
Erika7694ee2010-12-07 15:46:18 -0800490 launchEditEvent(event.id, event.startTime.toMillis(false), endTime, true);
491 return;
492 } else if (event.eventType == EventType.VIEW_EVENT_DETAILS) {
493 launchEditEvent(event.id, event.startTime.toMillis(false), endTime, false);
Mason Tang4003d1c2010-08-17 13:50:45 -0700494 return;
495 } else if (event.eventType == EventType.DELETE_EVENT) {
496 launchDeleteEvent(event.id, event.startTime.toMillis(false), endTime);
497 return;
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700498 } else if (event.eventType == EventType.SEARCH) {
499 launchSearch(event.id, event.query, event.componentName);
500 return;
Mason Tang4003d1c2010-08-17 13:50:45 -0700501 }
502 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700503 }
504
Erik3f348f32010-08-10 13:17:19 -0700505 /**
506 * Adds or updates an event handler. This uses a LinkedHashMap so that we can
507 * replace fragments based on the view id they are being expanded into.
508 *
509 * @param key The view id or placeholder for this handler
510 * @param eventHandler Typically a fragment or activity in the calendar app
511 */
512 public void registerEventHandler(int key, EventHandler eventHandler) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700513 synchronized (this) {
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700514 if (mDispatchInProgressCounter > 0) {
Erikcb811892010-09-28 13:44:19 -0700515 mToBeAddedEventHandlers.put(key, eventHandler);
516 } else {
517 eventHandlers.put(key, eventHandler);
518 }
Michael Chanab29d9e2010-07-21 06:08:47 -0700519 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700520 }
521
RoboErik3864be02011-07-25 15:56:50 -0700522 public void registerFirstEventHandler(int key, EventHandler eventHandler) {
523 synchronized (this) {
524 registerEventHandler(key, eventHandler);
525 if (mDispatchInProgressCounter > 0) {
526 mToBeAddedFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
527 } else {
528 mFirstEventHandler = new Pair<Integer, EventHandler>(key, eventHandler);
529 }
530 }
531 }
532
Erik3f348f32010-08-10 13:17:19 -0700533 public void deregisterEventHandler(Integer key) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700534 synchronized (this) {
Isaac Katzenelsonccf565b2011-04-27 14:40:08 -0700535 if (mDispatchInProgressCounter > 0) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700536 // To avoid ConcurrencyException, stash away the event handler for now.
Erik3f348f32010-08-10 13:17:19 -0700537 mToBeRemovedEventHandlers.add(key);
Michael Chanab29d9e2010-07-21 06:08:47 -0700538 } else {
Erik3f348f32010-08-10 13:17:19 -0700539 eventHandlers.remove(key);
RoboErik3864be02011-07-25 15:56:50 -0700540 if (mFirstEventHandler != null && mFirstEventHandler.first == key) {
541 mFirstEventHandler = null;
542 }
Michael Chanab29d9e2010-07-21 06:08:47 -0700543 }
544 }
Michael Chan83b0fe32010-07-08 16:46:26 -0700545 }
546
Michael Chan3458a172010-07-13 17:58:21 -0700547 // FRAG_TODO doesn't work yet
Erik25251192010-07-12 15:30:14 -0700548 public void filterBroadcasts(Object sender, long eventTypes) {
Michael Chan83b0fe32010-07-08 16:46:26 -0700549 filters.put(sender, eventTypes);
550 }
551
Mason Tang8e3d4302010-07-12 17:39:30 -0700552 /**
553 * @return the time that this controller is currently pointed at
554 */
555 public long getTime() {
556 return mTime.toMillis(false);
557 }
558
Erik7b92da22010-09-23 14:55:22 -0700559 /**
Erik144edfa2010-11-22 14:23:49 -0800560 * Set the time this controller is currently pointed at
561 *
562 * @param millisTime Time since epoch in millis
563 */
564 public void setTime(long millisTime) {
565 mTime.set(millisTime);
566 }
567
568 /**
Erik7b92da22010-09-23 14:55:22 -0700569 * @return the last event ID the edit view was launched with
570 */
571 public long getEventId() {
572 return mEventId;
573 }
574
Michael Chanab29d9e2010-07-21 06:08:47 -0700575 public int getViewType() {
576 return mViewType;
577 }
578
579 public int getPreviousViewType() {
580 return mPreviousViewType;
581 }
Mason Tang8e3d4302010-07-12 17:39:30 -0700582
Michael Chan2aeb8d92011-07-10 13:32:09 -0700583 private void launchSelectVisibleCalendars() {
584 Intent intent = new Intent(Intent.ACTION_VIEW);
Michael Chan07d9fee2011-07-25 09:41:21 -0700585 intent.setClass(mContext, SelectVisibleCalendarsActivity.class);
Michael Chan2aeb8d92011-07-10 13:32:09 -0700586 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
587 mContext.startActivity(intent);
588 }
589
Michael Chan9e89dca2010-07-13 17:56:09 -0700590 private void launchSettings() {
591 Intent intent = new Intent(Intent.ACTION_VIEW);
Michael Chan07d9fee2011-07-25 09:41:21 -0700592 intent.setClass(mContext, CalendarSettingsActivity.class);
Michael Chan9e89dca2010-07-13 17:56:09 -0700593 intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Michael Chanab29d9e2010-07-21 06:08:47 -0700594 mContext.startActivity(intent);
Michael Chan9e89dca2010-07-13 17:56:09 -0700595 }
596
597 private void launchCreateEvent(long startMillis, long endMillis) {
598 Intent intent = new Intent(Intent.ACTION_VIEW);
Michael Chan07d9fee2011-07-25 09:41:21 -0700599 intent.setClass(mContext, EditEventActivity.class);
RoboErika27a8862011-06-23 15:26:23 -0700600 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
601 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
Erik7b92da22010-09-23 14:55:22 -0700602 mEventId = -1;
Michael Chanab29d9e2010-07-21 06:08:47 -0700603 mContext.startActivity(intent);
Michael Chan9e89dca2010-07-13 17:56:09 -0700604 }
605
606 private void launchViewEvent(long eventId, long startMillis, long endMillis) {
607 Intent intent = new Intent(Intent.ACTION_VIEW);
608 Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
609 intent.setData(eventUri);
Michael Chan07d9fee2011-07-25 09:41:21 -0700610 intent.setClass(mContext, AllInOneActivity.class);
RoboErika27a8862011-06-23 15:26:23 -0700611 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
612 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
Michael Chanab29d9e2010-07-21 06:08:47 -0700613 mContext.startActivity(intent);
Michael Chan9e89dca2010-07-13 17:56:09 -0700614 }
615
Erika7694ee2010-12-07 15:46:18 -0800616 private void launchEditEvent(long eventId, long startMillis, long endMillis, boolean edit) {
Michael Chan9e89dca2010-07-13 17:56:09 -0700617 Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
618 Intent intent = new Intent(Intent.ACTION_EDIT, uri);
RoboErika27a8862011-06-23 15:26:23 -0700619 intent.putExtra(EXTRA_EVENT_BEGIN_TIME, startMillis);
620 intent.putExtra(EXTRA_EVENT_END_TIME, endMillis);
Michael Chanab29d9e2010-07-21 06:08:47 -0700621 intent.setClass(mContext, EditEventActivity.class);
Erika7694ee2010-12-07 15:46:18 -0800622 intent.putExtra(EVENT_EDIT_ON_LAUNCH, edit);
Erik7b92da22010-09-23 14:55:22 -0700623 mEventId = eventId;
Michael Chanab29d9e2010-07-21 06:08:47 -0700624 mContext.startActivity(intent);
Michael Chan9e89dca2010-07-13 17:56:09 -0700625 }
626
Michael Chan1881a892011-02-08 18:16:58 -0800627// private void launchAlerts() {
628// Intent intent = new Intent();
629// intent.setClass(mContext, AlertActivity.class);
630// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
631// mContext.startActivity(intent);
632// }
633
Michael Chan9e89dca2010-07-13 17:56:09 -0700634 private void launchDeleteEvent(long eventId, long startMillis, long endMillis) {
635 launchDeleteEventAndFinish(null, eventId, startMillis, endMillis, -1);
636 }
637
638 private void launchDeleteEventAndFinish(Activity parentActivity, long eventId, long startMillis,
639 long endMillis, int deleteWhich) {
Michael Chanab29d9e2010-07-21 06:08:47 -0700640 DeleteEventHelper deleteEventHelper = new DeleteEventHelper(mContext, parentActivity,
Michael Chan9e89dca2010-07-13 17:56:09 -0700641 parentActivity != null /* exit when done */);
642 deleteEventHelper.delete(startMillis, endMillis, eventId, deleteWhich);
643 }
Michael Chan3458a172010-07-13 17:58:21 -0700644
Daisuke Miyakawa6d2e6f72010-09-17 10:30:54 -0700645 private void launchSearch(long eventId, String query, ComponentName componentName) {
646 final SearchManager searchManager =
647 (SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE);
648 final SearchableInfo searchableInfo = searchManager.getSearchableInfo(componentName);
649 final Intent intent = new Intent(Intent.ACTION_SEARCH);
650 intent.putExtra(SearchManager.QUERY, query);
651 intent.setComponent(searchableInfo.getSearchActivity());
652 mContext.startActivity(intent);
653 }
654
Erikba1b94a2010-07-20 17:50:50 -0700655 public void refreshCalendars() {
656 Log.d(TAG, "RefreshCalendars starting");
657 // get the account, url, and current sync state
658 mService.startQuery(mService.getNextToken(), null, Calendars.CONTENT_URI,
659 new String[] {Calendars._ID, // 0
RoboErik0e1e6242011-05-05 15:25:02 -0700660 Calendars.ACCOUNT_NAME, // 1
661 Calendars.ACCOUNT_TYPE, // 2
Erikba1b94a2010-07-20 17:50:50 -0700662 },
663 REFRESH_SELECTION, REFRESH_ARGS, REFRESH_ORDER);
Erikba1b94a2010-07-20 17:50:50 -0700664 }
665
Erikdd95df52010-08-27 09:31:18 -0700666 // Forces the viewType. Should only be used for initialization.
667 public void setViewType(int viewType) {
668 mViewType = viewType;
669 }
670
Erik7b92da22010-09-23 14:55:22 -0700671 // Sets the eventId. Should only be used for initialization.
672 public void setEventId(long eventId) {
673 mEventId = eventId;
674 }
675
Erik7116ba42010-07-23 10:13:36 -0700676 private class RefreshInBackground extends AsyncTask<Cursor, Integer, Integer> {
677 /* (non-Javadoc)
678 * @see android.os.AsyncTask#doInBackground(Params[])
679 */
680 @Override
681 protected Integer doInBackground(Cursor... params) {
682 if (params.length != 1) {
683 return null;
Erikba1b94a2010-07-20 17:50:50 -0700684 }
Erik7116ba42010-07-23 10:13:36 -0700685 Cursor cursor = params[0];
686 if (cursor == null) {
687 return null;
688 }
Erikba1b94a2010-07-20 17:50:50 -0700689
Erik7116ba42010-07-23 10:13:36 -0700690 String previousAccount = null;
691 String previousType = null;
692 Log.d(TAG, "Refreshing " + cursor.getCount() + " calendars");
693 try {
694 while (cursor.moveToNext()) {
695 Account account = null;
696 String accountName = cursor.getString(1);
697 String accountType = cursor.getString(2);
698 // Only need to schedule one sync per account and they're
699 // ordered by account,type
700 if (TextUtils.equals(accountName, previousAccount) &&
701 TextUtils.equals(accountType, previousType)) {
702 continue;
703 }
704 previousAccount = accountName;
705 previousType = accountType;
706 account = new Account(accountName, accountType);
707 scheduleSync(account, false /* two-way sync */, null);
708 }
709 } finally {
710 cursor.close();
711 }
712 return null;
Erikba1b94a2010-07-20 17:50:50 -0700713 }
Erik7116ba42010-07-23 10:13:36 -0700714
715 /**
716 * Schedule a calendar sync for the account.
717 * @param account the account for which to schedule a sync
718 * @param uploadChangesOnly if set, specify that the sync should only send
719 * up local changes. This is typically used for a local sync, a user override of
720 * too many deletions, or a sync after a calendar is unselected.
721 * @param url the url feed for the calendar to sync (may be null, in which case a poll of
722 * all feeds is done.)
723 */
724 void scheduleSync(Account account, boolean uploadChangesOnly, String url) {
725 Bundle extras = new Bundle();
726 if (uploadChangesOnly) {
727 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, uploadChangesOnly);
728 }
729 if (url != null) {
730 extras.putString("feed", url);
731 extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
732 }
733 ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras);
Erikba1b94a2010-07-20 17:50:50 -0700734 }
Erikba1b94a2010-07-20 17:50:50 -0700735 }
736
Michael Chan3458a172010-07-13 17:58:21 -0700737 private String eventInfoToString(EventInfo eventInfo) {
738 String tmp = "Unknown";
739
740 StringBuilder builder = new StringBuilder();
Michael Chanab29d9e2010-07-21 06:08:47 -0700741 if ((eventInfo.eventType & EventType.GO_TO) != 0) {
Michael Chan3458a172010-07-13 17:58:21 -0700742 tmp = "Go to time/event";
743 } else if ((eventInfo.eventType & EventType.CREATE_EVENT) != 0) {
744 tmp = "New event";
745 } else if ((eventInfo.eventType & EventType.VIEW_EVENT) != 0) {
746 tmp = "View event";
Michael Chan6d4ce6e2011-01-11 11:15:16 -0800747 } else if ((eventInfo.eventType & EventType.VIEW_EVENT_DETAILS) != 0) {
748 tmp = "View details";
Michael Chan3458a172010-07-13 17:58:21 -0700749 } else if ((eventInfo.eventType & EventType.EDIT_EVENT) != 0) {
750 tmp = "Edit event";
751 } else if ((eventInfo.eventType & EventType.DELETE_EVENT) != 0) {
752 tmp = "Delete event";
Michael Chan2aeb8d92011-07-10 13:32:09 -0700753 } else if ((eventInfo.eventType & EventType.LAUNCH_SELECT_VISIBLE_CALENDARS) != 0) {
754 tmp = "Launch select visible calendars";
Michael Chan3458a172010-07-13 17:58:21 -0700755 } else if ((eventInfo.eventType & EventType.LAUNCH_SETTINGS) != 0) {
756 tmp = "Launch settings";
Erik3f348f32010-08-10 13:17:19 -0700757 } else if ((eventInfo.eventType & EventType.EVENTS_CHANGED) != 0) {
758 tmp = "Refresh events";
Mason Tang4003d1c2010-08-17 13:50:45 -0700759 } else if ((eventInfo.eventType & EventType.SEARCH) != 0) {
760 tmp = "Search";
Michael Chan6d4ce6e2011-01-11 11:15:16 -0800761 } else if ((eventInfo.eventType & EventType.USER_HOME) != 0) {
762 tmp = "Gone home";
763 } else if ((eventInfo.eventType & EventType.UPDATE_TITLE) != 0) {
764 tmp = "Update title";
Michael Chan3458a172010-07-13 17:58:21 -0700765 }
766 builder.append(tmp);
767 builder.append(": id=");
768 builder.append(eventInfo.id);
Michael Chand6734db2010-07-22 00:48:08 -0700769 builder.append(", selected=");
770 builder.append(eventInfo.selectedTime);
771 builder.append(", start=");
Michael Chan3458a172010-07-13 17:58:21 -0700772 builder.append(eventInfo.startTime);
Michael Chand6734db2010-07-22 00:48:08 -0700773 builder.append(", end=");
Michael Chan3458a172010-07-13 17:58:21 -0700774 builder.append(eventInfo.endTime);
775 builder.append(", viewType=");
776 builder.append(eventInfo.viewType);
777 builder.append(", x=");
778 builder.append(eventInfo.x);
779 builder.append(", y=");
780 builder.append(eventInfo.y);
781 return builder.toString();
782 }
Michael Chan49701592010-06-30 11:04:03 -0700783}