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