2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.calendar;
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;
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;
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;
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;
50 static class ViewHolder {
54 public AgendaByDayAdapter(Context 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());
62 public int getCount() {
63 if (mRowInfo != null) {
64 return mRowInfo.size();
66 return mAgendaAdapter.getCount();
69 public Object getItem(int position) {
70 if (mRowInfo != null) {
71 RowInfo row = mRowInfo.get(position);
72 if (row.mType == TYPE_DAY) {
75 return mAgendaAdapter.getItem(row.mData);
78 return mAgendaAdapter.getItem(position);
81 public long getItemId(int position) {
82 if (mRowInfo != null) {
83 RowInfo row = mRowInfo.get(position);
84 if (row.mType == TYPE_DAY) {
87 return mAgendaAdapter.getItemId(row.mData);
90 return mAgendaAdapter.getItemId(position);
94 public int getViewTypeCount() {
99 public int getItemViewType(int position) {
100 return mRowInfo != null && mRowInfo.size() > position ?
101 mRowInfo.get(position).mType : TYPE_DAY;
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);
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;
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);
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;
139 mStringBuilder.setLength(0);
141 if (row.mData == mTodayJulianDay) {
142 dateViewText = mContext.getString(R.string.agenda_today, DateUtils.formatDateRange(
143 mContext, mFormatter, millis, millis, flags).toString());
145 flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
146 dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis,
150 if (AgendaWindowAdapter.BASICLOG) {
151 dateViewText += " P:" + position;
153 holder.dateView.setText(dateViewText);
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());
163 throw new IllegalStateException("Unknown event type:" + row.mType);
167 public void clearDayHeaderInfo() {
171 public void changeCursor(Cursor cursor) {
172 calculateDays(cursor);
173 mAgendaAdapter.changeCursor(cursor);
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();
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);
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));
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
204 if (info.mEndDay < currentDay) {
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;
215 rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
219 // If the day header was not added for the start day, then
221 if (!dayHeaderAdded) {
222 rowInfo.add(new RowInfo(TYPE_DAY, startDay));
225 prevStartDay = startDay;
228 // Add in the event for this cursor position
229 rowInfo.add(new RowInfo(TYPE_MEETING, position));
231 // If this event spans multiple days, then add it to the multipleDay
233 int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY);
234 if (endDay > startDay) {
235 multipleDayList.add(new MultipleDayInfo(position, endDay));
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);
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
258 if (info.mEndDay < currentDay) {
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;
269 rowInfo.add(new RowInfo(TYPE_MEETING, info.mPosition));
276 private static class RowInfo {
277 // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING)
280 // If mType is TYPE_DAY, then mData is the Julian day. Otherwise
281 // mType is TYPE_MEETING and mData is the cursor position.
284 RowInfo(int type, int data) {
290 private static class MultipleDayInfo {
294 MultipleDayInfo(int position, int endDay) {
295 mPosition = position;
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.
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
310 public int findDayPositionNearestTime(Time time) {
311 if (mRowInfo == null) {
314 long millis = time.toMillis(false /* use isDst */);
315 int julianDay = Time.getJulianDay(millis, time.gmtoff);
316 int minDistance = 1000; // some big number
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);
326 if (distance < minDistance) {
327 minDistance = distance;
333 // We didn't find an exact match so take the nearest day that had
339 * Finds the Julian day containing the event at the given position.
341 * @param position the list position of an event
342 * @return the Julian day containing that event
344 public int findJulianDayFromPosition(int position) {
345 if (mRowInfo == null || position < 0) {
349 int len = mRowInfo.size();
350 if (position >= len) return 0; // no row info at this position
352 for (int index = position; index >= 0; index--) {
353 RowInfo row = mRowInfo.get(index);
354 if (row.mType == TYPE_DAY) {
362 * Converts a list position to a cursor position. The list contains
363 * day headers as well as events. The cursor contains only events.
365 * @param listPos the list position of an event
366 * @return the corresponding cursor position of that event
368 public int getCursorPosition(int listPos) {
369 if (mRowInfo != null && listPos >= 0) {
370 RowInfo row = mRowInfo.get(listPos);
371 if (row.mType == TYPE_MEETING) {
374 int nextPos = listPos + 1;
375 if (nextPos < mRowInfo.size()) {
376 nextPos = getCursorPosition(nextPos);
383 return Integer.MIN_VALUE;
387 public boolean areAllItemsEnabled() {
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;