@Override
 public int getSectionForPosition(int position) {
   SectionAndItem<T> sectionAndItem = getSectionAndItem(position);
   if (sectionAndItem != null && sectionAndItem.getType() != SectionAndItem.Type.ACTIVITY_CIRCLE) {
     return Math.max(
         0, Math.min(sectionKeys.indexOf(sectionAndItem.sectionKey), sectionKeys.size() - 1));
   }
   return 0;
 }
 @Override
 public int getPositionForSection(int section) {
   if (displaySections) {
     section = Math.max(0, Math.min(section, sectionKeys.size() - 1));
     if (section < sectionKeys.size()) {
       return getPosition(sectionKeys.get(section), null);
     }
   }
   return 0;
 }
  private void rebuildSections() {
    sectionKeys = new ArrayList<String>();
    graphObjectsBySection = new HashMap<String, ArrayList<T>>();
    graphObjectsById = new HashMap<String, T>();
    displaySections = false;

    if (cursor == null || cursor.getCount() == 0) {
      return;
    }

    int objectsAdded = 0;
    cursor.moveToFirst();
    do {
      T graphObject = cursor.getGraphObject();

      if (!filterIncludesItem(graphObject)) {
        continue;
      }

      objectsAdded++;

      String sectionKeyOfItem = getSectionKeyOfGraphObject(graphObject);
      if (!graphObjectsBySection.containsKey(sectionKeyOfItem)) {
        sectionKeys.add(sectionKeyOfItem);
        graphObjectsBySection.put(sectionKeyOfItem, new ArrayList<T>());
      }
      List<T> section = graphObjectsBySection.get(sectionKeyOfItem);
      section.add(graphObject);

      graphObjectsById.put(getIdOfGraphObject(graphObject), graphObject);
    } while (cursor.moveToNext());

    if (sortFields != null) {
      final Collator collator = Collator.getInstance();
      for (List<T> section : graphObjectsBySection.values()) {
        Collections.sort(
            section,
            new Comparator<GraphObject>() {
              @Override
              public int compare(GraphObject a, GraphObject b) {
                return compareGraphObjects(a, b, sortFields, collator);
              }
            });
      }
    }

    Collections.sort(sectionKeys, Collator.getInstance());

    displaySections = sectionKeys.size() > 1 && objectsAdded > DISPLAY_SECTIONS_THRESHOLD;
  }
  public void prioritizeViewRange(int firstVisibleItem, int lastVisibleItem, int prefetchBuffer) {
    if ((lastVisibleItem < firstVisibleItem) || (sectionKeys.size() == 0)) {
      return;
    }

    // We want to prioritize requests for items which are visible but do not have pictures
    // loaded yet. We also want to pre-fetch pictures for items which are not yet visible
    // but are within a buffer on either side of the visible items, on the assumption that
    // they will be visible soon. For these latter items, we'll store the images in memory
    // in the hopes we can immediately populate their image view when needed.

    // Prioritize the requests in reverse order since each call to prioritizeRequest will just
    // move it to the front of the queue. And we want the earliest ones in the range to be at
    // the front of the queue, so all else being equal, the list will appear to populate from
    // the top down.
    for (int i = lastVisibleItem; i >= 0; i--) {
      SectionAndItem<T> sectionAndItem = getSectionAndItem(i);
      if (sectionAndItem.graphObject != null) {
        String id = getIdOfGraphObject(sectionAndItem.graphObject);
        ImageRequest request = pendingRequests.get(id);
        if (request != null) {
          ImageDownloader.prioritizeRequest(request);
        }
      }
    }

    // For items which are not visible, but within the buffer on either side, we want to
    // fetch those items and store them in a small in-memory cache of bitmaps.
    int start = Math.max(0, firstVisibleItem - prefetchBuffer);
    int end = Math.min(lastVisibleItem + prefetchBuffer, getCount() - 1);
    ArrayList<T> graphObjectsToPrefetchPicturesFor = new ArrayList<T>();
    // Add the IDs before and after the visible range.
    for (int i = start; i < firstVisibleItem; ++i) {
      SectionAndItem<T> sectionAndItem = getSectionAndItem(i);
      if (sectionAndItem.graphObject != null) {
        graphObjectsToPrefetchPicturesFor.add(sectionAndItem.graphObject);
      }
    }
    for (int i = lastVisibleItem + 1; i <= end; ++i) {
      SectionAndItem<T> sectionAndItem = getSectionAndItem(i);
      if (sectionAndItem.graphObject != null) {
        graphObjectsToPrefetchPicturesFor.add(sectionAndItem.graphObject);
      }
    }
    for (T graphObject : graphObjectsToPrefetchPicturesFor) {
      URI uri = getPictureUriOfGraphObject(graphObject);
      final String id = getIdOfGraphObject(graphObject);

      // This URL already have been requested for pre-fetching, but we want to act in an LRU manner,
      // so move
      // it to the end of the list regardless.
      boolean alreadyPrefetching = prefetchedProfilePictureIds.remove(id);
      prefetchedProfilePictureIds.add(id);

      // If we've already requested it for pre-fetching, no need to do so again.
      if (!alreadyPrefetching) {
        downloadProfilePicture(id, uri, null);
      }
    }
  }
 @Override
 public Object[] getSections() {
   if (displaySections) {
     return sectionKeys.toArray();
   } else {
     return new Object[0];
   }
 }
  @Override
  public int getCount() {
    if (sectionKeys.size() == 0) {
      return 0;
    }

    // If we are not displaying sections, we don't display a header; otherwise, we have one header
    // per item in
    // addition to the actual items.
    int count = (displaySections) ? sectionKeys.size() : 0;
    for (List<T> section : graphObjectsBySection.values()) {
      count += section.size();
    }

    // If we should show a cell with an activity circle indicating more data is coming, add it to
    // the count.
    if (shouldShowActivityCircleCell()) {
      ++count;
    }

    return count;
  }
  SectionAndItem<T> getSectionAndItem(int position) {
    if (sectionKeys.size() == 0) {
      return null;
    }
    String sectionKey = null;
    T graphObject = null;

    if (!displaySections) {
      sectionKey = sectionKeys.get(0);
      List<T> section = graphObjectsBySection.get(sectionKey);
      if (position >= 0 && position < section.size()) {
        graphObject = graphObjectsBySection.get(sectionKey).get(position);
      } else {
        // We are off the end; we must be adding an activity circle to indicate more data is coming.
        assert dataNeededListener != null && cursor.areMoreObjectsAvailable();
        // We return null for both to indicate this.
        return new SectionAndItem<T>(null, null);
      }
    } else {
      // Count through the sections; the "0" position in each section is the header. We decrement
      // position each time we skip forward a certain number of elements, including the header.
      for (String key : sectionKeys) {
        // Decrement if we skip over the header
        if (position-- == 0) {
          sectionKey = key;
          break;
        }

        List<T> section = graphObjectsBySection.get(key);
        if (position < section.size()) {
          // The position is somewhere in this section. Get the corresponding graph object.
          sectionKey = key;
          graphObject = section.get(position);
          break;
        }
        // Decrement by as many items as we skipped over
        position -= section.size();
      }
    }
    if (sectionKey != null) {
      // Note: graphObject will be null if this represents a section header.
      return new SectionAndItem<T>(sectionKey, graphObject);
    } else {
      throw new IndexOutOfBoundsException("position");
    }
  }
 @Override
 public boolean isEmpty() {
   // We'll never populate sectionKeys unless we have at least one object.
   return sectionKeys.size() == 0;
 }