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