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;
  }
 private boolean shouldShowActivityCircleCell() {
   // We show the "more data" activity circle cell if we have a listener to request more data,
   // we are expecting more data, and we have some data already (i.e., not on a fresh query).
   return (cursor != null)
       && cursor.areMoreObjectsAvailable()
       && (dataNeededListener != null)
       && !isEmpty();
 }
  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 View getView(int position, View convertView, ViewGroup parent) {
    SectionAndItem<T> sectionAndItem = getSectionAndItem(position);

    switch (sectionAndItem.getType()) {
      case SECTION_HEADER:
        return getSectionHeaderView(sectionAndItem.sectionKey, convertView, parent);
      case GRAPH_OBJECT:
        return getGraphObjectView(sectionAndItem.graphObject, convertView, parent);
      case ACTIVITY_CIRCLE:
        // If we get a request for this view, it means we need more data.
        assert cursor.areMoreObjectsAvailable() && (dataNeededListener != null);
        dataNeededListener.onDataNeeded();
        return getActivityCircleView(convertView, parent);
      default:
        throw new FacebookException("Unexpected type of section and item.");
    }
  }