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.agenda; 18 19 import com.android.calendar.R; 20 import com.android.calendar.Utils; 21 import com.android.calendar.agenda.AgendaWindowAdapter.DayAdapterInfo; 22 23 import android.content.Context; 24 import android.database.Cursor; 25 import android.graphics.Typeface; 26 import android.text.TextUtils; 27 import android.text.format.DateUtils; 28 import android.text.format.Time; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.BaseAdapter; 34 import android.widget.TextView; 35 36 import java.util.ArrayList; 37 import java.util.Formatter; 38 import java.util.Iterator; 39 import java.util.LinkedList; 40 import java.util.Locale; 41 42 public class AgendaByDayAdapter extends BaseAdapter { 43 private static final int TYPE_DAY = 0; 44 private static final int TYPE_MEETING = 1; 45 static final int TYPE_LAST = 2; 46 47 private final Context mContext; 48 private final AgendaAdapter mAgendaAdapter; 49 private final LayoutInflater mInflater; 50 private ArrayList<RowInfo> mRowInfo; 51 private int mTodayJulianDay; 52 private Time mTmpTime; 53 private String mTimeZone; 54 // Note: Formatter is not thread safe. Fine for now as it is only used by the main thread. 55 private final Formatter mFormatter; 56 private final StringBuilder mStringBuilder; 57 58 static class ViewHolder { 59 TextView dayView; 60 TextView dateView; 61 int julianDay; 62 boolean grayed; 63 } 64 65 private final Runnable mTZUpdater = new Runnable() { 66 @Override 67 public void run() { 68 mTimeZone = Utils.getTimeZone(mContext, this); 69 mTmpTime = new Time(mTimeZone); 70 notifyDataSetChanged(); 71 } 72 }; 73 AgendaByDayAdapter(Context context)74 public AgendaByDayAdapter(Context context) { 75 mContext = context; 76 mAgendaAdapter = new AgendaAdapter(context, R.layout.agenda_item); 77 mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 78 mStringBuilder = new StringBuilder(50); 79 mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); 80 mTimeZone = Utils.getTimeZone(context, mTZUpdater); 81 mTmpTime = new Time(mTimeZone); 82 } 83 getInstanceId(int position)84 public long getInstanceId(int position) { 85 if (mRowInfo == null || position >= mRowInfo.size()) { 86 return -1; 87 } 88 return mRowInfo.get(position).mInstanceId; 89 } 90 getStartTime(int position)91 public long getStartTime(int position) { 92 if (mRowInfo == null || position >= mRowInfo.size()) { 93 return -1; 94 } 95 return mRowInfo.get(position).mEventStartTimeMilli; 96 } 97 98 99 // Returns the position of a header of a specific item getHeaderPosition(int position)100 public int getHeaderPosition(int position) { 101 if (mRowInfo == null || position >= mRowInfo.size()) { 102 return -1; 103 } 104 105 for (int i = position; i >=0; i --) { 106 RowInfo row = mRowInfo.get(i); 107 if (row != null && row.mType == TYPE_DAY) 108 return i; 109 } 110 return -1; 111 } 112 113 // Returns the number of items in a section defined by a specific header location getHeaderItemsCount(int position)114 public int getHeaderItemsCount(int position) { 115 if (mRowInfo == null) { 116 return -1; 117 } 118 int count = 0; 119 for (int i = position +1; i < mRowInfo.size(); i++) { 120 if (mRowInfo.get(i).mType != TYPE_MEETING) { 121 return count; 122 } 123 count ++; 124 } 125 return count; 126 } 127 getCount()128 public int getCount() { 129 if (mRowInfo != null) { 130 return mRowInfo.size(); 131 } 132 return mAgendaAdapter.getCount(); 133 } 134 getItem(int position)135 public Object getItem(int position) { 136 if (mRowInfo != null) { 137 RowInfo row = mRowInfo.get(position); 138 if (row.mType == TYPE_DAY) { 139 return row; 140 } else { 141 return mAgendaAdapter.getItem(row.mPosition); 142 } 143 } 144 return mAgendaAdapter.getItem(position); 145 } 146 getItemId(int position)147 public long getItemId(int position) { 148 if (mRowInfo != null) { 149 RowInfo row = mRowInfo.get(position); 150 if (row.mType == TYPE_DAY) { 151 return -position; 152 } else { 153 return mAgendaAdapter.getItemId(row.mPosition); 154 } 155 } 156 return mAgendaAdapter.getItemId(position); 157 } 158 159 @Override getViewTypeCount()160 public int getViewTypeCount() { 161 return TYPE_LAST; 162 } 163 164 @Override getItemViewType(int position)165 public int getItemViewType(int position) { 166 return mRowInfo != null && mRowInfo.size() > position ? 167 mRowInfo.get(position).mType : TYPE_DAY; 168 } 169 isDayHeaderView(int position)170 public boolean isDayHeaderView(int position) { 171 return (getItemViewType(position) == TYPE_DAY); 172 } 173 getView(int position, View convertView, ViewGroup parent)174 public View getView(int position, View convertView, ViewGroup parent) { 175 if ((mRowInfo == null) || (position > mRowInfo.size())) { 176 // If we have no row info, mAgendaAdapter returns the view. 177 return mAgendaAdapter.getView(position, convertView, parent); 178 } 179 180 RowInfo row = mRowInfo.get(position); 181 if (row.mType == TYPE_DAY) { 182 ViewHolder holder = null; 183 View agendaDayView = null; 184 if ((convertView != null) && (convertView.getTag() != null)) { 185 // Listview may get confused and pass in a different type of 186 // view since we keep shifting data around. Not a big problem. 187 Object tag = convertView.getTag(); 188 if (tag instanceof ViewHolder) { 189 agendaDayView = convertView; 190 holder = (ViewHolder) tag; 191 holder.julianDay = row.mDay; 192 } 193 } 194 195 if (holder == null) { 196 // Create a new AgendaView with a ViewHolder for fast access to 197 // views w/o calling findViewById() 198 holder = new ViewHolder(); 199 agendaDayView = mInflater.inflate(R.layout.agenda_day, parent, false); 200 holder.dayView = (TextView) agendaDayView.findViewById(R.id.day); 201 holder.dateView = (TextView) agendaDayView.findViewById(R.id.date); 202 holder.julianDay = row.mDay; 203 holder.grayed = false; 204 agendaDayView.setTag(holder); 205 } 206 207 // Re-use the member variable "mTime" which is set to the local 208 // time zone. 209 // It's difficult to find and update all these adapters when the 210 // home tz changes so check it here and update if needed. 211 String tz = Utils.getTimeZone(mContext, mTZUpdater); 212 if (!TextUtils.equals(tz, mTmpTime.timezone)) { 213 mTimeZone = tz; 214 mTmpTime = new Time(tz); 215 } 216 217 // Build the text for the day of the week. 218 // Should be yesterday/today/tomorrow (if applicable) + day of the week 219 220 Time date = mTmpTime; 221 long millis = date.setJulianDay(row.mDay); 222 int flags = DateUtils.FORMAT_SHOW_WEEKDAY; 223 mStringBuilder.setLength(0); 224 225 String dayViewText = Utils.getDayOfWeekString(row.mDay, mTodayJulianDay, millis, 226 mContext); 227 228 // Build text for the date 229 // Format should be month day 230 231 mStringBuilder.setLength(0); 232 flags = DateUtils.FORMAT_SHOW_DATE; 233 String dateViewText = DateUtils.formatDateRange(mContext, mFormatter, millis, millis, 234 flags, mTimeZone).toString(); 235 236 if (AgendaWindowAdapter.BASICLOG) { 237 dayViewText += " P:" + position; 238 dateViewText += " P:" + position; 239 } 240 holder.dayView.setText(dayViewText); 241 holder.dateView.setText(dateViewText); 242 243 // Set the background of the view, it is grayed for day that are in the past and today 244 if (row.mDay > mTodayJulianDay) { 245 agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_primary); 246 holder.grayed = false; 247 } else { 248 agendaDayView.setBackgroundResource(R.drawable.agenda_item_bg_secondary); 249 holder.grayed = true; 250 } 251 return agendaDayView; 252 } else if (row.mType == TYPE_MEETING) { 253 View itemView = mAgendaAdapter.getView(row.mPosition, convertView, parent); 254 AgendaAdapter.ViewHolder holder = ((AgendaAdapter.ViewHolder) itemView.getTag()); 255 TextView title = holder.title; 256 // The holder in the view stores information from the cursor, but the cursor has no 257 // notion of multi-day event and the start time of each instance of a multi-day event 258 // is the same. RowInfo has the correct info , so take it from there. 259 holder.startTimeMilli = row.mEventStartTimeMilli; 260 boolean allDay = holder.allDay; 261 if (AgendaWindowAdapter.BASICLOG) { 262 title.setText(title.getText() + " P:" + position); 263 } else { 264 title.setText(title.getText()); 265 } 266 267 // if event in the past or started already, un-bold the title and set the background 268 if ((!allDay && row.mEventStartTimeMilli <= System.currentTimeMillis()) || 269 (allDay && row.mDay <= mTodayJulianDay)) { 270 itemView.setBackgroundResource(R.drawable.agenda_item_bg_secondary); 271 title.setTypeface(Typeface.DEFAULT); 272 holder.grayed = true; 273 } else { 274 itemView.setBackgroundResource(R.drawable.agenda_item_bg_primary); 275 title.setTypeface(Typeface.DEFAULT_BOLD); 276 holder.grayed = false; 277 } 278 holder.julianDay = row.mDay; 279 return itemView; 280 } else { 281 // Error 282 throw new IllegalStateException("Unknown event type:" + row.mType); 283 } 284 } 285 clearDayHeaderInfo()286 public void clearDayHeaderInfo() { 287 mRowInfo = null; 288 } 289 changeCursor(DayAdapterInfo info)290 public void changeCursor(DayAdapterInfo info) { 291 calculateDays(info); 292 mAgendaAdapter.changeCursor(info.cursor); 293 } 294 calculateDays(DayAdapterInfo dayAdapterInfo)295 public void calculateDays(DayAdapterInfo dayAdapterInfo) { 296 Cursor cursor = dayAdapterInfo.cursor; 297 ArrayList<RowInfo> rowInfo = new ArrayList<RowInfo>(); 298 int prevStartDay = -1; 299 300 Time tempTime = new Time(mTimeZone); 301 long now = System.currentTimeMillis(); 302 tempTime.set(now); 303 mTodayJulianDay = Time.getJulianDay(now, tempTime.gmtoff); 304 305 LinkedList<MultipleDayInfo> multipleDayList = new LinkedList<MultipleDayInfo>(); 306 for (int position = 0; cursor.moveToNext(); position++) { 307 int startDay = cursor.getInt(AgendaWindowAdapter.INDEX_START_DAY); 308 long id = cursor.getLong(AgendaWindowAdapter.INDEX_EVENT_ID); 309 long startTime = cursor.getLong(AgendaWindowAdapter.INDEX_BEGIN); 310 long endTime = cursor.getLong(AgendaWindowAdapter.INDEX_END); 311 long instanceId = cursor.getLong(AgendaWindowAdapter.INDEX_INSTANCE_ID); 312 boolean allDay = cursor.getInt(AgendaWindowAdapter.INDEX_ALL_DAY) != 0; 313 if (allDay) { 314 startTime = Utils.convertAlldayUtcToLocal(tempTime, startTime, mTimeZone); 315 endTime = Utils.convertAlldayUtcToLocal(tempTime, endTime, mTimeZone); 316 } 317 // Skip over the days outside of the adapter's range 318 startDay = Math.max(startDay, dayAdapterInfo.start); 319 // Make sure event's start time is not before the start of the day 320 // (setJulianDay sets the time to 12:00am) 321 long adapterStartTime = tempTime.setJulianDay(startDay); 322 startTime = Math.max(startTime, adapterStartTime); 323 324 if (startDay != prevStartDay) { 325 // Check if we skipped over any empty days 326 if (prevStartDay == -1) { 327 rowInfo.add(new RowInfo(TYPE_DAY, startDay)); 328 } else { 329 // If there are any multiple-day events that span the empty 330 // range of days, then create day headers and events for 331 // those multiple-day events. 332 boolean dayHeaderAdded = false; 333 for (int currentDay = prevStartDay + 1; currentDay <= startDay; currentDay++) { 334 dayHeaderAdded = false; 335 Iterator<MultipleDayInfo> iter = multipleDayList.iterator(); 336 while (iter.hasNext()) { 337 MultipleDayInfo info = iter.next(); 338 // If this event has ended then remove it from the 339 // list. 340 if (info.mEndDay < currentDay) { 341 iter.remove(); 342 continue; 343 } 344 345 // If this is the first event for the day, then 346 // insert a day header. 347 if (!dayHeaderAdded) { 348 rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); 349 dayHeaderAdded = true; 350 } 351 long nextMidnight = Utils.getNextMidnight(tempTime, 352 info.mEventStartTimeMilli, mTimeZone); 353 354 long infoEndTime = (info.mEndDay == currentDay) ? 355 info.mEventEndTimeMilli : nextMidnight; 356 rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition, 357 info.mEventId, info.mEventStartTimeMilli, 358 infoEndTime, info.mInstanceId, info.mAllDay)); 359 360 info.mEventStartTimeMilli = nextMidnight; 361 } 362 } 363 364 // If the day header was not added for the start day, then 365 // add it now. 366 if (!dayHeaderAdded) { 367 rowInfo.add(new RowInfo(TYPE_DAY, startDay)); 368 } 369 } 370 prevStartDay = startDay; 371 } 372 373 // If this event spans multiple days, then add it to the multipleDay 374 // list. 375 int endDay = cursor.getInt(AgendaWindowAdapter.INDEX_END_DAY); 376 377 // Skip over the days outside of the adapter's range 378 endDay = Math.min(endDay, dayAdapterInfo.end); 379 if (endDay > startDay) { 380 long nextMidnight = Utils.getNextMidnight(tempTime, startTime, mTimeZone); 381 multipleDayList.add(new MultipleDayInfo(position, endDay, id, nextMidnight, 382 endTime, instanceId, allDay)); 383 // Add in the event for this cursor position - since it is the start of a multi-day 384 // event, the end time is midnight 385 rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, 386 nextMidnight, instanceId, allDay)); 387 } else { 388 // Add in the event for this cursor position 389 rowInfo.add(new RowInfo(TYPE_MEETING, startDay, position, id, startTime, endTime, 390 instanceId, allDay)); 391 } 392 } 393 394 // There are no more cursor events but we might still have multiple-day 395 // events left. So create day headers and events for those. 396 if (prevStartDay > 0) { 397 for (int currentDay = prevStartDay + 1; currentDay <= dayAdapterInfo.end; 398 currentDay++) { 399 boolean dayHeaderAdded = false; 400 Iterator<MultipleDayInfo> iter = multipleDayList.iterator(); 401 while (iter.hasNext()) { 402 MultipleDayInfo info = iter.next(); 403 // If this event has ended then remove it from the 404 // list. 405 if (info.mEndDay < currentDay) { 406 iter.remove(); 407 continue; 408 } 409 410 // If this is the first event for the day, then 411 // insert a day header. 412 if (!dayHeaderAdded) { 413 rowInfo.add(new RowInfo(TYPE_DAY, currentDay)); 414 dayHeaderAdded = true; 415 } 416 long nextMidnight = Utils.getNextMidnight(tempTime, info.mEventStartTimeMilli, 417 mTimeZone); 418 long infoEndTime = 419 (info.mEndDay == currentDay) ? info.mEventEndTimeMilli : nextMidnight; 420 rowInfo.add(new RowInfo(TYPE_MEETING, currentDay, info.mPosition, 421 info.mEventId, info.mEventStartTimeMilli, infoEndTime, 422 info.mInstanceId, info.mAllDay)); 423 424 info.mEventStartTimeMilli = nextMidnight; 425 } 426 } 427 } 428 mRowInfo = rowInfo; 429 } 430 431 private static class RowInfo { 432 // mType is either a day header (TYPE_DAY) or an event (TYPE_MEETING) 433 final int mType; 434 435 final int mDay; // Julian day 436 final int mPosition; // cursor position (not used for TYPE_DAY) 437 // This is used to mark a day header as the first day with events that is "today" 438 // or later. This flag is used by the adapter to create a view with a visual separator 439 // between the past and the present/future 440 boolean mFirstDayAfterYesterday; 441 final long mEventId; 442 final long mEventStartTimeMilli; 443 final long mEventEndTimeMilli; 444 final long mInstanceId; 445 final boolean mAllDay; 446 RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime, long instanceId, boolean allDay)447 RowInfo(int type, int julianDay, int position, long id, long startTime, long endTime, 448 long instanceId, boolean allDay) { 449 mType = type; 450 mDay = julianDay; 451 mPosition = position; 452 mEventId = id; 453 mEventStartTimeMilli = startTime; 454 mEventEndTimeMilli = endTime; 455 mFirstDayAfterYesterday = false; 456 mInstanceId = instanceId; 457 mAllDay = allDay; 458 } 459 RowInfo(int type, int julianDay)460 RowInfo(int type, int julianDay) { 461 mType = type; 462 mDay = julianDay; 463 mPosition = 0; 464 mEventId = 0; 465 mEventStartTimeMilli = 0; 466 mEventEndTimeMilli = 0; 467 mFirstDayAfterYesterday = false; 468 mInstanceId = -1; 469 mAllDay = false; 470 } 471 } 472 473 private static class MultipleDayInfo { 474 final int mPosition; 475 final int mEndDay; 476 final long mEventId; 477 long mEventStartTimeMilli; 478 long mEventEndTimeMilli; 479 final long mInstanceId; 480 final boolean mAllDay; 481 MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime, long instanceId, boolean allDay)482 MultipleDayInfo(int position, int endDay, long id, long startTime, long endTime, 483 long instanceId, boolean allDay) { 484 mPosition = position; 485 mEndDay = endDay; 486 mEventId = id; 487 mEventStartTimeMilli = startTime; 488 mEventEndTimeMilli = endTime; 489 mInstanceId = instanceId; 490 mAllDay = allDay; 491 } 492 } 493 494 /** 495 * Finds the position in the cursor of the event that best matches the time and Id. 496 * It will try to find the event that has the specified id and start time, if such event 497 * doesn't exist, it will return the event with a matching id that is closest to the start time. 498 * If the id doesn't exist, it will return the event with start time closest to the specified 499 * time. 500 * @param time - start of event in milliseconds (or any arbitrary time if event id is unknown) 501 * @param id - Event id (-1 if unknown). 502 * @return Position of event (if found) or position of nearest event according to the time. 503 * Zero if no event found 504 */ findEventPositionNearestTime(Time time, long id)505 public int findEventPositionNearestTime(Time time, long id) { 506 if (mRowInfo == null) { 507 return 0; 508 } 509 long millis = time.toMillis(false /* use isDst */); 510 long minDistance = Integer.MAX_VALUE; // some big number 511 long idFoundMinDistance = Integer.MAX_VALUE; // some big number 512 int minIndex = 0; 513 int idFoundMinIndex = 0; 514 int eventInTimeIndex = -1; 515 int allDayEventInTimeIndex = -1; 516 int allDayEventDay = 0; 517 int minDay = 0; 518 boolean idFound = false; 519 int len = mRowInfo.size(); 520 521 // Loop through the events and find the best match 522 // 1. Event id and start time matches requested id and time 523 // 2. Event id matches and closest time 524 // 3. No event id match , time matches a all day event (midnight) 525 // 4. No event id match , time is between event start and end 526 // 5. No event id match , all day event 527 // 6. The closest event to the requested time 528 529 for (int index = 0; index < len; index++) { 530 RowInfo row = mRowInfo.get(index); 531 if (row.mType == TYPE_DAY) { 532 continue; 533 } 534 535 // Found exact match - done 536 if (row.mEventId == id) { 537 if (row.mEventStartTimeMilli == millis) { 538 return index; 539 } 540 541 // Not an exact match, Save event index if it is the closest to time so far 542 long distance = Math.abs(millis - row.mEventStartTimeMilli); 543 if (distance < idFoundMinDistance) { 544 idFoundMinDistance = distance; 545 idFoundMinIndex = index; 546 } 547 idFound = true; 548 } 549 if (!idFound) { 550 // Found an event that contains the requested time 551 if (millis >= row.mEventStartTimeMilli && millis <= row.mEventEndTimeMilli) { 552 if (row.mAllDay) { 553 if (allDayEventInTimeIndex == -1) { 554 allDayEventInTimeIndex = index; 555 allDayEventDay = row.mDay; 556 } 557 } else if (eventInTimeIndex == -1){ 558 eventInTimeIndex = index; 559 } 560 } else if (eventInTimeIndex == -1){ 561 // Save event index if it is the closest to time so far 562 long distance = Math.abs(millis - row.mEventStartTimeMilli); 563 if (distance < minDistance) { 564 minDistance = distance; 565 minIndex = index; 566 minDay = row.mDay; 567 } 568 } 569 } 570 } 571 // We didn't find an exact match so take the best matching event 572 // Closest event with the same id 573 if (idFound) { 574 return idFoundMinIndex; 575 } 576 // Event which occurs at the searched time 577 if (eventInTimeIndex != -1) { 578 return eventInTimeIndex; 579 // All day event which occurs at the same day of the searched time as long as there is 580 // no regular event at the same day 581 } else if (allDayEventInTimeIndex != -1 && minDay != allDayEventDay) { 582 return allDayEventInTimeIndex; 583 } 584 // Closest event 585 return minIndex; 586 } 587 588 589 /** 590 * Returns a flag indicating if this position is the first day after "yesterday" that has 591 * events in it. 592 * 593 * @return a flag indicating if this is the "first day after yesterday" 594 */ isFirstDayAfterYesterday(int position)595 public boolean isFirstDayAfterYesterday(int position) { 596 int headerPos = getHeaderPosition(position); 597 RowInfo row = mRowInfo.get(headerPos); 598 if (row != null) { 599 return row.mFirstDayAfterYesterday; 600 } 601 return false; 602 } 603 604 /** 605 * Finds the Julian day containing the event at the given position. 606 * 607 * @param position the list position of an event 608 * @return the Julian day containing that event 609 */ findJulianDayFromPosition(int position)610 public int findJulianDayFromPosition(int position) { 611 if (mRowInfo == null || position < 0) { 612 return 0; 613 } 614 615 int len = mRowInfo.size(); 616 if (position >= len) return 0; // no row info at this position 617 618 for (int index = position; index >= 0; index--) { 619 RowInfo row = mRowInfo.get(index); 620 if (row.mType == TYPE_DAY) { 621 return row.mDay; 622 } 623 } 624 return 0; 625 } 626 627 /** 628 * Marks the current row as the first day that has events after "yesterday". 629 * Used to mark the separation between the past and the present/future 630 * 631 * @param position in the adapter 632 */ setAsFirstDayAfterYesterday(int position)633 public void setAsFirstDayAfterYesterday(int position) { 634 if (mRowInfo == null || position < 0 || position > mRowInfo.size()) { 635 return; 636 } 637 RowInfo row = mRowInfo.get(position); 638 row.mFirstDayAfterYesterday = true; 639 } 640 641 /** 642 * Converts a list position to a cursor position. The list contains 643 * day headers as well as events. The cursor contains only events. 644 * 645 * @param listPos the list position of an event 646 * @return the corresponding cursor position of that event 647 * if the position point to day header , it will give the position of the next event 648 * negated. 649 */ getCursorPosition(int listPos)650 public int getCursorPosition(int listPos) { 651 if (mRowInfo != null && listPos >= 0) { 652 RowInfo row = mRowInfo.get(listPos); 653 if (row.mType == TYPE_MEETING) { 654 return row.mPosition; 655 } else { 656 int nextPos = listPos + 1; 657 if (nextPos < mRowInfo.size()) { 658 nextPos = getCursorPosition(nextPos); 659 if (nextPos >= 0) { 660 return -nextPos; 661 } 662 } 663 } 664 } 665 return Integer.MIN_VALUE; 666 } 667 668 @Override areAllItemsEnabled()669 public boolean areAllItemsEnabled() { 670 return false; 671 } 672 673 @Override isEnabled(int position)674 public boolean isEnabled(int position) { 675 if (mRowInfo != null && position < mRowInfo.size()) { 676 RowInfo row = mRowInfo.get(position); 677 return row.mType == TYPE_MEETING; 678 } 679 return true; 680 } 681 } 682