• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.agenda;
18 
19 import com.android.calendar.R;
20 import com.android.calendar.Utils;
21 import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo;
22 
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.graphics.Typeface;
26 import android.text.TextUtils;
27 import android.text.format.DateUtils;
28 import android.text.format.Time;
29 import android.util.Log;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.BaseAdapter;
34 import android.widget.TextView;
35 
36 import java.util.ArrayList;
37 import java.util.Formatter;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.Locale;
41 
42 public class AgendaByDayAdapter extends BaseAdapter {
43     private static final int TYPE_DAY = 0;
44     private static final int TYPE_MEETING = 1;
45     static final int TYPE_LAST = 2;
46 
47     private final Context mContext;
48     private final AgendaAdapter mAgendaAdapter;
49     private final LayoutInflater mInflater;
50     private ArrayList<RowInfo> mRowInfo;
51     private int mTodayJulianDay;
52     private Time mTmpTime;
53     private String mTimeZone;
54     // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread.
55     private final Formatter mFormatter;
56     private final StringBuilder mStringBuilder;
57 
58     static class ViewHolder {
59         TextView dayView;
60         TextView dateView;
61         int julianDay;
62         boolean grayed;
63     }
64 
65     private final Runnable mTZUpdater = new Runnable() {
66         @Override
67         public void run() {
68             mTimeZone = Utils.getTimeZone(mContext, this);
69             mTmpTime = new Time(mTimeZone);
70             notifyDataSetChanged();
71         }
72     };
73 
AgendaByDayAdapter(Context context)74     public AgendaByDayAdapter(Context context) {
75         mContext = context;
76         mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item);
77         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
78         mStringBuilder = new StringBuilder(50);
79         mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
80         mTimeZone = Utils.getTimeZone(context, mTZUpdater);
81         mTmpTime = new Time(mTimeZone);
82     }
83 
getInstanceId(int position)84     public long getInstanceId(int position) {
85         if (mRowInfo == null || position >= mRowInfo.size()) {
86             return -1;
87         }
88         return mRowInfo.get(position).mInstanceId;
89     }
90 
getStartTime(int position)91     public long getStartTime(int position) {
92         if (mRowInfo == null || position >= mRowInfo.size()) {
93             return -1;
94         }
95         return mRowInfo.get(position).mEventStartTimeMilli;
96     }
97 
98 
99     // Returns the position of a header of a specific item
getHeaderPosition(int position)100     public int getHeaderPosition(int position) {
101         if (mRowInfo == null || position >= mRowInfo.size()) {
102             return -1;
103         }
104 
105         for (int i = position; i >=0; i --) {
106             RowInfo row = mRowInfo.get(i);
107             if (row != null && row.mType == TYPE_DAY)
108                 return i;
109         }
110         return -1;
111     }
112 
113     // Returns the number of items in a section defined by a specific header location
getHeaderItemsCount(int position)114     public int getHeaderItemsCount(int position) {
115         if (mRowInfo == null) {
116             return -1;
117         }
118         int count = 0;
119         for (int i = position +1; i < mRowInfo.size(); i++) {
120             if (mRowInfo.get(i).mType != TYPE_MEETING) {
121                 return count;
122             }
123             count ++;
124         }
125         return count;
126     }
127 
getCount()128     public int getCount() {
129         if (mRowInfo != null) {
130             return mRowInfo.size();
131         }
132         return mAgendaAdapter.getCount();
133     }
134 
getItem(int position)135     public Object getItem(int position) {
136         if (mRowInfo != null) {
137             RowInfo row = mRowInfo.get(position);
138             if (row.mType == TYPE_DAY) {
139                 return row;
140             } else {
141                 return mAgendaAdapter.getItem(row.mPosition);
142             }
143         }
144         return mAgendaAdapter.getItem(position);
145     }
146 
getItemId(int position)147     public long getItemId(int position) {
148         if (mRowInfo != null) {
149             RowInfo row = mRowInfo.get(position);
150             if (row.mType == TYPE_DAY) {
151                 return -position;
152             } else {
153                 return mAgendaAdapter.getItemId(row.mPosition);
154             }
155         }
156         return mAgendaAdapter.getItemId(position);
157     }
158 
159     @Override
getViewTypeCount()160     public int getViewTypeCount() {
161         return TYPE_LAST;
162     }
163 
164     @Override
getItemViewType(int position)165     public int getItemViewType(int position) {
166         return mRowInfo != null && mRowInfo.size() > position ?
167                 mRowInfo.get(position).mType : TYPE_DAY;
168     }
169 
isDayHeaderView(int position)170     public boolean isDayHeaderView(int position) {
171         return (getItemViewType(position) == TYPE_DAY);
172     }
173 
getView(int position, View convertView, ViewGroup parent)174     public View getView(int position, View convertView, ViewGroup parent) {
175         if ((mRowInfo == null) || (position > mRowInfo.size())) {
176             // If we have no row info, mAgendaAdapter returns the view.
177             return mAgendaAdapter.getView(position, convertView, parent);
178         }
179 
180         RowInfo row = mRowInfo.get(position);
181         if (row.mType == TYPE_DAY) {
182             ViewHolder holder = null;
183             View agendaDayView = null;
184             if ((convertView != null) && (convertView.getTag() != null)) {
185                 // Listview may get confused and pass in a different type of
186                 // view since we keep shifting data around. Not a big problem.
187                 Object tag = convertView.getTag();
188                 if (tag instanceof ViewHolder) {
189                     agendaDayView = convertView;
190                     holder = (ViewHolder) tag;
191                     holder.julianDay = row.mDay;
192                 }
193             }
194 
195             if (holder == null) {
196                 // Create a new AgendaView with a ViewHolder for fast access to
197                 // views w/o calling findViewById()
198                 holder = new ViewHolder();
199                 agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false);
200                 holder.dayView = (TextView) agendaDayView.findViewById(R.id.day);
201                 holder.dateView = (TextView) agendaDayView.findViewById(R.id.date);
202                 holder.julianDay = row.mDay;
203                 holder.grayed = false;
204                 agendaDayView.setTag(holder);
205             }
206 
207             // Re-use the member variable "mTime" which is set to the local
208             // time zone.
209             // It's difficult to find and update all these adapters when the
210             // home tz changes so check it here and update if needed.
211             String tz = Utils.getTimeZone(mContext, mTZUpdater);
212             if (!TextUtils.equals(tz, mTmpTime.timezone)) {
213                 mTimeZone = tz;
214                 mTmpTime = new Time(tz);
215             }
216 
217             // Build the text for the day of the week.
218             // Should be yesterday/today/tomorrow (if applicable) + day of the week
219 
220             Time date = mTmpTime;
221             long millis = date.setJulianDay(row.mDay);
222             int flags = DateUtils.FORMAT_SHOW_WEEKDAY;
223             mStringBuilder.setLength(0);
224 
225             String dayViewText = Utils.getDayOfWeekString(row.mDay, mTodayJulianDay, millis,
226                     mContext);
227 
228             // Build text for the date
229             // Format should be month day
230 
231             mStringBuilder.setLength(0);
232             flags = DateUtils.FORMAT_SHOW_DATE;
233             String dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis,
234                     flags, mTimeZone).toString();
235 
236             if (AgendaWindowAdapter.BASICLOG) {
237                 dayViewText += " P:" + position;
238                 dateViewText += " P:" + position;
239             }
240             holder.dayView.setText(dayViewText);
241             holder.dateView.setText(dateViewText);
242 
243             // Set the background of the view, it is grayed for day that are in the past and today
244             if (row.mDay > mTodayJulianDay) {
245                 agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_primary);
246                 holder.grayed = false;
247             } else {
248                 agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_secondary);
249                 holder.grayed = true;
250             }
251             return agendaDayView;
252         } else if (row.mType == TYPE_MEETING) {
253             View itemView = mAgendaAdapter.getView(row.mPosition, convertView, parent);
254             AgendaAdapter.ViewHolder holder = ((AgendaAdapter.ViewHolder) itemView.getTag());
255             TextView title = holder.title;
256             // The holder in the view stores information from the cursor, but the cursor has no
257             // notion of multi-day event and the start time of each instance of a multi-day event
258             // is the same.  RowInfo has the correct info , so take it from there.
259             holder.startTimeMilli = row.mEventStartTimeMilli;
260             boolean allDay = holder.allDay;
261             if (AgendaWindowAdapter.BASICLOG) {
262                 title.setText(title.getText() + " P:" + position);
263             } else {
264                 title.setText(title.getText());
265             }
266 
267             // if event in the past or started already, un-bold the title and set the background
268             if ((!allDay && row.mEventStartTimeMilli <= System.currentTimeMillis()) ||
269                     (allDay && row.mDay <= mTodayJulianDay)) {
270                 itemView.setBackgroundResource(R.drawable.agenda_item_bg_secondary);
271                 title.setTypeface(Typeface.DEFAULT);
272                 holder.grayed = true;
273             } else {
274                 itemView.setBackgroundResource(R.drawable.agenda_item_bg_primary);
275                 title.setTypeface(Typeface.DEFAULT_BOLD);
276                 holder.grayed = false;
277             }
278             holder.julianDay = row.mDay;
279             return itemView;
280         } else {
281             // Error
282             throw new IllegalStateException("Unknown event type:" + row.mType);
283         }
284     }
285 
clearDayHeaderInfo()286     public void clearDayHeaderInfo() {
287         mRowInfo = null;
288     }
289 
changeCursor(DayAdapterInfo info)290     public void changeCursor(DayAdapterInfo info) {
291         calculateDays(info);
292         mAgendaAdapter.changeCursor(info.cursor);
293     }
294 
calculateDays(DayAdapterInfo dayAdapterInfo)295     public void calculateDays(DayAdapterInfo dayAdapterInfo) {
296         Cursor cursor = dayAdapterInfo.cursor;
297         ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
298         int prevStartDay = -1;
299 
300         Time tempTime = new Time(mTimeZone);
301         long now = System.currentTimeMillis();
302         tempTime.set(now);
303         mTodayJulianDay = Time.getJulianDay(now, tempTime.gmtoff);
304 
305         LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>();
306         for (int position = 0; cursor.moveToNext(); position++) {
307             int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY);
308             long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID);
309             long startTime =  cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
310             long endTime =  cursor.getLong(AgendaWindowAdapter.INDEX_END);
311             long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID);
312             boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
313             if (allDay) {
314                 startTime = Utils.convertAlldayUtcToLocal(tempTime, startTime, mTimeZone);
315                 endTime = Utils.convertAlldayUtcToLocal(tempTime, endTime, mTimeZone);
316             }
317             // Skip over the days outside of the adapter's range
318             startDay = Math.max(startDay, dayAdapterInfo.start);
319             // Make sure event's start time is not before the start of the day
320             // (setJulianDay sets the time to 12:00am)
321             long adapterStartTime = tempTime.setJulianDay(startDay);
322             startTime = Math.max(startTime, adapterStartTime);
323 
324             if (startDay != prevStartDay) {
325                 // Check if we skipped over any empty days
326                 if (prevStartDay == -1) {
327                     rowInfo.add(new RowInfo(TYPE_DAY, startDay));
328                 } else {
329                     // If there are any multiple-day events that span the empty
330                     // range of days, then create day headers and events for
331                     // those multiple-day events.
332                     boolean dayHeaderAdded = false;
333                     for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) {
334                         dayHeaderAdded = false;
335                         Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
336                         while (iter.hasNext()) {
337                             MultipleDayInfo info = iter.next();
338                             // If this event has ended then remove it from the
339                             // list.
340                             if (info.mEndDay < currentDay) {
341                                 iter.remove();
342                                 continue;
343                             }
344 
345                             // If this is the first event for the day, then
346                             // insert a day header.
347                             if (!dayHeaderAdded) {
348                                 rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
349                                 dayHeaderAdded = true;
350                             }
351                             long nextMidnight = Utils.getNextMidnight(tempTime,
352                                     info.mEventStartTimeMilli, mTimeZone);
353 
354                             long infoEndTime = (info.mEndDay == currentDay) ?
355                                     info.mEventEndTimeMilli : nextMidnight;
356                             rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
357                                     info.mEventId, info.mEventStartTimeMilli,
358                                     infoEndTime, info.mInstanceId, info.mAllDay));
359 
360                             info.mEventStartTimeMilli = nextMidnight;
361                         }
362                     }
363 
364                     // If the day header was not added for the start day, then
365                     // add it now.
366                     if (!dayHeaderAdded) {
367                         rowInfo.add(new RowInfo(TYPE_DAY, startDay));
368                     }
369                 }
370                 prevStartDay = startDay;
371             }
372 
373             // If this event spans multiple days, then add it to the multipleDay
374             // list.
375             int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY);
376 
377             // Skip over the days outside of the adapter's range
378             endDay = Math.min(endDay, dayAdapterInfo.end);
379             if (endDay > startDay) {
380                 long nextMidnight = Utils.getNextMidnight(tempTime, startTime, mTimeZone);
381                 multipleDayList.add(new MultipleDayInfo(position, endDay, id, nextMidnight,
382                         endTime, instanceId, allDay));
383                 // Add in the event for this cursor position - since it is the start of a multi-day
384                 // event, the end time is midnight
385                 rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime,
386                         nextMidnight, instanceId, allDay));
387             } else {
388                 // Add in the event for this cursor position
389                 rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, endTime,
390                         instanceId, allDay));
391             }
392         }
393 
394         // There are no more cursor events but we might still have multiple-day
395         // events left.  So create day headers and events for those.
396         if (prevStartDay > 0) {
397             for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end;
398                     currentDay++) {
399                 boolean dayHeaderAdded = false;
400                 Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
401                 while (iter.hasNext()) {
402                     MultipleDayInfo info = iter.next();
403                     // If this event has ended then remove it from the
404                     // list.
405                     if (info.mEndDay < currentDay) {
406                         iter.remove();
407                         continue;
408                     }
409 
410                     // If this is the first event for the day, then
411                     // insert a day header.
412                     if (!dayHeaderAdded) {
413                         rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
414                         dayHeaderAdded = true;
415                     }
416                     long nextMidnight = Utils.getNextMidnight(tempTime, info.mEventStartTimeMilli,
417                             mTimeZone);
418                     long infoEndTime =
419                             (info.mEndDay == currentDay) ? info.mEventEndTimeMilli : nextMidnight;
420                     rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
421                             info.mEventId, info.mEventStartTimeMilli, infoEndTime,
422                             info.mInstanceId, info.mAllDay));
423 
424                     info.mEventStartTimeMilli = nextMidnight;
425                 }
426             }
427         }
428         mRowInfo = rowInfo;
429     }
430 
431     private static class RowInfo {
432         // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
433         final int mType;
434 
435         final int mDay;          // Julian day
436         final int mPosition;     // cursor position (not used for TYPE_DAY)
437         // This is used to mark a day header as the first day with events that is "today"
438         // or later. This flag is used by the adapter to create a view with a visual separator
439         // between the past and the present/future
440         boolean mFirstDayAfterYesterday;
441         final long mEventId;
442         final long mEventStartTimeMilli;
443         final long mEventEndTimeMilli;
444         final long mInstanceId;
445         final boolean mAllDay;
446 
RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime, long instanceId, boolean allDay)447         RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime,
448                 long instanceId, boolean allDay) {
449             mType = type;
450             mDay = julianDay;
451             mPosition = position;
452             mEventId = id;
453             mEventStartTimeMilli = startTime;
454             mEventEndTimeMilli = endTime;
455             mFirstDayAfterYesterday = false;
456             mInstanceId = instanceId;
457             mAllDay = allDay;
458         }
459 
RowInfo(int type, int julianDay)460         RowInfo(int type, int julianDay) {
461             mType = type;
462             mDay = julianDay;
463             mPosition = 0;
464             mEventId = 0;
465             mEventStartTimeMilli = 0;
466             mEventEndTimeMilli = 0;
467             mFirstDayAfterYesterday = false;
468             mInstanceId = -1;
469             mAllDay = false;
470         }
471     }
472 
473     private static class MultipleDayInfo {
474         final int mPosition;
475         final int mEndDay;
476         final long mEventId;
477         long mEventStartTimeMilli;
478         long mEventEndTimeMilli;
479         final long mInstanceId;
480         final boolean mAllDay;
481 
MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime, long instanceId, boolean allDay)482         MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime,
483                 long instanceId, boolean allDay) {
484             mPosition = position;
485             mEndDay = endDay;
486             mEventId = id;
487             mEventStartTimeMilli = startTime;
488             mEventEndTimeMilli = endTime;
489             mInstanceId = instanceId;
490             mAllDay = allDay;
491         }
492     }
493 
494     /**
495      * Finds the position in the cursor of the event that best matches the time and Id.
496      * It will try to find the event that has the specified id and start time, if such event
497      * doesn't exist, it will return the event with a matching id that is closest to the start time.
498      * If the id doesn't exist, it will return the event with start time closest to the specified
499      * time.
500      * @param time - start of event in milliseconds (or any arbitrary time if event id is unknown)
501      * @param id - Event id (-1 if unknown).
502      * @return Position of event (if found) or position of nearest event according to the time.
503      *         Zero if no event found
504      */
findEventPositionNearestTime(Time time, long id)505     public int findEventPositionNearestTime(Time time, long id) {
506         if (mRowInfo == null) {
507             return 0;
508         }
509         long millis = time.toMillis(false /* use isDst */);
510         long minDistance =  Integer.MAX_VALUE;  // some big number
511         long idFoundMinDistance =  Integer.MAX_VALUE;  // some big number
512         int minIndex = 0;
513         int idFoundMinIndex = 0;
514         int eventInTimeIndex = -1;
515         int allDayEventInTimeIndex = -1;
516         int allDayEventDay = 0;
517         int minDay = 0;
518         boolean idFound = false;
519         int len = mRowInfo.size();
520 
521         // Loop through the events and find the best match
522         // 1. Event id and start time matches requested id and time
523         // 2. Event id matches and closest time
524         // 3. No event id match , time matches a all day event (midnight)
525         // 4. No event id match , time is between event start and end
526         // 5. No event id match , all day event
527         // 6. The closest event to the requested time
528 
529         for (int index = 0; index < len; index++) {
530             RowInfo row = mRowInfo.get(index);
531             if (row.mType == TYPE_DAY) {
532                 continue;
533             }
534 
535             // Found exact match - done
536             if (row.mEventId == id) {
537                 if (row.mEventStartTimeMilli == millis) {
538                     return index;
539                 }
540 
541                 // Not an exact match, Save event index if it is the closest to time so far
542                 long distance = Math.abs(millis - row.mEventStartTimeMilli);
543                 if (distance < idFoundMinDistance) {
544                     idFoundMinDistance = distance;
545                     idFoundMinIndex = index;
546                 }
547                 idFound = true;
548             }
549             if (!idFound) {
550                 // Found an event that contains the requested time
551                 if (millis >= row.mEventStartTimeMilli && millis <= row.mEventEndTimeMilli) {
552                     if (row.mAllDay) {
553                         if (allDayEventInTimeIndex == -1) {
554                             allDayEventInTimeIndex = index;
555                             allDayEventDay = row.mDay;
556                         }
557                     } else if (eventInTimeIndex == -1){
558                         eventInTimeIndex = index;
559                     }
560                 } else if (eventInTimeIndex == -1){
561                     // Save event index if it is the closest to time so far
562                     long distance = Math.abs(millis - row.mEventStartTimeMilli);
563                     if (distance < minDistance) {
564                         minDistance = distance;
565                         minIndex = index;
566                         minDay = row.mDay;
567                     }
568                 }
569             }
570         }
571         // We didn't find an exact match so take the best matching event
572         // Closest event with the same id
573         if (idFound) {
574             return idFoundMinIndex;
575         }
576         // Event which occurs at the searched time
577         if (eventInTimeIndex != -1) {
578             return eventInTimeIndex;
579         // All day event which occurs at the same day of the searched time as long as there is
580         // no regular event at the same day
581         } else if (allDayEventInTimeIndex != -1 && minDay != allDayEventDay) {
582             return allDayEventInTimeIndex;
583         }
584         // Closest event
585         return minIndex;
586     }
587 
588 
589     /**
590      * Returns a flag indicating if this position is the first day after "yesterday" that has
591      * events in it.
592      *
593      * @return a flag indicating if this is the "first day after yesterday"
594      */
isFirstDayAfterYesterday(int position)595     public boolean isFirstDayAfterYesterday(int position) {
596         int headerPos = getHeaderPosition(position);
597         RowInfo row = mRowInfo.get(headerPos);
598         if (row != null) {
599             return row.mFirstDayAfterYesterday;
600         }
601         return false;
602     }
603 
604     /**
605      * Finds the Julian day containing the event at the given position.
606      *
607      * @param position the list position of an event
608      * @return the Julian day containing that event
609      */
findJulianDayFromPosition(int position)610     public int findJulianDayFromPosition(int position) {
611         if (mRowInfo == null || position < 0) {
612             return 0;
613         }
614 
615         int len = mRowInfo.size();
616         if (position >= len) return 0;  // no row info at this position
617 
618         for (int index = position; index >= 0; index--) {
619             RowInfo row = mRowInfo.get(index);
620             if (row.mType == TYPE_DAY) {
621                 return row.mDay;
622             }
623         }
624         return 0;
625     }
626 
627     /**
628      * Marks the current row as the first day that has events after "yesterday".
629      * Used to mark the separation between the past and the present/future
630      *
631      * @param position in the adapter
632      */
setAsFirstDayAfterYesterday(int position)633     public void setAsFirstDayAfterYesterday(int position) {
634         if (mRowInfo == null || position < 0 || position > mRowInfo.size()) {
635             return;
636         }
637         RowInfo row = mRowInfo.get(position);
638         row.mFirstDayAfterYesterday = true;
639     }
640 
641     /**
642      * Converts a list position to a cursor position.  The list contains
643      * day headers as well as events.  The cursor contains only events.
644      *
645      * @param listPos the list position of an event
646      * @return the corresponding cursor position of that event
647      *         if the position point to day header , it will give the position of the next event
648      *         negated.
649      */
getCursorPosition(int listPos)650     public int getCursorPosition(int listPos) {
651         if (mRowInfo != null && listPos >= 0) {
652             RowInfo row = mRowInfo.get(listPos);
653             if (row.mType == TYPE_MEETING) {
654                 return row.mPosition;
655             } else {
656                 int nextPos = listPos + 1;
657                 if (nextPos < mRowInfo.size()) {
658                     nextPos = getCursorPosition(nextPos);
659                     if (nextPos >= 0) {
660                         return -nextPos;
661                     }
662                 }
663             }
664         }
665         return Integer.MIN_VALUE;
666     }
667 
668     @Override
areAllItemsEnabled()669     public boolean areAllItemsEnabled() {
670         return false;
671     }
672 
673     @Override
isEnabled(int position)674     public boolean isEnabled(int position) {
675         if (mRowInfo != null && position < mRowInfo.size()) {
676             RowInfo row = mRowInfo.get(position);
677             return row.mType == TYPE_MEETING;
678         }
679         return true;
680     }
681 }
682