blob: 095e43e72017f6dd673dc2d29455bdd40c431d16 [file] [log] [blame]
The Android Open Source Project146de362009-03-03 19:32:18 -08001/*
2 * Copyright (C) 2007 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 android.content.ContentResolver;
20import android.content.ContentUris;
The Android Open Source Project146de362009-03-03 19:32:18 -080021import android.content.Context;
22import android.content.SharedPreferences;
23import android.content.res.Resources;
24import android.database.Cursor;
RoboErika27a8862011-06-23 15:26:23 -070025import android.net.Uri;
The Android Open Source Project146de362009-03-03 19:32:18 -080026import android.os.Debug;
RoboErika7c03902011-06-14 11:06:44 -070027import android.provider.CalendarContract.Attendees;
RoboErika27a8862011-06-23 15:26:23 -070028import android.provider.CalendarContract.Calendars;
RoboErika7c03902011-06-14 11:06:44 -070029import android.provider.CalendarContract.Events;
30import android.provider.CalendarContract.Instances;
The Android Open Source Project146de362009-03-03 19:32:18 -080031import android.text.TextUtils;
32import android.text.format.DateUtils;
The Android Open Source Project146de362009-03-03 19:32:18 -080033import android.util.Log;
34
35import java.util.ArrayList;
RoboErika27a8862011-06-23 15:26:23 -070036import java.util.Arrays;
The Android Open Source Project146de362009-03-03 19:32:18 -080037import java.util.Iterator;
38import java.util.concurrent.atomic.AtomicInteger;
39
40// TODO: should Event be Parcelable so it can be passed via Intents?
RoboErik1d92cbd2011-02-23 09:36:32 -080041public class Event implements Cloneable {
The Android Open Source Project146de362009-03-03 19:32:18 -080042
Erik981874e2010-10-05 16:52:52 -070043 private static final String TAG = "CalEvent";
The Android Open Source Project146de362009-03-03 19:32:18 -080044 private static final boolean PROFILE = false;
45
RoboErik2fda2452011-02-15 17:47:45 -080046 /**
47 * The sort order is:
RoboErikff982e82011-02-28 17:11:16 -080048 * 1) events with an earlier start (begin for normal events, startday for allday)
49 * 2) events with a later end (end for normal events, endday for allday)
50 * 3) the title (unnecessary, but nice)
RoboErik2fda2452011-02-15 17:47:45 -080051 *
52 * The start and end day is sorted first so that all day events are
53 * sorted correctly with respect to events that are >24 hours (and
54 * therefore show up in the allday area).
55 */
56 private static final String SORT_EVENTS_BY =
RoboErikff982e82011-02-28 17:11:16 -080057 "begin ASC, end DESC, title ASC";
58 private static final String SORT_ALLDAY_BY =
59 "startDay ASC, endDay DESC, title ASC";
60 private static final String DISPLAY_AS_ALLDAY = "dispAllday";
61
62 private static final String EVENTS_WHERE = DISPLAY_AS_ALLDAY + "=0";
63 private static final String ALLDAY_WHERE = DISPLAY_AS_ALLDAY + "=1";
RoboErik2fda2452011-02-15 17:47:45 -080064
Erik981874e2010-10-05 16:52:52 -070065 // The projection to use when querying instances to build a list of events
66 public static final String[] EVENT_PROJECTION = new String[] {
The Android Open Source Project146de362009-03-03 19:32:18 -080067 Instances.TITLE, // 0
68 Instances.EVENT_LOCATION, // 1
69 Instances.ALL_DAY, // 2
Michael Chan693ca602012-05-31 12:24:11 -070070 Instances.DISPLAY_COLOR, // 3 If SDK < 16, set to Instances.CALENDAR_COLOR.
The Android Open Source Project146de362009-03-03 19:32:18 -080071 Instances.EVENT_TIMEZONE, // 4
72 Instances.EVENT_ID, // 5
73 Instances.BEGIN, // 6
74 Instances.END, // 7
75 Instances._ID, // 8
76 Instances.START_DAY, // 9
77 Instances.END_DAY, // 10
78 Instances.START_MINUTE, // 11
79 Instances.END_MINUTE, // 12
80 Instances.HAS_ALARM, // 13
81 Instances.RRULE, // 14
82 Instances.RDATE, // 15
83 Instances.SELF_ATTENDEE_STATUS, // 16
Michael Chan6d34cec2009-09-11 14:42:31 -070084 Events.ORGANIZER, // 17
85 Events.GUESTS_CAN_MODIFY, // 18
RoboErikff982e82011-02-28 17:11:16 -080086 Instances.ALL_DAY + "=1 OR (" + Instances.END + "-" + Instances.BEGIN + ")>="
87 + DateUtils.DAY_IN_MILLIS + " AS " + DISPLAY_AS_ALLDAY, // 19
The Android Open Source Project146de362009-03-03 19:32:18 -080088 };
89
90 // The indices for the projection array above.
91 private static final int PROJECTION_TITLE_INDEX = 0;
92 private static final int PROJECTION_LOCATION_INDEX = 1;
93 private static final int PROJECTION_ALL_DAY_INDEX = 2;
94 private static final int PROJECTION_COLOR_INDEX = 3;
95 private static final int PROJECTION_TIMEZONE_INDEX = 4;
96 private static final int PROJECTION_EVENT_ID_INDEX = 5;
97 private static final int PROJECTION_BEGIN_INDEX = 6;
98 private static final int PROJECTION_END_INDEX = 7;
99 private static final int PROJECTION_START_DAY_INDEX = 9;
100 private static final int PROJECTION_END_DAY_INDEX = 10;
101 private static final int PROJECTION_START_MINUTE_INDEX = 11;
102 private static final int PROJECTION_END_MINUTE_INDEX = 12;
103 private static final int PROJECTION_HAS_ALARM_INDEX = 13;
104 private static final int PROJECTION_RRULE_INDEX = 14;
105 private static final int PROJECTION_RDATE_INDEX = 15;
106 private static final int PROJECTION_SELF_ATTENDEE_STATUS_INDEX = 16;
Michael Chan6d34cec2009-09-11 14:42:31 -0700107 private static final int PROJECTION_ORGANIZER_INDEX = 17;
108 private static final int PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX = 18;
RoboErikff982e82011-02-28 17:11:16 -0800109 private static final int PROJECTION_DISPLAY_AS_ALLDAY = 19;
110
Michael Chan693ca602012-05-31 12:24:11 -0700111 static {
Sara Tingfac2d152012-05-31 14:59:57 -0700112 if (!Utils.isJellybeanOrLater()) {
Michael Chan693ca602012-05-31 12:24:11 -0700113 EVENT_PROJECTION[PROJECTION_COLOR_INDEX] = Instances.CALENDAR_COLOR;
114 }
115 }
116
RoboErikff982e82011-02-28 17:11:16 -0800117 private static String mNoTitleString;
118 private static int mNoColorColor;
The Android Open Source Project146de362009-03-03 19:32:18 -0800119
120 public long id;
121 public int color;
122 public CharSequence title;
123 public CharSequence location;
124 public boolean allDay;
Michael Chan6d34cec2009-09-11 14:42:31 -0700125 public String organizer;
126 public boolean guestsCanModify;
The Android Open Source Project146de362009-03-03 19:32:18 -0800127
128 public int startDay; // start Julian day
129 public int endDay; // end Julian day
130 public int startTime; // Start and end time are in minutes since midnight
131 public int endTime;
132
133 public long startMillis; // UTC milliseconds since the epoch
134 public long endMillis; // UTC milliseconds since the epoch
135 private int mColumn;
136 private int mMaxColumns;
137
138 public boolean hasAlarm;
139 public boolean isRepeating;
Erik981874e2010-10-05 16:52:52 -0700140
The Android Open Source Project146de362009-03-03 19:32:18 -0800141 public int selfAttendeeStatus;
142
143 // The coordinates of the event rectangle drawn on the screen.
144 public float left;
145 public float right;
146 public float top;
147 public float bottom;
148
149 // These 4 fields are used for navigating among events within the selected
150 // hour in the Day and Week view.
151 public Event nextRight;
152 public Event nextLeft;
153 public Event nextUp;
154 public Event nextDown;
155
The Android Open Source Project146de362009-03-03 19:32:18 -0800156 @Override
Michael Chan98ab9de2009-05-14 16:09:13 -0700157 public final Object clone() throws CloneNotSupportedException {
158 super.clone();
The Android Open Source Project146de362009-03-03 19:32:18 -0800159 Event e = new Event();
160
161 e.title = title;
162 e.color = color;
163 e.location = location;
164 e.allDay = allDay;
165 e.startDay = startDay;
166 e.endDay = endDay;
167 e.startTime = startTime;
168 e.endTime = endTime;
169 e.startMillis = startMillis;
170 e.endMillis = endMillis;
171 e.hasAlarm = hasAlarm;
172 e.isRepeating = isRepeating;
173 e.selfAttendeeStatus = selfAttendeeStatus;
Michael Chan6d34cec2009-09-11 14:42:31 -0700174 e.organizer = organizer;
175 e.guestsCanModify = guestsCanModify;
The Android Open Source Project146de362009-03-03 19:32:18 -0800176
177 return e;
178 }
179
180 public final void copyTo(Event dest) {
181 dest.id = id;
182 dest.title = title;
183 dest.color = color;
184 dest.location = location;
185 dest.allDay = allDay;
186 dest.startDay = startDay;
187 dest.endDay = endDay;
188 dest.startTime = startTime;
189 dest.endTime = endTime;
190 dest.startMillis = startMillis;
191 dest.endMillis = endMillis;
192 dest.hasAlarm = hasAlarm;
193 dest.isRepeating = isRepeating;
194 dest.selfAttendeeStatus = selfAttendeeStatus;
Michael Chan6d34cec2009-09-11 14:42:31 -0700195 dest.organizer = organizer;
196 dest.guestsCanModify = guestsCanModify;
The Android Open Source Project146de362009-03-03 19:32:18 -0800197 }
198
199 public static final Event newInstance() {
200 Event e = new Event();
201
202 e.id = 0;
203 e.title = null;
204 e.color = 0;
205 e.location = null;
206 e.allDay = false;
207 e.startDay = 0;
208 e.endDay = 0;
209 e.startTime = 0;
210 e.endTime = 0;
211 e.startMillis = 0;
212 e.endMillis = 0;
213 e.hasAlarm = false;
214 e.isRepeating = false;
215 e.selfAttendeeStatus = Attendees.ATTENDEE_STATUS_NONE;
216
217 return e;
218 }
219
220 /**
RoboErikb5e3c172011-07-14 16:09:44 -0700221 * Loads <i>days</i> days worth of instances starting at <i>startDay</i>.
The Android Open Source Project146de362009-03-03 19:32:18 -0800222 */
RoboErikb5e3c172011-07-14 16:09:44 -0700223 public static void loadEvents(Context context, ArrayList<Event> events, int startDay, int days,
224 int requestId, AtomicInteger sequenceNumber) {
The Android Open Source Project146de362009-03-03 19:32:18 -0800225
226 if (PROFILE) {
227 Debug.startMethodTracing("loadEvents");
228 }
229
RoboErikff982e82011-02-28 17:11:16 -0800230 Cursor cEvents = null;
231 Cursor cAllday = null;
The Android Open Source Project146de362009-03-03 19:32:18 -0800232
233 events.clear();
234 try {
RoboErikb5e3c172011-07-14 16:09:44 -0700235 int endDay = startDay + days - 1;
The Android Open Source Project146de362009-03-03 19:32:18 -0800236
RoboErikb5e3c172011-07-14 16:09:44 -0700237 // We use the byDay instances query to get a list of all events for
238 // the days we're interested in.
The Android Open Source Project146de362009-03-03 19:32:18 -0800239 // The sort order is: events with an earlier start time occur
240 // first and if the start times are the same, then events with
241 // a later end time occur first. The later end time is ordered
242 // first so that long rectangles in the calendar views appear on
243 // the left side. If the start and end times of two events are
244 // the same then we sort alphabetically on the title. This isn't
245 // required for correctness, it just adds a nice touch.
246
The Android Open Source Project146de362009-03-03 19:32:18 -0800247 // Respect the preference to show/hide declined events
Daisuke Miyakawa4b441bd2010-09-16 14:55:36 -0700248 SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context);
249 boolean hideDeclined = prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED,
The Android Open Source Project146de362009-03-03 19:32:18 -0800250 false);
251
RoboErikff982e82011-02-28 17:11:16 -0800252 String where = EVENTS_WHERE;
253 String whereAllday = ALLDAY_WHERE;
The Android Open Source Project146de362009-03-03 19:32:18 -0800254 if (hideDeclined) {
RoboErikff982e82011-02-28 17:11:16 -0800255 String hideString = " AND " + Instances.SELF_ATTENDEE_STATUS + "!="
256 + Attendees.ATTENDEE_STATUS_DECLINED;
257 where += hideString;
258 whereAllday += hideString;
The Android Open Source Project146de362009-03-03 19:32:18 -0800259 }
260
RoboErikb5e3c172011-07-14 16:09:44 -0700261 cEvents = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
262 endDay, where, null, SORT_EVENTS_BY);
263 cAllday = instancesQuery(context.getContentResolver(), EVENT_PROJECTION, startDay,
264 endDay, whereAllday, null, SORT_ALLDAY_BY);
The Android Open Source Project146de362009-03-03 19:32:18 -0800265
The Android Open Source Project146de362009-03-03 19:32:18 -0800266 // Check if we should return early because there are more recent
267 // load requests waiting.
268 if (requestId != sequenceNumber.get()) {
269 return;
270 }
271
RoboErikff982e82011-02-28 17:11:16 -0800272 buildEventsFromCursor(events, cEvents, context, startDay, endDay);
273 buildEventsFromCursor(events, cAllday, context, startDay, endDay);
The Android Open Source Project146de362009-03-03 19:32:18 -0800274
The Android Open Source Project146de362009-03-03 19:32:18 -0800275 } finally {
RoboErikff982e82011-02-28 17:11:16 -0800276 if (cEvents != null) {
277 cEvents.close();
278 }
279 if (cAllday != null) {
280 cAllday.close();
The Android Open Source Project146de362009-03-03 19:32:18 -0800281 }
282 if (PROFILE) {
283 Debug.stopMethodTracing();
284 }
285 }
286 }
287
RoboErikff982e82011-02-28 17:11:16 -0800288 /**
RoboErika27a8862011-06-23 15:26:23 -0700289 * Performs a query to return all visible instances in the given range
290 * that match the given selection. This is a blocking function and
291 * should not be done on the UI thread. This will cause an expansion of
292 * recurring events to fill this time range if they are not already
293 * expanded and will slow down for larger time ranges with many
294 * recurring events.
295 *
296 * @param cr The ContentResolver to use for the query
297 * @param projection The columns to return
298 * @param begin The start of the time range to query in UTC millis since
299 * epoch
300 * @param end The end of the time range to query in UTC millis since
301 * epoch
302 * @param selection Filter on the query as an SQL WHERE statement
303 * @param selectionArgs Args to replace any '?'s in the selection
304 * @param orderBy How to order the rows as an SQL ORDER BY statement
305 * @return A Cursor of instances matching the selection
306 */
RoboErikb5e3c172011-07-14 16:09:44 -0700307 private static final Cursor instancesQuery(ContentResolver cr, String[] projection,
308 int startDay, int endDay, String selection, String[] selectionArgs, String orderBy) {
RoboErika27a8862011-06-23 15:26:23 -0700309 String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?";
310 String[] WHERE_CALENDARS_ARGS = {"1"};
311 String DEFAULT_SORT_ORDER = "begin ASC";
312
RoboErikb5e3c172011-07-14 16:09:44 -0700313 Uri.Builder builder = Instances.CONTENT_BY_DAY_URI.buildUpon();
314 ContentUris.appendId(builder, startDay);
315 ContentUris.appendId(builder, endDay);
RoboErika27a8862011-06-23 15:26:23 -0700316 if (TextUtils.isEmpty(selection)) {
317 selection = WHERE_CALENDARS_SELECTED;
318 selectionArgs = WHERE_CALENDARS_ARGS;
319 } else {
320 selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
321 if (selectionArgs != null && selectionArgs.length > 0) {
322 selectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1);
323 selectionArgs[selectionArgs.length - 1] = WHERE_CALENDARS_ARGS[0];
324 } else {
325 selectionArgs = WHERE_CALENDARS_ARGS;
326 }
327 }
328 return cr.query(builder.build(), projection, selection, selectionArgs,
329 orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
330 }
331
332 /**
RoboErikff982e82011-02-28 17:11:16 -0800333 * Adds all the events from the cursors to the events list.
334 *
335 * @param events The list of events
336 * @param cEvents Events to add to the list
337 * @param context
338 * @param startDay
339 * @param endDay
340 */
341 public static void buildEventsFromCursor(
342 ArrayList<Event> events, Cursor cEvents, Context context, int startDay, int endDay) {
343 if (cEvents == null || events == null) {
Erik981874e2010-10-05 16:52:52 -0700344 Log.e(TAG, "buildEventsFromCursor: null cursor or null events list!");
345 return;
346 }
347
RoboErikff982e82011-02-28 17:11:16 -0800348 int count = cEvents.getCount();
Erik981874e2010-10-05 16:52:52 -0700349
350 if (count == 0) {
351 return;
352 }
353
354 Resources res = context.getResources();
RoboErikff982e82011-02-28 17:11:16 -0800355 mNoTitleString = res.getString(R.string.no_title_label);
356 mNoColorColor = res.getColor(R.color.event_center);
357 // Sort events in two passes so we ensure the allday and standard events
358 // get sorted in the correct order
Isaac Katzenelson4d2f0352012-03-26 17:37:24 -0700359 cEvents.moveToPosition(-1);
RoboErikff982e82011-02-28 17:11:16 -0800360 while (cEvents.moveToNext()) {
RoboErikb5e3c172011-07-14 16:09:44 -0700361 Event e = generateEventFromCursor(cEvents);
362 if (e.startDay > endDay || e.endDay < startDay) {
363 continue;
364 }
Erik981874e2010-10-05 16:52:52 -0700365 events.add(e);
366 }
367 }
368
The Android Open Source Project146de362009-03-03 19:32:18 -0800369 /**
RoboErikff982e82011-02-28 17:11:16 -0800370 * @param cEvents Cursor pointing at event
RoboErikff982e82011-02-28 17:11:16 -0800371 * @return An event created from the cursor
372 */
RoboErikb5e3c172011-07-14 16:09:44 -0700373 private static Event generateEventFromCursor(Cursor cEvents) {
RoboErikff982e82011-02-28 17:11:16 -0800374 Event e = new Event();
375
376 e.id = cEvents.getLong(PROJECTION_EVENT_ID_INDEX);
377 e.title = cEvents.getString(PROJECTION_TITLE_INDEX);
378 e.location = cEvents.getString(PROJECTION_LOCATION_INDEX);
379 e.allDay = cEvents.getInt(PROJECTION_ALL_DAY_INDEX) != 0;
380 e.organizer = cEvents.getString(PROJECTION_ORGANIZER_INDEX);
381 e.guestsCanModify = cEvents.getInt(PROJECTION_GUESTS_CAN_INVITE_OTHERS_INDEX) != 0;
382
383 if (e.title == null || e.title.length() == 0) {
384 e.title = mNoTitleString;
385 }
386
387 if (!cEvents.isNull(PROJECTION_COLOR_INDEX)) {
388 // Read the color from the database
RoboErik4acb2fd2011-07-18 15:39:49 -0700389 e.color = Utils.getDisplayColorFromColor(cEvents.getInt(PROJECTION_COLOR_INDEX));
RoboErikff982e82011-02-28 17:11:16 -0800390 } else {
391 e.color = mNoColorColor;
392 }
393
394 long eStart = cEvents.getLong(PROJECTION_BEGIN_INDEX);
395 long eEnd = cEvents.getLong(PROJECTION_END_INDEX);
396
397 e.startMillis = eStart;
398 e.startTime = cEvents.getInt(PROJECTION_START_MINUTE_INDEX);
399 e.startDay = cEvents.getInt(PROJECTION_START_DAY_INDEX);
400
401 e.endMillis = eEnd;
402 e.endTime = cEvents.getInt(PROJECTION_END_MINUTE_INDEX);
403 e.endDay = cEvents.getInt(PROJECTION_END_DAY_INDEX);
404
RoboErikff982e82011-02-28 17:11:16 -0800405 e.hasAlarm = cEvents.getInt(PROJECTION_HAS_ALARM_INDEX) != 0;
406
407 // Check if this is a repeating event
408 String rrule = cEvents.getString(PROJECTION_RRULE_INDEX);
409 String rdate = cEvents.getString(PROJECTION_RDATE_INDEX);
410 if (!TextUtils.isEmpty(rrule) || !TextUtils.isEmpty(rdate)) {
411 e.isRepeating = true;
412 } else {
413 e.isRepeating = false;
414 }
415
416 e.selfAttendeeStatus = cEvents.getInt(PROJECTION_SELF_ATTENDEE_STATUS_INDEX);
417 return e;
418 }
419
420 /**
The Android Open Source Project146de362009-03-03 19:32:18 -0800421 * Computes a position for each event. Each event is displayed
422 * as a non-overlapping rectangle. For normal events, these rectangles
423 * are displayed in separate columns in the week view and day view. For
424 * all-day events, these rectangles are displayed in separate rows along
425 * the top. In both cases, each event is assigned two numbers: N, and
426 * Max, that specify that this event is the Nth event of Max number of
427 * events that are displayed in a group. The width and position of each
428 * rectangle depend on the maximum number of rectangles that occur at
429 * the same time.
430 *
431 * @param eventsList the list of events, sorted into increasing time order
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700432 * @param minimumDurationMillis minimum duration acceptable as cell height of each event
433 * rectangle in millisecond. Should be 0 when it is not determined.
The Android Open Source Project146de362009-03-03 19:32:18 -0800434 */
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700435 /* package */ static void computePositions(ArrayList<Event> eventsList,
Daisuke Miyakawa27d671c2010-10-13 10:22:20 -0700436 long minimumDurationMillis) {
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700437 if (eventsList == null) {
The Android Open Source Project146de362009-03-03 19:32:18 -0800438 return;
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700439 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800440
441 // Compute the column positions separately for the all-day events
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700442 doComputePositions(eventsList, minimumDurationMillis, false);
443 doComputePositions(eventsList, minimumDurationMillis, true);
The Android Open Source Project146de362009-03-03 19:32:18 -0800444 }
445
446 private static void doComputePositions(ArrayList<Event> eventsList,
RoboErik68655b52011-02-23 10:59:37 -0800447 long minimumDurationMillis, boolean doAlldayEvents) {
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700448 final ArrayList<Event> activeList = new ArrayList<Event>();
449 final ArrayList<Event> groupList = new ArrayList<Event>();
450
Daisuke Miyakawa27d671c2010-10-13 10:22:20 -0700451 if (minimumDurationMillis < 0) {
452 minimumDurationMillis = 0;
Daisuke Miyakawa46b05ff2010-09-29 13:04:51 -0700453 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800454
455 long colMask = 0;
456 int maxCols = 0;
457 for (Event event : eventsList) {
458 // Process all-day events separately
RoboErik68655b52011-02-23 10:59:37 -0800459 if (event.drawAsAllday() != doAlldayEvents)
The Android Open Source Project146de362009-03-03 19:32:18 -0800460 continue;
461
RoboErik68655b52011-02-23 10:59:37 -0800462 if (!doAlldayEvents) {
463 colMask = removeNonAlldayActiveEvents(
464 event, activeList.iterator(), minimumDurationMillis, colMask);
465 } else {
466 colMask = removeAlldayActiveEvents(event, activeList.iterator(), colMask);
The Android Open Source Project146de362009-03-03 19:32:18 -0800467 }
468
469 // If the active list is empty, then reset the max columns, clear
470 // the column bit mask, and empty the groupList.
471 if (activeList.isEmpty()) {
472 for (Event ev : groupList) {
473 ev.setMaxColumns(maxCols);
474 }
475 maxCols = 0;
476 colMask = 0;
477 groupList.clear();
478 }
479
480 // Find the first empty column. Empty columns are represented by
481 // zero bits in the column mask "colMask".
482 int col = findFirstZeroBit(colMask);
483 if (col == 64)
484 col = 63;
485 colMask |= (1L << col);
486 event.setColumn(col);
487 activeList.add(event);
488 groupList.add(event);
489 int len = activeList.size();
490 if (maxCols < len)
491 maxCols = len;
492 }
493 for (Event ev : groupList) {
494 ev.setMaxColumns(maxCols);
495 }
496 }
497
RoboErik68655b52011-02-23 10:59:37 -0800498 private static long removeAlldayActiveEvents(Event event, Iterator<Event> iter, long colMask) {
499 // Remove the inactive allday events. An event on the active list
500 // becomes inactive when the end day is less than the current event's
501 // start day.
502 while (iter.hasNext()) {
503 final Event active = iter.next();
504 if (active.endDay < event.startDay) {
505 colMask &= ~(1L << active.getColumn());
506 iter.remove();
507 }
508 }
509 return colMask;
510 }
511
512 private static long removeNonAlldayActiveEvents(
513 Event event, Iterator<Event> iter, long minDurationMillis, long colMask) {
514 long start = event.getStartMillis();
515 // Remove the inactive events. An event on the active list
516 // becomes inactive when its end time is less than or equal to
517 // the current event's start time.
518 while (iter.hasNext()) {
519 final Event active = iter.next();
520
521 final long duration = Math.max(
522 active.getEndMillis() - active.getStartMillis(), minDurationMillis);
523 if ((active.getStartMillis() + duration) <= start) {
524 colMask &= ~(1L << active.getColumn());
525 iter.remove();
526 }
527 }
528 return colMask;
529 }
530
The Android Open Source Project146de362009-03-03 19:32:18 -0800531 public static int findFirstZeroBit(long val) {
532 for (int ii = 0; ii < 64; ++ii) {
533 if ((val & (1L << ii)) == 0)
534 return ii;
535 }
536 return 64;
537 }
538
The Android Open Source Project146de362009-03-03 19:32:18 -0800539 public final void dump() {
540 Log.e("Cal", "+-----------------------------------------+");
541 Log.e("Cal", "+ id = " + id);
542 Log.e("Cal", "+ color = " + color);
543 Log.e("Cal", "+ title = " + title);
544 Log.e("Cal", "+ location = " + location);
545 Log.e("Cal", "+ allDay = " + allDay);
546 Log.e("Cal", "+ startDay = " + startDay);
547 Log.e("Cal", "+ endDay = " + endDay);
548 Log.e("Cal", "+ startTime = " + startTime);
549 Log.e("Cal", "+ endTime = " + endTime);
Michael Chan6d34cec2009-09-11 14:42:31 -0700550 Log.e("Cal", "+ organizer = " + organizer);
551 Log.e("Cal", "+ guestwrt = " + guestsCanModify);
The Android Open Source Project146de362009-03-03 19:32:18 -0800552 }
553
554 public final boolean intersects(int julianDay, int startMinute,
555 int endMinute) {
556 if (endDay < julianDay) {
557 return false;
558 }
559
560 if (startDay > julianDay) {
561 return false;
562 }
563
564 if (endDay == julianDay) {
565 if (endTime < startMinute) {
566 return false;
567 }
568 // An event that ends at the start minute should not be considered
569 // as intersecting the given time span, but don't exclude
570 // zero-length (or very short) events.
571 if (endTime == startMinute
572 && (startTime != endTime || startDay != endDay)) {
573 return false;
574 }
575 }
576
577 if (startDay == julianDay && startTime > endMinute) {
578 return false;
579 }
580
581 return true;
582 }
583
584 /**
585 * Returns the event title and location separated by a comma. If the
586 * location is already part of the title (at the end of the title), then
587 * just the title is returned.
588 *
589 * @return the event title and location as a String
590 */
591 public String getTitleAndLocation() {
592 String text = title.toString();
593
594 // Append the location to the title, unless the title ends with the
595 // location (for example, "meeting in building 42" ends with the
596 // location).
597 if (location != null) {
598 String locationString = location.toString();
599 if (!text.endsWith(locationString)) {
600 text += ", " + locationString;
601 }
602 }
603 return text;
604 }
605
606 public void setColumn(int column) {
607 mColumn = column;
608 }
609
610 public int getColumn() {
611 return mColumn;
612 }
613
614 public void setMaxColumns(int maxColumns) {
615 mMaxColumns = maxColumns;
616 }
617
618 public int getMaxColumns() {
619 return mMaxColumns;
620 }
621
622 public void setStartMillis(long startMillis) {
623 this.startMillis = startMillis;
624 }
625
626 public long getStartMillis() {
627 return startMillis;
628 }
629
630 public void setEndMillis(long endMillis) {
631 this.endMillis = endMillis;
632 }
633
634 public long getEndMillis() {
635 return endMillis;
636 }
Erikd0314192011-02-01 13:53:14 -0800637
RoboErik68655b52011-02-23 10:59:37 -0800638 public boolean drawAsAllday() {
RoboErik1d92cbd2011-02-23 09:36:32 -0800639 // Use >= so we'll pick up Exchange allday events
640 return allDay || endMillis - startMillis >= DateUtils.DAY_IN_MILLIS;
Erikd0314192011-02-01 13:53:14 -0800641 }
The Android Open Source Project146de362009-03-03 19:32:18 -0800642}