Home | History | Annotate | Download | only in calendar
      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;
     18 
     19 import com.android.calendar.AgendaWindowAdapter.DayAdapterInfo;
     20 
     21 import android.content.Context;
     22 import android.database.Cursor;
     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 java.util.ArrayList;
     32 import java.util.Iterator;
     33 import java.util.LinkedList;
     34 
     35 public class AgendaByDayAdapter extends BaseAdapter {
     36     private static final int TYPE_DAY = 0;
     37     private static final int TYPE_MEETING = 1;
     38     static final int TYPE_LAST = 2;
     39 
     40     private final Context mContext;
     41     private final AgendaAdapter mAgendaAdapter;
     42     private final LayoutInflater mInflater;
     43     private ArrayList<RowInfo> mRowInfo;
     44     private int mTodayJulianDay;
     45 
     46     // Placeholder if we need some code for updating the tz later.
     47     private Runnable mUpdateTZ = null;
     48 
     49     static class ViewHolder {
     50         TextView dateView;
     51     }
     52 
     53     public AgendaByDayAdapter(Context context) {
     54         mContext = context;
     55         mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item);
     56         mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     57     }
     58 
     59     public int getCount() {
     60         if (mRowInfo != null) {
     61             return mRowInfo.size();
     62         }
     63         return mAgendaAdapter.getCount();
     64     }
     65 
     66     public Object getItem(int position) {
     67         if (mRowInfo != null) {
     68             RowInfo row = mRowInfo.get(position);
     69             if (row.mType == TYPE_DAY) {
     70                 return row;
     71             } else {
     72                 return mAgendaAdapter.getItem(row.mData);
     73             }
     74         }
     75         return mAgendaAdapter.getItem(position);
     76     }
     77 
     78     public long getItemId(int position) {
     79         if (mRowInfo != null) {
     80             RowInfo row = mRowInfo.get(position);
     81             if (row.mType == TYPE_DAY) {
     82                 return -position;
     83             } else {
     84                 return mAgendaAdapter.getItemId(row.mData);
     85             }
     86         }
     87         return mAgendaAdapter.getItemId(position);
     88     }
     89 
     90     @Override
     91     public int getViewTypeCount() {
     92         return TYPE_LAST;
     93     }
     94 
     95     @Override
     96     public int getItemViewType(int position) {
     97         return mRowInfo != null && mRowInfo.size() > position ?
     98                 mRowInfo.get(position).mType : TYPE_DAY;
     99     }
    100 
    101     public View getView(int position, View convertView, ViewGroup parent) {
    102         if ((mRowInfo == null) || (position > mRowInfo.size())) {
    103             // If we have no row info, mAgendaAdapter returns the view.
    104             return mAgendaAdapter.getView(position, convertView, parent);
    105         }
    106 
    107         RowInfo row = mRowInfo.get(position);
    108         if (row.mType == TYPE_DAY) {
    109             ViewHolder holder = null;
    110             View agendaDayView = null;
    111             if ((convertView != null) && (convertView.getTag() != null)) {
    112                 // Listview may get confused and pass in a different type of
    113                 // view since we keep shifting data around. Not a big problem.
    114                 Object tag = convertView.getTag();
    115                 if (tag instanceof ViewHolder) {
    116                     agendaDayView = convertView;
    117                     holder = (ViewHolder) tag;
    118                 }
    119             }
    120 
    121             if (holder == null) {
    122                 // Create a new AgendaView with a ViewHolder for fast access to
    123                 // views w/o calling findViewById()
    124                 holder = new ViewHolder();
    125                 agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false);
    126                 holder.dateView = (TextView) agendaDayView.findViewById(R.id.date);
    127                 agendaDayView.setTag(holder);
    128             }
    129 
    130             Time date = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
    131             long millis = date.setJulianDay(row.mData);
    132             int flags = DateUtils.FORMAT_SHOW_YEAR
    133                     | DateUtils.FORMAT_SHOW_DATE;
    134 
    135             String dateViewText;
    136             if (row.mData == mTodayJulianDay) {
    137                 dateViewText = mContext.getString(R.string.agenda_today, Utils.formatDateRange(
    138                         mContext, millis, millis, flags).toString());
    139             } else {
    140                 flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
    141                 dateViewText = Utils.formatDateRange(mContext, millis, millis,
    142                         flags).toString();
    143             }
    144 
    145             if (AgendaWindowAdapter.BASICLOG) {
    146                 dateViewText += " P:" + position;
    147             }
    148             holder.dateView.setText(dateViewText);
    149 
    150             return agendaDayView;
    151         } else if (row.mType == TYPE_MEETING) {
    152             View x = mAgendaAdapter.getView(row.mData, convertView, parent);
    153             TextView y = ((AgendaAdapter.ViewHolder) x.getTag()).title;
    154             if (AgendaWindowAdapter.BASICLOG) {
    155                 y.setText(y.getText() + " P:" + position);
    156             } else {
    157                 y.setText(y.getText());
    158             }
    159             return x;
    160         } else {
    161             // Error
    162             throw new IllegalStateException("Unknown event type:" + row.mType);
    163         }
    164     }
    165 
    166     public void clearDayHeaderInfo() {
    167         mRowInfo = null;
    168     }
    169 
    170     public void changeCursor(DayAdapterInfo info) {
    171         calculateDays(info);
    172         mAgendaAdapter.changeCursor(info.cursor);
    173     }
    174 
    175     public void calculateDays(DayAdapterInfo dayAdapterInfo) {
    176         Cursor cursor = dayAdapterInfo.cursor;
    177         ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>();
    178         int prevStartDay = -1;
    179         Time time = new Time(Utils.getTimeZone(mContext, mUpdateTZ));
    180         long now = System.currentTimeMillis();
    181         time.set(now);
    182         mTodayJulianDay = Time.getJulianDay(now, time.gmtoff);
    183         LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>();
    184         for (int position = 0; cursor.moveToNext(); position++) {
    185             int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY);
    186 
    187             // Skip over the days outside of the adapter's range
    188             startDay = Math.max(startDay, dayAdapterInfo.start);
    189 
    190             if (startDay != prevStartDay) {
    191                 // Check if we skipped over any empty days
    192                 if (prevStartDay == -1) {
    193                     rowInfo.add(new RowInfo(TYPE_DAY, startDay));
    194                 } else {
    195                     // If there are any multiple-day events that span the empty
    196                     // range of days, then create day headers and events for
    197                     // those multiple-day events.
    198                     boolean dayHeaderAdded = false;
    199                     for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) {
    200                         dayHeaderAdded = false;
    201                         Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
    202                         while (iter.hasNext()) {
    203                             MultipleDayInfo info = iter.next();
    204                             // If this event has ended then remove it from the
    205                             // list.
    206                             if (info.mEndDay < currentDay) {
    207                                 iter.remove();
    208                                 continue;
    209                             }
    210 
    211                             // If this is the first event for the day, then
    212                             // insert a day header.
    213                             if (!dayHeaderAdded) {
    214                                 rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
    215                                 dayHeaderAdded = true;
    216                             }
    217                             rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
    218                         }
    219                     }
    220 
    221                     // If the day header was not added for the start day, then
    222                     // add it now.
    223                     if (!dayHeaderAdded) {
    224                         rowInfo.add(new RowInfo(TYPE_DAY, startDay));
    225                     }
    226                 }
    227                 prevStartDay = startDay;
    228             }
    229 
    230             // Add in the event for this cursor position
    231             rowInfo.add(new RowInfo(TYPE_MEETING, position));
    232 
    233             // If this event spans multiple days, then add it to the multipleDay
    234             // list.
    235             int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY);
    236 
    237             // Skip over the days outside of the adapter's range
    238             endDay = Math.min(endDay, dayAdapterInfo.end);
    239             if (endDay > startDay) {
    240                 multipleDayList.add(new MultipleDayInfo(position, endDay));
    241             }
    242         }
    243 
    244         // There are no more cursor events but we might still have multiple-day
    245         // events left.  So create day headers and events for those.
    246         if (prevStartDay > 0) {
    247             for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end;
    248                     currentDay++) {
    249                 boolean dayHeaderAdded = false;
    250                 Iterator<MultipleDayInfo> iter = multipleDayList.iterator();
    251                 while (iter.hasNext()) {
    252                     MultipleDayInfo info = iter.next();
    253                     // If this event has ended then remove it from the
    254                     // list.
    255                     if (info.mEndDay < currentDay) {
    256                         iter.remove();
    257                         continue;
    258                     }
    259 
    260                     // If this is the first event for the day, then
    261                     // insert a day header.
    262                     if (!dayHeaderAdded) {
    263                         rowInfo.add(new RowInfo(TYPE_DAY, currentDay));
    264                         dayHeaderAdded = true;
    265                     }
    266                     rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
    267                 }
    268             }
    269         }
    270         mRowInfo = rowInfo;
    271     }
    272 
    273     static class RowInfo {
    274         // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
    275         final int mType;
    276 
    277         // If mType is TYPE_DAY, then mData is the Julian day.  Otherwise
    278         // mType is TYPE_MEETING and mData is the cursor position.
    279         final int mData;
    280 
    281         RowInfo(int type, int data) {
    282             mType = type;
    283             mData = data;
    284         }
    285     }
    286 
    287     private static class MultipleDayInfo {
    288         final int mPosition;
    289         final int mEndDay;
    290 
    291         MultipleDayInfo(int position, int endDay) {
    292             mPosition = position;
    293             mEndDay = endDay;
    294         }
    295     }
    296 
    297     /**
    298      * Searches for the day that matches the given Time object and returns the
    299      * list position of that day.  If there are no events for that day, then it
    300      * finds the nearest day (before or after) that has events and returns the
    301      * list position for that day.
    302      *
    303      * @param time the date to search for
    304      * @return the cursor position of the first event for that date, or zero
    305      * if no match was found
    306      */
    307     public int findDayPositionNearestTime(Time time) {
    308         if (mRowInfo == null) {
    309             return 0;
    310         }
    311         long millis = time.toMillis(false /* use isDst */);
    312         int julianDay = Time.getJulianDay(millis, time.gmtoff);
    313         int minDistance = 1000;  // some big number
    314         int minIndex = 0;
    315         int len = mRowInfo.size();
    316         for (int index = 0; index < len; index++) {
    317             RowInfo row = mRowInfo.get(index);
    318             if (row.mType == TYPE_DAY) {
    319                 int distance = Math.abs(julianDay - row.mData);
    320                 if (distance == 0) {
    321                     return index;
    322                 }
    323                 if (distance < minDistance) {
    324                     minDistance = distance;
    325                     minIndex = index;
    326                 }
    327             }
    328         }
    329 
    330         // We didn't find an exact match so take the nearest day that had
    331         // events.
    332         return minIndex;
    333     }
    334 
    335     /**
    336      * Finds the Julian day containing the event at the given position.
    337      *
    338      * @param position the list position of an event
    339      * @return the Julian day containing that event
    340      */
    341     public int findJulianDayFromPosition(int position) {
    342         if (mRowInfo == null || position < 0) {
    343             return 0;
    344         }
    345 
    346         int len = mRowInfo.size();
    347         if (position >= len) return 0;  // no row info at this position
    348 
    349         for (int index = position; index >= 0; index--) {
    350             RowInfo row = mRowInfo.get(index);
    351             if (row.mType == TYPE_DAY) {
    352                 return row.mData;
    353             }
    354         }
    355         return 0;
    356     }
    357 
    358     /**
    359      * Converts a list position to a cursor position.  The list contains
    360      * day headers as well as events.  The cursor contains only events.
    361      *
    362      * @param listPos the list position of an event
    363      * @return the corresponding cursor position of that event
    364      */
    365     public int getCursorPosition(int listPos) {
    366         if (mRowInfo != null && listPos >= 0) {
    367             RowInfo row = mRowInfo.get(listPos);
    368             if (row.mType == TYPE_MEETING) {
    369                 return row.mData;
    370             } else {
    371                 int nextPos = listPos + 1;
    372                 if (nextPos < mRowInfo.size()) {
    373                     nextPos = getCursorPosition(nextPos);
    374                     if (nextPos >= 0) {
    375                         return -nextPos;
    376                     }
    377                 }
    378             }
    379         }
    380         return Integer.MIN_VALUE;
    381     }
    382 
    383     @Override
    384     public boolean areAllItemsEnabled() {
    385         return false;
    386     }
    387 
    388     @Override
    389     public boolean isEnabled(int position) {
    390         if (mRowInfo != null && position < mRowInfo.size()) {
    391             RowInfo row = mRowInfo.get(position);
    392             return row.mType == TYPE_MEETING;
    393         }
    394         return true;
    395     }
    396 }
    397