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