Home | History | Annotate | Download | only in allapps
      1 /*
      2  * Copyright (C) 2015 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 package com.android.launcher3.allapps;
     17 
     18 import android.content.Context;
     19 import android.support.v7.widget.RecyclerView;
     20 import android.util.Log;
     21 import com.android.launcher3.AppInfo;
     22 import com.android.launcher3.Launcher;
     23 import com.android.launcher3.LauncherAppState;
     24 import com.android.launcher3.compat.AlphabeticIndexCompat;
     25 import com.android.launcher3.compat.UserHandleCompat;
     26 import com.android.launcher3.model.AppNameComparator;
     27 import com.android.launcher3.util.ComponentKey;
     28 
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.HashMap;
     32 import java.util.List;
     33 import java.util.Locale;
     34 import java.util.Map;
     35 import java.util.TreeMap;
     36 
     37 /**
     38  * The alphabetically sorted list of applications.
     39  */
     40 public class AlphabeticalAppsList {
     41 
     42     public static final String TAG = "AlphabeticalAppsList";
     43     private static final boolean DEBUG = false;
     44     private static final boolean DEBUG_PREDICTIONS = false;
     45 
     46     private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
     47     private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
     48 
     49     private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
     50 
     51     /**
     52      * Info about a section in the alphabetic list
     53      */
     54     public static class SectionInfo {
     55         // The number of applications in this section
     56         public int numApps;
     57         // The section break AdapterItem for this section
     58         public AdapterItem sectionBreakItem;
     59         // The first app AdapterItem for this section
     60         public AdapterItem firstAppItem;
     61     }
     62 
     63     /**
     64      * Info about a fast scroller section, depending if sections are merged, the fast scroller
     65      * sections will not be the same set as the section headers.
     66      */
     67     public static class FastScrollSectionInfo {
     68         // The section name
     69         public String sectionName;
     70         // The AdapterItem to scroll to for this section
     71         public AdapterItem fastScrollToItem;
     72         // The touch fraction that should map to this fast scroll section info
     73         public float touchFraction;
     74 
     75         public FastScrollSectionInfo(String sectionName) {
     76             this.sectionName = sectionName;
     77         }
     78     }
     79 
     80     /**
     81      * Info about a particular adapter item (can be either section or app)
     82      */
     83     public static class AdapterItem {
     84         /** Common properties */
     85         // The index of this adapter item in the list
     86         public int position;
     87         // The type of this item
     88         public int viewType;
     89 
     90         /** Section & App properties */
     91         // The section for this item
     92         public SectionInfo sectionInfo;
     93 
     94         /** App-only properties */
     95         // The section name of this app.  Note that there can be multiple items with different
     96         // sectionNames in the same section
     97         public String sectionName = null;
     98         // The index of this app in the section
     99         public int sectionAppIndex = -1;
    100         // The row that this item shows up on
    101         public int rowIndex;
    102         // The index of this app in the row
    103         public int rowAppIndex;
    104         // The associated AppInfo for the app
    105         public AppInfo appInfo = null;
    106         // The index of this app not including sections
    107         public int appIndex = -1;
    108 
    109         public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
    110             AdapterItem item = new AdapterItem();
    111             item.viewType = AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE;
    112             item.position = pos;
    113             item.sectionInfo = section;
    114             section.sectionBreakItem = item;
    115             return item;
    116         }
    117 
    118         public static AdapterItem asPredictedApp(int pos, SectionInfo section, String sectionName,
    119                 int sectionAppIndex, AppInfo appInfo, int appIndex) {
    120             AdapterItem item = asApp(pos, section, sectionName, sectionAppIndex, appInfo, appIndex);
    121             item.viewType = AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
    122             return item;
    123         }
    124 
    125         public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
    126                 int sectionAppIndex, AppInfo appInfo, int appIndex) {
    127             AdapterItem item = new AdapterItem();
    128             item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
    129             item.position = pos;
    130             item.sectionInfo = section;
    131             item.sectionName = sectionName;
    132             item.sectionAppIndex = sectionAppIndex;
    133             item.appInfo = appInfo;
    134             item.appIndex = appIndex;
    135             return item;
    136         }
    137 
    138         public static AdapterItem asEmptySearch(int pos) {
    139             AdapterItem item = new AdapterItem();
    140             item.viewType = AllAppsGridAdapter.EMPTY_SEARCH_VIEW_TYPE;
    141             item.position = pos;
    142             return item;
    143         }
    144 
    145         public static AdapterItem asDivider(int pos) {
    146             AdapterItem item = new AdapterItem();
    147             item.viewType = AllAppsGridAdapter.SEARCH_MARKET_DIVIDER_VIEW_TYPE;
    148             item.position = pos;
    149             return item;
    150         }
    151 
    152         public static AdapterItem asMarketSearch(int pos) {
    153             AdapterItem item = new AdapterItem();
    154             item.viewType = AllAppsGridAdapter.SEARCH_MARKET_VIEW_TYPE;
    155             item.position = pos;
    156             return item;
    157         }
    158     }
    159 
    160     /**
    161      * Common interface for different merging strategies.
    162      */
    163     public interface MergeAlgorithm {
    164         boolean continueMerging(SectionInfo section, SectionInfo withSection,
    165                 int sectionAppCount, int numAppsPerRow, int mergeCount);
    166     }
    167 
    168     private Launcher mLauncher;
    169 
    170     // The set of apps from the system not including predictions
    171     private final List<AppInfo> mApps = new ArrayList<>();
    172     private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
    173 
    174     // The set of filtered apps with the current filter
    175     private List<AppInfo> mFilteredApps = new ArrayList<>();
    176     // The current set of adapter items
    177     private List<AdapterItem> mAdapterItems = new ArrayList<>();
    178     // The set of sections for the apps with the current filter
    179     private List<SectionInfo> mSections = new ArrayList<>();
    180     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
    181     private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
    182     // The set of predicted app component names
    183     private List<ComponentKey> mPredictedAppComponents = new ArrayList<>();
    184     // The set of predicted apps resolved from the component names and the current set of apps
    185     private List<AppInfo> mPredictedApps = new ArrayList<>();
    186     // The of ordered component names as a result of a search query
    187     private ArrayList<ComponentKey> mSearchResults;
    188     private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
    189     private RecyclerView.Adapter mAdapter;
    190     private AlphabeticIndexCompat mIndexer;
    191     private AppNameComparator mAppNameComparator;
    192     private MergeAlgorithm mMergeAlgorithm;
    193     private int mNumAppsPerRow;
    194     private int mNumPredictedAppsPerRow;
    195     private int mNumAppRowsInAdapter;
    196 
    197     public AlphabeticalAppsList(Context context) {
    198         mLauncher = (Launcher) context;
    199         mIndexer = new AlphabeticIndexCompat(context);
    200         mAppNameComparator = new AppNameComparator(context);
    201     }
    202 
    203     /**
    204      * Sets the number of apps per row.
    205      */
    206     public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow,
    207             MergeAlgorithm mergeAlgorithm) {
    208         mNumAppsPerRow = numAppsPerRow;
    209         mNumPredictedAppsPerRow = numPredictedAppsPerRow;
    210         mMergeAlgorithm = mergeAlgorithm;
    211 
    212         updateAdapterItems();
    213     }
    214 
    215     /**
    216      * Sets the adapter to notify when this dataset changes.
    217      */
    218     public void setAdapter(RecyclerView.Adapter adapter) {
    219         mAdapter = adapter;
    220     }
    221 
    222     /**
    223      * Returns all the apps.
    224      */
    225     public List<AppInfo> getApps() {
    226         return mApps;
    227     }
    228 
    229     /**
    230      * Returns sections of all the current filtered applications.
    231      */
    232     public List<SectionInfo> getSections() {
    233         return mSections;
    234     }
    235 
    236     /**
    237      * Returns fast scroller sections of all the current filtered applications.
    238      */
    239     public List<FastScrollSectionInfo> getFastScrollerSections() {
    240         return mFastScrollerSections;
    241     }
    242 
    243     /**
    244      * Returns the current filtered list of applications broken down into their sections.
    245      */
    246     public List<AdapterItem> getAdapterItems() {
    247         return mAdapterItems;
    248     }
    249 
    250     /**
    251      * Returns the number of rows of applications (not including predictions)
    252      */
    253     public int getNumAppRows() {
    254         return mNumAppRowsInAdapter;
    255     }
    256 
    257     /**
    258      * Returns the number of applications in this list.
    259      */
    260     public int getNumFilteredApps() {
    261         return mFilteredApps.size();
    262     }
    263 
    264     /**
    265      * Returns whether there are is a filter set.
    266      */
    267     public boolean hasFilter() {
    268         return (mSearchResults != null);
    269     }
    270 
    271     /**
    272      * Returns whether there are no filtered results.
    273      */
    274     public boolean hasNoFilteredResults() {
    275         return (mSearchResults != null) && mFilteredApps.isEmpty();
    276     }
    277 
    278     public boolean hasPredictedComponents() {
    279         return (mPredictedAppComponents != null && mPredictedAppComponents.size() > 0);
    280     }
    281 
    282     /**
    283      * Sets the sorted list of filtered components.
    284      */
    285     public boolean setOrderedFilter(ArrayList<ComponentKey> f) {
    286         if (mSearchResults != f) {
    287             boolean same = mSearchResults != null && mSearchResults.equals(f);
    288             mSearchResults = f;
    289             updateAdapterItems();
    290             return !same;
    291         }
    292         return false;
    293     }
    294 
    295     /**
    296      * Sets the current set of predicted apps.  Since this can be called before we get the full set
    297      * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
    298      */
    299     public void setPredictedApps(List<ComponentKey> apps) {
    300         mPredictedAppComponents.clear();
    301         mPredictedAppComponents.addAll(apps);
    302         onAppsUpdated();
    303     }
    304 
    305     /**
    306      * Sets the current set of apps.
    307      */
    308     public void setApps(List<AppInfo> apps) {
    309         mComponentToAppMap.clear();
    310         addApps(apps);
    311     }
    312 
    313     /**
    314      * Adds new apps to the list.
    315      */
    316     public void addApps(List<AppInfo> apps) {
    317         updateApps(apps);
    318     }
    319 
    320     /**
    321      * Updates existing apps in the list
    322      */
    323     public void updateApps(List<AppInfo> apps) {
    324         for (AppInfo app : apps) {
    325             mComponentToAppMap.put(app.toComponentKey(), app);
    326         }
    327         onAppsUpdated();
    328     }
    329 
    330     /**
    331      * Removes some apps from the list.
    332      */
    333     public void removeApps(List<AppInfo> apps) {
    334         for (AppInfo app : apps) {
    335             mComponentToAppMap.remove(app.toComponentKey());
    336         }
    337         onAppsUpdated();
    338     }
    339 
    340     /**
    341      * Updates internals when the set of apps are updated.
    342      */
    343     private void onAppsUpdated() {
    344         // Sort the list of apps
    345         mApps.clear();
    346         mApps.addAll(mComponentToAppMap.values());
    347         Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
    348 
    349         // As a special case for some languages (currently only Simplified Chinese), we may need to
    350         // coalesce sections
    351         Locale curLocale = mLauncher.getResources().getConfiguration().locale;
    352         TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
    353         boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
    354         if (localeRequiresSectionSorting) {
    355             // Compute the section headers.  We use a TreeMap with the section name comparator to
    356             // ensure that the sections are ordered when we iterate over it later
    357             sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
    358             for (AppInfo info : mApps) {
    359                 // Add the section to the cache
    360                 String sectionName = getAndUpdateCachedSectionName(info.title);
    361 
    362                 // Add it to the mapping
    363                 ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
    364                 if (sectionApps == null) {
    365                     sectionApps = new ArrayList<>();
    366                     sectionMap.put(sectionName, sectionApps);
    367                 }
    368                 sectionApps.add(info);
    369             }
    370 
    371             // Add each of the section apps to the list in order
    372             List<AppInfo> allApps = new ArrayList<>(mApps.size());
    373             for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
    374                 allApps.addAll(entry.getValue());
    375             }
    376 
    377             mApps.clear();
    378             mApps.addAll(allApps);
    379         } else {
    380             // Just compute the section headers for use below
    381             for (AppInfo info : mApps) {
    382                 // Add the section to the cache
    383                 getAndUpdateCachedSectionName(info.title);
    384             }
    385         }
    386 
    387         // Recompose the set of adapter items from the current set of apps
    388         updateAdapterItems();
    389     }
    390 
    391     /**
    392      * Updates the set of filtered apps with the current filter.  At this point, we expect
    393      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
    394      */
    395     private void updateAdapterItems() {
    396         SectionInfo lastSectionInfo = null;
    397         String lastSectionName = null;
    398         FastScrollSectionInfo lastFastScrollerSectionInfo = null;
    399         int position = 0;
    400         int appIndex = 0;
    401 
    402         // Prepare to update the list of sections, filtered apps, etc.
    403         mFilteredApps.clear();
    404         mFastScrollerSections.clear();
    405         mAdapterItems.clear();
    406         mSections.clear();
    407 
    408         if (DEBUG_PREDICTIONS) {
    409             if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) {
    410                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
    411                         UserHandleCompat.myUserHandle()));
    412                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
    413                         UserHandleCompat.myUserHandle()));
    414                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
    415                         UserHandleCompat.myUserHandle()));
    416                 mPredictedAppComponents.add(new ComponentKey(mApps.get(0).componentName,
    417                         UserHandleCompat.myUserHandle()));
    418             }
    419         }
    420 
    421         // Process the predicted app components
    422         mPredictedApps.clear();
    423         if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
    424             for (ComponentKey ck : mPredictedAppComponents) {
    425                 AppInfo info = mComponentToAppMap.get(ck);
    426                 if (info != null) {
    427                     mPredictedApps.add(info);
    428                 } else {
    429                     if (LauncherAppState.isDogfoodBuild()) {
    430                         Log.e(TAG, "Predicted app not found: " + ck.flattenToString(mLauncher));
    431                     }
    432                 }
    433                 // Stop at the number of predicted apps
    434                 if (mPredictedApps.size() == mNumPredictedAppsPerRow) {
    435                     break;
    436                 }
    437             }
    438 
    439             if (!mPredictedApps.isEmpty()) {
    440                 // Add a section for the predictions
    441                 lastSectionInfo = new SectionInfo();
    442                 lastFastScrollerSectionInfo = new FastScrollSectionInfo("");
    443                 AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
    444                 mSections.add(lastSectionInfo);
    445                 mFastScrollerSections.add(lastFastScrollerSectionInfo);
    446                 mAdapterItems.add(sectionItem);
    447 
    448                 // Add the predicted app items
    449                 for (AppInfo info : mPredictedApps) {
    450                     AdapterItem appItem = AdapterItem.asPredictedApp(position++, lastSectionInfo,
    451                             "", lastSectionInfo.numApps++, info, appIndex++);
    452                     if (lastSectionInfo.firstAppItem == null) {
    453                         lastSectionInfo.firstAppItem = appItem;
    454                         lastFastScrollerSectionInfo.fastScrollToItem = appItem;
    455                     }
    456                     mAdapterItems.add(appItem);
    457                     mFilteredApps.add(info);
    458                 }
    459             }
    460         }
    461 
    462         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
    463         // ordered set of sections
    464         for (AppInfo info : getFiltersAppInfos()) {
    465             String sectionName = getAndUpdateCachedSectionName(info.title);
    466 
    467             // Create a new section if the section names do not match
    468             if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
    469                 lastSectionName = sectionName;
    470                 lastSectionInfo = new SectionInfo();
    471                 lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
    472                 mSections.add(lastSectionInfo);
    473                 mFastScrollerSections.add(lastFastScrollerSectionInfo);
    474 
    475                 // Create a new section item to break the flow of items in the list
    476                 if (!hasFilter()) {
    477                     AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
    478                     mAdapterItems.add(sectionItem);
    479                 }
    480             }
    481 
    482             // Create an app item
    483             AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
    484                     lastSectionInfo.numApps++, info, appIndex++);
    485             if (lastSectionInfo.firstAppItem == null) {
    486                 lastSectionInfo.firstAppItem = appItem;
    487                 lastFastScrollerSectionInfo.fastScrollToItem = appItem;
    488             }
    489             mAdapterItems.add(appItem);
    490             mFilteredApps.add(info);
    491         }
    492 
    493         // Append the search market item if we are currently searching
    494         if (hasFilter()) {
    495             if (hasNoFilteredResults()) {
    496                 mAdapterItems.add(AdapterItem.asEmptySearch(position++));
    497             } else {
    498                 mAdapterItems.add(AdapterItem.asDivider(position++));
    499             }
    500             mAdapterItems.add(AdapterItem.asMarketSearch(position++));
    501         }
    502 
    503         // Merge multiple sections together as requested by the merge strategy for this device
    504         mergeSections();
    505 
    506         if (mNumAppsPerRow != 0) {
    507             // Update the number of rows in the adapter after we do all the merging (otherwise, we
    508             // would have to shift the values again)
    509             int numAppsInSection = 0;
    510             int numAppsInRow = 0;
    511             int rowIndex = -1;
    512             for (AdapterItem item : mAdapterItems) {
    513                 item.rowIndex = 0;
    514                 if (item.viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE) {
    515                     numAppsInSection = 0;
    516                 } else if (item.viewType == AllAppsGridAdapter.ICON_VIEW_TYPE ||
    517                         item.viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
    518                     if (numAppsInSection % mNumAppsPerRow == 0) {
    519                         numAppsInRow = 0;
    520                         rowIndex++;
    521                     }
    522                     item.rowIndex = rowIndex;
    523                     item.rowAppIndex = numAppsInRow;
    524                     numAppsInSection++;
    525                     numAppsInRow++;
    526                 }
    527             }
    528             mNumAppRowsInAdapter = rowIndex + 1;
    529 
    530             // Pre-calculate all the fast scroller fractions
    531             switch (mFastScrollDistributionMode) {
    532                 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
    533                     float rowFraction = 1f / mNumAppRowsInAdapter;
    534                     for (FastScrollSectionInfo info : mFastScrollerSections) {
    535                         AdapterItem item = info.fastScrollToItem;
    536                         if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
    537                                 item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
    538                             info.touchFraction = 0f;
    539                             continue;
    540                         }
    541 
    542                         float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
    543                         info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
    544                     }
    545                     break;
    546                 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
    547                     float perSectionTouchFraction = 1f / mFastScrollerSections.size();
    548                     float cumulativeTouchFraction = 0f;
    549                     for (FastScrollSectionInfo info : mFastScrollerSections) {
    550                         AdapterItem item = info.fastScrollToItem;
    551                         if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE &&
    552                                 item.viewType != AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE) {
    553                             info.touchFraction = 0f;
    554                             continue;
    555                         }
    556                         info.touchFraction = cumulativeTouchFraction;
    557                         cumulativeTouchFraction += perSectionTouchFraction;
    558                     }
    559                     break;
    560             }
    561         }
    562 
    563         // Refresh the recycler view
    564         if (mAdapter != null) {
    565             mAdapter.notifyDataSetChanged();
    566         }
    567     }
    568 
    569     private List<AppInfo> getFiltersAppInfos() {
    570         if (mSearchResults == null) {
    571             return mApps;
    572         }
    573 
    574         ArrayList<AppInfo> result = new ArrayList<>();
    575         for (ComponentKey key : mSearchResults) {
    576             AppInfo match = mComponentToAppMap.get(key);
    577             if (match != null) {
    578                 result.add(match);
    579             }
    580         }
    581         return result;
    582     }
    583 
    584     /**
    585      * Merges multiple sections to reduce visual raggedness.
    586      */
    587     private void mergeSections() {
    588         // Ignore merging until we have an algorithm and a valid row size
    589         if (mMergeAlgorithm == null || mNumAppsPerRow == 0) {
    590             return;
    591         }
    592 
    593         // Go through each section and try and merge some of the sections
    594         if (!hasFilter()) {
    595             int sectionAppCount = 0;
    596             for (int i = 0; i < mSections.size() - 1; i++) {
    597                 SectionInfo section = mSections.get(i);
    598                 sectionAppCount = section.numApps;
    599                 int mergeCount = 1;
    600 
    601                 // Merge rows based on the current strategy
    602                 while (i < (mSections.size() - 1) &&
    603                         mMergeAlgorithm.continueMerging(section, mSections.get(i + 1),
    604                                 sectionAppCount, mNumAppsPerRow, mergeCount)) {
    605                     SectionInfo nextSection = mSections.remove(i + 1);
    606 
    607                     // Remove the next section break
    608                     mAdapterItems.remove(nextSection.sectionBreakItem);
    609                     int pos = mAdapterItems.indexOf(section.firstAppItem);
    610 
    611                     // Point the section for these new apps to the merged section
    612                     int nextPos = pos + section.numApps;
    613                     for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
    614                         AdapterItem item = mAdapterItems.get(j);
    615                         item.sectionInfo = section;
    616                         item.sectionAppIndex += section.numApps;
    617                     }
    618 
    619                     // Update the following adapter items of the removed section item
    620                     pos = mAdapterItems.indexOf(nextSection.firstAppItem);
    621                     for (int j = pos; j < mAdapterItems.size(); j++) {
    622                         AdapterItem item = mAdapterItems.get(j);
    623                         item.position--;
    624                     }
    625                     section.numApps += nextSection.numApps;
    626                     sectionAppCount += nextSection.numApps;
    627 
    628                     if (DEBUG) {
    629                         Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
    630                                 " to " + section.firstAppItem.sectionName +
    631                                 " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
    632                     }
    633                     mergeCount++;
    634                 }
    635             }
    636         }
    637     }
    638 
    639     /**
    640      * Returns the cached section name for the given title, recomputing and updating the cache if
    641      * the title has no cached section name.
    642      */
    643     private String getAndUpdateCachedSectionName(CharSequence title) {
    644         String sectionName = mCachedSectionNames.get(title);
    645         if (sectionName == null) {
    646             sectionName = mIndexer.computeSectionName(title);
    647             mCachedSectionNames.put(title, sectionName);
    648         }
    649         return sectionName;
    650     }
    651 }
    652