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