OSDN Git Service

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