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