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