Home | History | Annotate | Download | only in agenda
      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 Formatter mFormatter;
     56     private StringBuilder mStringBuilder;
     57 
     58     static class ViewHolder {
     59         TextView dayView;
     60         TextView dateView;
     61         int julianDay;
     62         boolean grayed;
     63     }
     64 
     65     private 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 
     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 
     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
     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
    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 
    120     public int getCount() {
    121         if (mRowInfo != null) {
    122             return mRowInfo.size();
    123         }
    124         return mAgendaAdapter.getCount();
    125     }
    126 
    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 
    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
    152     public int getViewTypeCount() {
    153         return TYPE_LAST;
    154     }
    155 
    156     @Override
    157     public int getItemViewType(int position) {
    158         return mRowInfo != null && mRowInfo.size() > position ?
    159                 mRowInfo.get(position).mType : TYPE_DAY;
    160     }
    161 
    162     public boolean isDayHeaderView(int position) {
    163         return (getItemViewType(position) == TYPE_DAY);
    164     }
    165 
    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             long eventStartTime = holder.startTimeMilli;
    249             boolean allDay = holder.allDay;
    250             if (AgendaWindowAdapter.BASICLOG) {
    251                 title.setText(title.getText() + " P:" + position);
    252             } else {
    253                 title.setText(title.getText());
    254             }
    255 
    256             // if event in the past or started already, un-bold the title and set the background
    257             if ((!allDay && eventStartTime <= System.currentTimeMillis()) ||
    258                     (allDay && row.mDay <= mTodayJulianDay)) {
    259                 itemView.setBackgroundResource(R.drawable.agenda_item_bg_secondary);
    260                 title.setTypeface(Typeface.DEFAULT);
    261                 holder.grayed = true;
    262             } else {
    263                 itemView.setBackgroundResource(R.drawable.agenda_item_bg_primary);
    264                 title.setTypeface(Typeface.DEFAULT_BOLD);
    265                 holder.grayed = false;
    266             }
    267             holder.julianDay = row.mDay;
    268             return itemView;
    269         } else {
    270             // Error
    271             throw new IllegalStateException("Unknown event type:" + row.mType);
    272         }
    273     }
    274 
    275     public void clearDayHeaderInfo() {
    276         mRowInfo = null;
    277     }
    278 
    279     public void changeCursor(DayAdapterInfo info) {
    280         calculateDays(info);
    281         mAgendaAdapter.changeCursor(info.cursor);
    282     }
    283 
    284     public void calculateDays(DayAdapterInfo dayAdapterInfo) {
    285         Cursor cursor = dayAdapterInfo.cursor;
    286         ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
    287         int prevStartDay = -1;
    288 
    289         Time tempTime = new Time(mTimeZone);
    290         long now = System.currentTimeMillis();
    291         tempTime.set(now);
    292         mTodayJulianDay = Time.getJulianDay(now, tempTime.gmtoff);
    293 
    294         LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>();
    295         for (int position = 0; cursor.moveToNext(); position++) {
    296             int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY);
    297             long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID);
    298             long startTime =  cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN);
    299             long endTime =  cursor.getLong(AgendaWindowAdapter.INDEX_END);
    300             long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID);
    301             boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0;
    302             if (allDay) {
    303                 startTime = Utils.convertAlldayUtcToLocal(tempTime, startTime, mTimeZone);
    304                 endTime = Utils.convertAlldayUtcToLocal(tempTime, endTime, mTimeZone);
    305             }
    306             // Skip over the days outside of the adapter's range
    307             startDay = Math.max(startDay, dayAdapterInfo.start);
    308             // Make sure event's start time is not before the start of the day
    309             // (setJulianDay sets the time to 12:00am)
    310             long adapterStartTime = tempTime.setJulianDay(startDay);
    311             startTime = Math.max(startTime, adapterStartTime);
    312 
    313             if (startDay != prevStartDay) {
    314                 // Check if we skipped over any empty days
    315                 if (prevStartDay == -1) {
    316                     rowInfo.add(new RowInfo(TYPE_DAY, startDay));
    317                 } else {
    318                     // If there are any multiple-day events that span the empty
    319                     // range of days, then create day headers and events for
    320                     // those multiple-day events.
    321                     boolean dayHeaderAdded = false;
    322                     for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) {
    323                         dayHeaderAdded = false;
    324                         Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
    325                         while (iter.hasNext()) {
    326                             MultipleDayInfo info = iter.next();
    327                             // If this event has ended then remove it from the
    328                             // list.
    329                             if (info.mEndDay < currentDay) {
    330                                 iter.remove();
    331                                 continue;
    332                             }
    333 
    334                             // If this is the first event for the day, then
    335                             // insert a day header.
    336                             if (!dayHeaderAdded) {
    337                                 rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
    338                                 dayHeaderAdded = true;
    339                             }
    340                             long nextMidnight = Utils.getNextMidnight(tempTime,
    341                                     info.mEventStartTimeMilli, mTimeZone);
    342 
    343                             long infoEndTime = (info.mEndDay == currentDay) ?
    344                                     info.mEventEndTimeMilli : nextMidnight;
    345                             rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
    346                                     info.mEventId, info.mEventStartTimeMilli,
    347                                     infoEndTime, info.mInstanceId, info.mAllDay));
    348 
    349                             info.mEventStartTimeMilli = nextMidnight;
    350                         }
    351                     }
    352 
    353                     // If the day header was not added for the start day, then
    354                     // add it now.
    355                     if (!dayHeaderAdded) {
    356                         rowInfo.add(new RowInfo(TYPE_DAY, startDay));
    357                     }
    358                 }
    359                 prevStartDay = startDay;
    360             }
    361 
    362             // Add in the event for this cursor position
    363             rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, endTime,
    364                     instanceId, allDay));
    365 
    366             // If this event spans multiple days, then add it to the multipleDay
    367             // list.
    368             int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY);
    369 
    370             // Skip over the days outside of the adapter's range
    371             endDay = Math.min(endDay, dayAdapterInfo.end);
    372             if (endDay > startDay) {
    373                 multipleDayList.add(new MultipleDayInfo(position, endDay, id,
    374                         Utils.getNextMidnight(tempTime, startTime, mTimeZone),
    375                         endTime, instanceId, allDay));
    376             }
    377         }
    378 
    379         // There are no more cursor events but we might still have multiple-day
    380         // events left.  So create day headers and events for those.
    381         if (prevStartDay > 0) {
    382             for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end;
    383                     currentDay++) {
    384                 boolean dayHeaderAdded = false;
    385                 Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
    386                 while (iter.hasNext()) {
    387                     MultipleDayInfo info = iter.next();
    388                     // If this event has ended then remove it from the
    389                     // list.
    390                     if (info.mEndDay < currentDay) {
    391                         iter.remove();
    392                         continue;
    393                     }
    394 
    395                     // If this is the first event for the day, then
    396                     // insert a day header.
    397                     if (!dayHeaderAdded) {
    398                         rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
    399                         dayHeaderAdded = true;
    400                     }
    401                     long nextMidnight = Utils.getNextMidnight(tempTime, info.mEventStartTimeMilli,
    402                             mTimeZone);
    403                     long infoEndTime =
    404                             (info.mEndDay == currentDay) ? info.mEventEndTimeMilli : nextMidnight;
    405                     rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition,
    406                             info.mEventId, info.mEventStartTimeMilli, infoEndTime,
    407                             info.mInstanceId, info.mAllDay));
    408 
    409                     info.mEventStartTimeMilli = nextMidnight;
    410                 }
    411             }
    412         }
    413         mRowInfo = rowInfo;
    414     }
    415 
    416     private static class RowInfo {
    417         // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
    418         final int mType;
    419 
    420         final int mDay;          // Julian day
    421         final int mPosition;     // cursor position (not used for TYPE_DAY)
    422         // This is used to mark a day header as the first day with events that is "today"
    423         // or later. This flag is used by the adapter to create a view with a visual separator
    424         // between the past and the present/future
    425         boolean mFirstDayAfterYesterday;
    426         final long mEventId;
    427         final long mEventStartTimeMilli;
    428         final long mEventEndTimeMilli;
    429         final long mInstanceId;
    430         final boolean mAllDay;
    431 
    432         RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime,
    433                 long instanceId, boolean allDay) {
    434             mType = type;
    435             mDay = julianDay;
    436             mPosition = position;
    437             mEventId = id;
    438             mEventStartTimeMilli = startTime;
    439             mEventEndTimeMilli = endTime;
    440             mFirstDayAfterYesterday = false;
    441             mInstanceId = instanceId;
    442             mAllDay = allDay;
    443         }
    444 
    445         RowInfo(int type, int julianDay) {
    446             mType = type;
    447             mDay = julianDay;
    448             mPosition = 0;
    449             mEventId = 0;
    450             mEventStartTimeMilli = 0;
    451             mEventEndTimeMilli = 0;
    452             mFirstDayAfterYesterday = false;
    453             mInstanceId = -1;
    454             mAllDay = false;
    455         }
    456     }
    457 
    458     private static class MultipleDayInfo {
    459         final int mPosition;
    460         final int mEndDay;
    461         final long mEventId;
    462         long mEventStartTimeMilli;
    463         long mEventEndTimeMilli;
    464         final long mInstanceId;
    465         final boolean mAllDay;
    466 
    467         MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime,
    468                 long instanceId, boolean allDay) {
    469             mPosition = position;
    470             mEndDay = endDay;
    471             mEventId = id;
    472             mEventStartTimeMilli = startTime;
    473             mEventEndTimeMilli = endTime;
    474             mInstanceId = instanceId;
    475             mAllDay = allDay;
    476         }
    477     }
    478 
    479     /**
    480      * Finds the position in the cursor of the event that best matches the time and Id.
    481      * It will try to find the event that has the specified id and start time, if such event
    482      * doesn't exist, it will return the event with a matching id that is closest to the start time.
    483      * If the id doesn't exist, it will return the event with start time closest to the specified
    484      * time.
    485      * @param time - start of event in milliseconds (or any arbitrary time if event id is unknown)
    486      * @param id - Event id (-1 if unknown).
    487      * @return Position of event (if found) or position of nearest event according to the time.
    488      *         Zero if no event found
    489      */
    490     public int findEventPositionNearestTime(Time time, long id) {
    491         if (mRowInfo == null) {
    492             return 0;
    493         }
    494         long millis = time.toMillis(false /* use isDst */);
    495         long minDistance =  Integer.MAX_VALUE;  // some big number
    496         long IdFoundMinDistance =  Integer.MAX_VALUE;  // some big number
    497         int minIndex = 0;
    498         int idFoundMinIndex = 0;
    499         int eventInTimeIndex = -1;
    500         int allDayEventInTimeIndex = -1;
    501         int allDayEventDay = 0;
    502         int minDay = 0;
    503         boolean idFound = false;
    504         int len = mRowInfo.size();
    505 
    506         // Loop through the events and find the best match
    507         // 1. Event id and start time matches requested id and time
    508         // 2. Event id matches and closest time
    509         // 3. No event id match , time matches a all day event (midnight)
    510         // 4. No event id match , time is between event start and end
    511         // 5. No event id match , all day event
    512         // 6. The closest event to the requested time
    513 
    514         for (int index = 0; index < len; index++) {
    515             RowInfo row = mRowInfo.get(index);
    516             if (row.mType == TYPE_DAY) {
    517                 continue;
    518             }
    519 
    520             // Found exact match - done
    521             if (row.mEventId == id) {
    522                 if (row.mEventStartTimeMilli == millis) {
    523                     return index;
    524                 }
    525 
    526                 // Not an exact match, Save event index if it is the closest to time so far
    527                 long distance = Math.abs(millis - row.mEventStartTimeMilli);
    528                 if (distance < minDistance) {
    529                     IdFoundMinDistance = distance;
    530                     idFoundMinIndex = index;
    531                 }
    532                 idFound = true;
    533             }
    534             if (!idFound) {
    535                 // Found an event that contains the requested time
    536                 if (millis >= row.mEventStartTimeMilli && millis <= row.mEventEndTimeMilli) {
    537                     if (row.mAllDay) {
    538                         if (millis == row.mEventStartTimeMilli) {
    539                             return index;
    540                         }
    541                         allDayEventInTimeIndex = index;
    542                         allDayEventDay = row.mDay;
    543                     } else {
    544                         eventInTimeIndex = index;
    545                     }
    546                 } else {
    547                     // Save event index if it is the closest to time so far
    548                     long distance = Math.abs(millis - row.mEventStartTimeMilli);
    549                     if (distance < minDistance) {
    550                         minDistance = distance;
    551                         minIndex = index;
    552                         minDay = row.mDay;
    553                     }
    554                 }
    555             }
    556         }
    557         // We didn't find an exact match so take the best matching event
    558         if (idFound) {
    559             return idFoundMinIndex;
    560         }
    561         if (eventInTimeIndex != -1) {
    562             return eventInTimeIndex;
    563         } else if (allDayEventInTimeIndex != -1 && minDay != allDayEventDay) {
    564             return allDayEventInTimeIndex;
    565         }
    566         return minIndex;
    567     }
    568 
    569 
    570     /**
    571      * Returns a flag indicating if this position is the first day after "yesterday" that has
    572      * events in it.
    573      *
    574      * @return a flag indicating if this is the "first day after yesterday"
    575      */
    576     public boolean isFirstDayAfterYesterday(int position) {
    577         int headerPos = getHeaderPosition(position);
    578         RowInfo row = mRowInfo.get(headerPos);
    579         if (row != null) {
    580             return row.mFirstDayAfterYesterday;
    581         }
    582         return false;
    583     }
    584 
    585     /**
    586      * Finds the Julian day containing the event at the given position.
    587      *
    588      * @param position the list position of an event
    589      * @return the Julian day containing that event
    590      */
    591     public int findJulianDayFromPosition(int position) {
    592         if (mRowInfo == null || position < 0) {
    593             return 0;
    594         }
    595 
    596         int len = mRowInfo.size();
    597         if (position >= len) return 0;  // no row info at this position
    598 
    599         for (int index = position; index >= 0; index--) {
    600             RowInfo row = mRowInfo.get(index);
    601             if (row.mType == TYPE_DAY) {
    602                 return row.mDay;
    603             }
    604         }
    605         return 0;
    606     }
    607 
    608     /**
    609      * Marks the current row as the first day that has events after "yesterday".
    610      * Used to mark the separation between the past and the present/future
    611      *
    612      * @param position in the adapter
    613      */
    614     public void setAsFirstDayAfterYesterday(int position) {
    615         if (mRowInfo == null || position < 0 || position > mRowInfo.size()) {
    616             return;
    617         }
    618         RowInfo row = mRowInfo.get(position);
    619         row.mFirstDayAfterYesterday = true;
    620     }
    621 
    622     /**
    623      * Converts a list position to a cursor position.  The list contains
    624      * day headers as well as events.  The cursor contains only events.
    625      *
    626      * @param listPos the list position of an event
    627      * @return the corresponding cursor position of that event
    628      *         if the position point to day header , it will give the position of the next event
    629      *         negated.
    630      */
    631     public int getCursorPosition(int listPos) {
    632         if (mRowInfo != null && listPos >= 0) {
    633             RowInfo row = mRowInfo.get(listPos);
    634             if (row.mType == TYPE_MEETING) {
    635                 return row.mPosition;
    636             } else {
    637                 int nextPos = listPos + 1;
    638                 if (nextPos < mRowInfo.size()) {
    639                     nextPos = getCursorPosition(nextPos);
    640                     if (nextPos >= 0) {
    641                         return -nextPos;
    642                     }
    643                 }
    644             }
    645         }
    646         return Integer.MIN_VALUE;
    647     }
    648 
    649     @Override
    650     public boolean areAllItemsEnabled() {
    651         return false;
    652     }
    653 
    654     @Override
    655     public boolean isEnabled(int position) {
    656         if (mRowInfo != null && position < mRowInfo.size()) {
    657             RowInfo row = mRowInfo.get(position);
    658             return row.mType == TYPE_MEETING;
    659         }
    660         return true;
    661     }
    662 }
    663