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 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 
     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             // 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 
    278     public void clearDayHeaderInfo() {
    279         mRowInfo = null;
    280     }
    281 
    282     public void changeCursor(DayAdapterInfo info) {
    283         calculateDays(info);
    284         mAgendaAdapter.changeCursor(info.cursor);
    285     }
    286 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    657     public boolean areAllItemsEnabled() {
    658         return false;
    659     }
    660 
    661     @Override
    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