Fixing issue with fast scroller not showing sub-section headers.

- Removed some old logic to draw merged section headers

Change-Id: I2a7ff9948a3dce253d6bdcda89cc9f222faab309
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 824ccde..70e36a7 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -82,17 +82,30 @@
      * Info about a section in the alphabetic list
      */
     public static class SectionInfo {
-        // The name of this section
-        public String sectionName;
         // The number of applications in this section
-        public int numAppsInSection;
-        // The section AdapterItem for this section
-        public AdapterItem sectionItem;
+        public int numApps;
+        // The section break AdapterItem for this section
+        public AdapterItem sectionBreakItem;
         // The first app AdapterItem for this section
         public AdapterItem firstAppItem;
+    }
 
-        public SectionInfo(String name) {
-            sectionName = name;
+    /**
+     * Info about a fast scroller section, depending if sections are merged, the fast scroller
+     * sections will not be the same set as the section headers.
+     */
+    public static class FastScrollSectionInfo {
+        // The section name
+        public String sectionName;
+        // To map the touch (from 0..1) to the index in the app list to jump to in the fast
+        // scroller, we use the fraction in range (0..1) of the app index / total app count.
+        public float appRangeFraction;
+        // The AdapterItem to scroll to for this section
+        public AdapterItem appItem;
+
+        public FastScrollSectionInfo(String sectionName, float appRangeFraction) {
+            this.sectionName = sectionName;
+            this.appRangeFraction = appRangeFraction;
         }
     }
 
@@ -100,31 +113,30 @@
      * Info about a particular adapter item (can be either section or app)
      */
     public static class AdapterItem {
+        /** Section & App properties */
         // The index of this adapter item in the list
         public int position;
         // Whether or not the item at this adapter position is a section or not
         public boolean isSectionHeader;
-        // The name of this section, or the section section name of the app.  Note that if this
-        // app was merged into another section, then this may be a different name than the
-        // sectionInfo's sectionName
-        public String sectionName;
-        // The section to which this app belongs
+        // The section for this item
         public SectionInfo sectionInfo;
-        // The index of this app in the section
-        public int sectionAppIndex;
-        // The associated AppInfo, or null if this adapter item is a section
-        public AppInfo appInfo;
-        // The index of this app (not including sections), or -1 if this adapter item is a section
-        public int appIndex;
 
-        public static AdapterItem asSection(int pos, SectionInfo section) {
+        /** App-only properties */
+        // The section name of this app.  Note that there can be multiple items with different
+        // sectionNames in the same section
+        public String sectionName = null;
+        // The index of this app in the section
+        public int sectionAppIndex = -1;
+        // The associated AppInfo for the app
+        public AppInfo appInfo = null;
+        // The index of this app not including sections
+        public int appIndex = -1;
+
+        public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
             AdapterItem item = new AdapterItem();
             item.position = pos;
             item.isSectionHeader = true;
             item.sectionInfo = section;
-            item.sectionName = section.sectionName;
-            item.appInfo = null;
-            item.appIndex = -1;
             return item;
         }
 
@@ -156,6 +168,7 @@
     private List<AppInfo> mFilteredApps = new ArrayList<>();
     private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
     private List<SectionInfo> mSections = new ArrayList<>();
+    private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
     private RecyclerView.Adapter mAdapter;
     private Filter mFilter;
     private AlphabeticIndexCompat mIndexer;
@@ -194,6 +207,13 @@
     }
 
     /**
+     * Returns fast scroller sections of all the current filtered applications.
+     */
+    public List<FastScrollSectionInfo> getFastScrollerSections() {
+        return mFastScrollerSections;
+    }
+
+    /**
      * Returns the current filtered list of applications broken down into their sections.
      */
     public List<AdapterItem> getAdapterItems() {
@@ -321,10 +341,13 @@
         mFilteredApps.clear();
         mSections.clear();
         mSectionedFilteredApps.clear();
+        mFastScrollerSections.clear();
         SectionInfo lastSectionInfo = null;
+        String lastSectionName = null;
+        FastScrollSectionInfo lastFastScrollerSectionInfo = null;
         int position = 0;
         int appIndex = 0;
-        int sectionAppIndex = 0;
+        int numApps = mApps.size();
         for (AppInfo info : mApps) {
             String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
 
@@ -334,26 +357,29 @@
             }
 
             // Create a new section if necessary
-            if (lastSectionInfo == null || !lastSectionInfo.sectionName.equals(sectionName)) {
-                lastSectionInfo = new SectionInfo(sectionName);
-                sectionAppIndex = 0;
+            if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+                lastSectionName = sectionName;
+                lastSectionInfo = new SectionInfo();
                 mSections.add(lastSectionInfo);
+                lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
+                        (float) appIndex / numApps);
+                mFastScrollerSections.add(lastFastScrollerSectionInfo);
 
                 // Create a new section item, this item is used to break the flow of items in the
                 // list
-                AdapterItem sectionItem = AdapterItem.asSection(position++, lastSectionInfo);
+                AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
                 if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) {
-                    lastSectionInfo.sectionItem = sectionItem;
+                    lastSectionInfo.sectionBreakItem = sectionItem;
                     mSectionedFilteredApps.add(sectionItem);
                 }
             }
 
             // Create an app item
             AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
-                    sectionAppIndex++, info, appIndex++);
-            lastSectionInfo.numAppsInSection++;
+                    lastSectionInfo.numApps++, info, appIndex++);
             if (lastSectionInfo.firstAppItem == null) {
                 lastSectionInfo.firstAppItem = appItem;
+                lastFastScrollerSectionInfo.appItem = appItem;
             }
             mSectionedFilteredApps.add(appItem);
             mFilteredApps.add(info);
@@ -365,9 +391,9 @@
             int sectionAppCount = 0;
             for (int i = 0; i < mSections.size(); i++) {
                 SectionInfo section = mSections.get(i);
-                String mergedSectionName = section.sectionName;
-                sectionAppCount = section.numAppsInSection;
+                sectionAppCount = section.numApps;
                 int mergeCount = 1;
+
                 // Merge rows if the last app in this section is in a column that is greater than
                 // 0, but less than the min number of apps per row.  In addition, apply the
                 // constraint to stop merging if the number of rows in the section is greater than
@@ -378,35 +404,25 @@
                         (i + 1) < mSections.size()) {
                     SectionInfo nextSection = mSections.remove(i + 1);
 
-                    // Merge the section names
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        mergedSectionName += nextSection.sectionName;
-                    }
                     // Remove the next section break
-                    mSectionedFilteredApps.remove(nextSection.sectionItem);
+                    mSectionedFilteredApps.remove(nextSection.sectionBreakItem);
                     int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
-                    if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                        // Update the section names for the two sections
-                        for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
-                            AdapterItem item = mSectionedFilteredApps.get(j);
-                            item.sectionName = mergedSectionName;
-                            item.sectionInfo = section;
-                        }
-                    }
                     // Point the section for these new apps to the merged section
-                    for (int j = pos + section.numAppsInSection; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) {
+                    int nextPos = pos + section.numApps;
+                    for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
                         AdapterItem item = mSectionedFilteredApps.get(j);
                         item.sectionInfo = section;
-                        item.sectionAppIndex += section.numAppsInSection;
+                        item.sectionAppIndex += section.numApps;
                     }
+
                     // Update the following adapter items of the removed section item
                     pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
                     for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
                         AdapterItem item = mSectionedFilteredApps.get(j);
                         item.position--;
                     }
-                    section.numAppsInSection += nextSection.numAppsInSection;
-                    sectionAppCount += nextSection.numAppsInSection;
+                    section.numApps += nextSection.numApps;
+                    sectionAppCount += nextSection.numApps;
                     mergeCount++;
                     if (mergeCount >= mMaxAllowableMerges) {
                         break;
diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java
index fb5f6d4..6556cf9 100644
--- a/src/com/android/launcher3/AppsContainerRecyclerView.java
+++ b/src/com/android/launcher3/AppsContainerRecyclerView.java
@@ -254,8 +254,9 @@
             mFastScrollTextPaint.setAlpha((int) (mFastScrollAlpha * 255));
             mFastScrollTextPaint.getTextBounds(mFastScrollSectionName, 0,
                     mFastScrollSectionName.length(), mFastScrollTextBounds);
+            float textWidth = mFastScrollTextPaint.measureText(mFastScrollSectionName);
             canvas.drawText(mFastScrollSectionName,
-                    (bgBounds.width() - mFastScrollTextBounds.width()) / 2,
+                    (bgBounds.width() - textWidth) / 2,
                     bgBounds.height() - (bgBounds.height() - mFastScrollTextBounds.height()) / 2,
                     mFastScrollTextPaint);
             canvas.restoreToCount(restoreCount);
@@ -285,38 +286,33 @@
     }
 
     /**
-     * Maps the progress (from 0..1) to the position that should be visible
+     * Maps the touch (from 0..1) to the adapter position that should be visible.
      */
-    private String scrollToPositionAtProgress(float progress) {
-        List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
-        if (sections.isEmpty()) {
+    private String scrollToPositionAtProgress(float touchFraction) {
+        // Ensure that we have any sections
+        List<AlphabeticalAppsList.FastScrollSectionInfo> fastScrollSections =
+                mApps.getFastScrollerSections();
+        if (fastScrollSections.isEmpty()) {
             return "";
         }
 
-        // Find the position of the first application in the section that contains the row at the
-        // current progress
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        int rowAtProgress = (int) (progress * getNumRows());
-        int rowCount = 0;
-        AlphabeticalAppsList.SectionInfo lastSectionInfo = null;
-        for (AlphabeticalAppsList.SectionInfo section : sections) {
-            int numRowsInSection = (int) Math.ceil((float) section.numAppsInSection / mNumAppsPerRow);
-            if (rowCount + numRowsInSection >= rowAtProgress) {
-                lastSectionInfo = section;
+        AlphabeticalAppsList.FastScrollSectionInfo lastScrollSection = fastScrollSections.get(0);
+        for (int i = 1; i < fastScrollSections.size(); i++) {
+            AlphabeticalAppsList.FastScrollSectionInfo scrollSection = fastScrollSections.get(i);
+            if (lastScrollSection.appRangeFraction <= touchFraction &&
+                    touchFraction < scrollSection.appRangeFraction) {
                 break;
             }
-            rowCount += numRowsInSection;
+            lastScrollSection = scrollSection;
         }
-        int position = items.indexOf(lastSectionInfo.firstAppItem);
 
         // Scroll the position into view, anchored at the top of the screen if possible. We call the
         // scroll method on the LayoutManager directly since it is not exposed by RecyclerView.
         LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
         stopScroll();
-        layoutManager.scrollToPositionWithOffset(position, 0);
+        layoutManager.scrollToPositionWithOffset(lastScrollSection.appItem.position, 0);
 
-        // Return the section name of the row
-        return lastSectionInfo.sectionName;
+        return lastScrollSection.sectionName;
     }
 
     /**
@@ -392,11 +388,11 @@
         int appIndex = 0;
         int rowCount = 0;
         for (AlphabeticalAppsList.SectionInfo info : sections) {
-            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
-            if (appIndex + info.numAppsInSection > position) {
+            int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
+            if (appIndex + info.numApps > position) {
                 return rowCount + ((position - appIndex) / mNumAppsPerRow);
             }
-            appIndex += info.numAppsInSection;
+            appIndex += info.numApps;
             rowCount += numRowsInSection;
         }
         return appIndex;
@@ -409,7 +405,7 @@
         List<AlphabeticalAppsList.SectionInfo> sections = mApps.getSections();
         int rowCount = 0;
         for (AlphabeticalAppsList.SectionInfo info : sections) {
-            int numRowsInSection = (int) Math.ceil((float) info.numAppsInSection / mNumAppsPerRow);
+            int numRowsInSection = (int) Math.ceil((float) info.numApps / mNumAppsPerRow);
             rowCount += numRowsInSection;
         }
         return rowCount;
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 9c05d0d..993f9c8 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -46,7 +46,6 @@
         View.OnClickListener, View.OnLongClickListener {
 
     public static final boolean GRID_MERGE_SECTIONS = true;
-    public static final boolean GRID_MERGE_SECTION_HEADERS = false;
     public static final boolean GRID_HIDE_SECTION_HEADERS = false;
 
     private static final boolean ALLOW_SINGLE_APP_LAUNCH = true;
diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java
index d83d6c9..259740c 100644
--- a/src/com/android/launcher3/AppsGridAdapter.java
+++ b/src/com/android/launcher3/AppsGridAdapter.java
@@ -4,7 +4,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.support.v7.widget.GridLayoutManager;
@@ -86,8 +85,6 @@
 
         private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
         private Rect mTmpBounds = new Rect();
-        private String[] mTmpSections = new String[2];
-        private PointF[] mTmpSectionBounds = new PointF[2];
 
         @Override
         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
@@ -111,26 +108,24 @@
 
                     // Draw all the sections for this index
                     String lastSectionName = item.sectionName;
-                    for (int j = item.sectionAppIndex; j < sectionInfo.numAppsInSection;j++, pos++) {
+                    for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
                         AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
+                        String sectionName = nextItem.sectionName;
                         if (nextItem.sectionInfo != sectionInfo) {
                             break;
                         }
-                        if (j > item.sectionAppIndex && nextItem.sectionName.equals(lastSectionName)) {
+                        if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
                             continue;
                         }
 
                         // Find the section code points
-                        getSectionLetters(nextItem.sectionName, mTmpSections, mTmpSectionBounds);
-                        String sectionBegin = mTmpSections[0];
-                        String sectionEnd = mTmpSections[1];
-                        PointF sectionBeginBounds = mTmpSectionBounds[0];
-                        PointF sectionEndBounds = mTmpSectionBounds[1];
+                        PointF sectionBounds = getAndCacheSectionBounds(sectionName);
 
                         // Calculate where to draw the section
-                        int sectionBaseline = (int) (viewTopOffset + sectionBeginBounds.y);
+                        int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
                         int x = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin :
                                 mPaddingStart;
+                        x += (int) ((mStartMargin - sectionBounds.x) / 2f);
                         int y = child.getTop() + sectionBaseline;
 
                         // Determine whether this is the last row with apps in that section, if
@@ -139,7 +134,8 @@
                         int appIndexInSection = items.get(pos).sectionAppIndex;
                         int nextRowPos = Math.min(items.size() - 1,
                                 pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
-                        boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(nextItem.sectionName);
+                        AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
+                        boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
                         if (!fixedToRow) {
                             y = Math.max(sectionBaseline, y);
                         }
@@ -154,25 +150,18 @@
                         if (FADE_OUT_SECTIONS) {
                             int alpha = 255;
                             if (fixedToRow) {
-                                alpha = Math.min(255, (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
+                                alpha = Math.min(255,
+                                        (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
                             }
                             mSectionTextPaint.setAlpha(alpha);
                         }
-                        if (sectionEnd != null) {
-                            // If there is a range, draw the range
-                            c.drawText(sectionBegin + "/" + sectionEnd,
-                                    x + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, y,
-                                    mSectionTextPaint);
-                        } else {
-                            c.drawText(sectionBegin, (int) (x + (mStartMargin / 2f) - (sectionBeginBounds.x / 2f)), y,
-                                    mSectionTextPaint);
-                        }
+                        c.drawText(sectionName, x, y, mSectionTextPaint);
 
                         lastSectionTop = y;
-                        lastSectionHeight = (int) (sectionBeginBounds.y + mSectionHeaderOffset);
-                        lastSectionName = nextItem.sectionName;
+                        lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
+                        lastSectionName = sectionName;
                     }
-                    i += (sectionInfo.numAppsInSection - item.sectionAppIndex);
+                    i += (sectionInfo.numApps - item.sectionAppIndex);
                 }
             }
         }
@@ -184,35 +173,7 @@
         }
 
         /**
-         * Given a section name, return the first and last section letters.
-         */
-        private void getSectionLetters(String sectionName, String[] lettersOut, PointF[] boundsOut) {
-            lettersOut[0] = lettersOut[1] = null;
-            boundsOut[0] = boundsOut[1] = null;
-            if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) {
-                int charOffset = 0;
-                while (charOffset < sectionName.length()) {
-                    int codePoint = sectionName.codePointAt(charOffset);
-                    int codePointSize = Character.charCount(codePoint);
-                    if (charOffset == 0) {
-                        // The first code point
-                        lettersOut[0] = sectionName.substring(charOffset, charOffset + codePointSize);
-                        boundsOut[0] = getAndCacheSectionBounds(lettersOut[0]);
-                    } else if ((charOffset + codePointSize) >= sectionName.length()) {
-                        // The last code point
-                        lettersOut[1] = sectionName.substring(charOffset, charOffset + codePointSize);
-                        boundsOut[0] = getAndCacheSectionBounds(lettersOut[1]);
-                    }
-                    charOffset += codePointSize;
-                }
-            } else {
-                lettersOut[0] = sectionName;
-                boundsOut[0] = getAndCacheSectionBounds(lettersOut[0]);
-            }
-        }
-
-        /**
-         * Given a section name, return the first and last section letters.
+         * Given a section name, return the bounds of the given section name.
          */
         private PointF getAndCacheSectionBounds(String sectionName) {
             PointF bounds = mCachedSectionBounds.get(sectionName);