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