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.content.Intent;
     20 import android.content.pm.PackageManager;
     21 import android.content.pm.ResolveInfo;
     22 import android.content.res.Resources;
     23 import android.graphics.Canvas;
     24 import android.graphics.Paint;
     25 import android.graphics.PointF;
     26 import android.graphics.Rect;
     27 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
     28 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat;
     29 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
     30 import android.support.v4.view.accessibility.AccessibilityEventCompat;
     31 import android.net.Uri;
     32 import android.support.v7.widget.GridLayoutManager;
     33 import android.support.v7.widget.RecyclerView;
     34 import android.support.v7.widget.RecyclerView.Recycler;
     35 import android.support.v7.widget.RecyclerView.State;
     36 import android.util.Log;
     37 import android.view.Gravity;
     38 import android.view.LayoutInflater;
     39 import android.view.View;
     40 import android.view.ViewConfiguration;
     41 import android.view.ViewGroup;
     42 import android.view.accessibility.AccessibilityEvent;
     43 import android.widget.TextView;
     44 import com.android.launcher3.AppInfo;
     45 import com.android.launcher3.BubbleTextView;
     46 import com.android.launcher3.Launcher;
     47 import com.android.launcher3.LauncherAppState;
     48 import com.android.launcher3.R;
     49 import com.android.launcher3.Utilities;
     50 
     51 import java.util.HashMap;
     52 import java.util.List;
     53 
     54 
     55 /**
     56  * The grid view adapter of all the apps.
     57  */
     58 public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
     59 
     60     public static final String TAG = "AppsGridAdapter";
     61     private static final boolean DEBUG = false;
     62 
     63     // A section break in the grid
     64     public static final int SECTION_BREAK_VIEW_TYPE = 0;
     65     // A normal icon
     66     public static final int ICON_VIEW_TYPE = 1;
     67     // A prediction icon
     68     public static final int PREDICTION_ICON_VIEW_TYPE = 2;
     69     // The message shown when there are no filtered results
     70     public static final int EMPTY_SEARCH_VIEW_TYPE = 3;
     71     // A divider that separates the apps list and the search market button
     72     public static final int SEARCH_MARKET_DIVIDER_VIEW_TYPE = 4;
     73     // The message to continue to a market search when there are no filtered results
     74     public static final int SEARCH_MARKET_VIEW_TYPE = 5;
     75 
     76     public interface BindViewCallback {
     77         public void onBindView(ViewHolder holder);
     78     }
     79 
     80     /**
     81      * ViewHolder for each icon.
     82      */
     83     public static class ViewHolder extends RecyclerView.ViewHolder {
     84         public View mContent;
     85 
     86         public ViewHolder(View v) {
     87             super(v);
     88             mContent = v;
     89         }
     90     }
     91 
     92     /**
     93      * A subclass of GridLayoutManager that overrides accessibility values during app search.
     94      */
     95     public class AppsGridLayoutManager extends GridLayoutManager {
     96 
     97         public AppsGridLayoutManager(Context context) {
     98             super(context, 1, GridLayoutManager.VERTICAL, false);
     99         }
    100 
    101         @Override
    102         public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    103             super.onInitializeAccessibilityEvent(event);
    104 
    105             // Ensure that we only report the number apps for accessibility not including other
    106             // adapter views
    107             final AccessibilityRecordCompat record = AccessibilityEventCompat
    108                     .asRecord(event);
    109 
    110             // count the number of SECTION_BREAK_VIEW_TYPE that is wrongfully
    111             // initialized as a node (also a row) for talk back.
    112             int numEmptyNode = getEmptyRowForAccessibility(-1 /* no view type */);
    113             record.setFromIndex(event.getFromIndex() - numEmptyNode);
    114             record.setToIndex(event.getToIndex() - numEmptyNode);
    115             record.setItemCount(mApps.getNumFilteredApps());
    116         }
    117 
    118         @Override
    119         public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler,
    120                 State state, View host, AccessibilityNodeInfoCompat info) {
    121 
    122             int viewType = getItemViewType(host);
    123             // Only initialize on node that is meaningful. Subtract empty row count.
    124             if (viewType == ICON_VIEW_TYPE || viewType == PREDICTION_ICON_VIEW_TYPE) {
    125                 super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
    126                 CollectionItemInfoCompat itemInfo = info.getCollectionItemInfo();
    127                 if (itemInfo != null) {
    128                     final CollectionItemInfoCompat dstItemInfo = CollectionItemInfoCompat.obtain(
    129                             itemInfo.getRowIndex() - getEmptyRowForAccessibility(viewType),
    130                             itemInfo.getRowSpan(),
    131                             itemInfo.getColumnIndex(),
    132                             itemInfo.getColumnSpan(),
    133                             itemInfo.isHeading(),
    134                             itemInfo.isSelected());
    135                     info.setCollectionItemInfo(dstItemInfo);
    136                 }
    137             }
    138         }
    139 
    140         @Override
    141         public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
    142                 RecyclerView.State state) {
    143             return super.getRowCountForAccessibility(recycler, state)
    144                     - getEmptyRowForAccessibility(-1 /* no view type */);
    145         }
    146 
    147         /**
    148          * Returns the total number of SECTION_BREAK_VIEW_TYPE that is wrongfully
    149          * initialized as a node (also a row) for talk back.
    150          */
    151         private int getEmptyRowForAccessibility(int viewType) {
    152             int numEmptyNode = 0;
    153             if (mApps.hasFilter()) {
    154                 // search result screen has only one SECTION_BREAK_VIEW
    155                 numEmptyNode = 1;
    156             } else {
    157                 // default all apps screen may have one or two SECTION_BREAK_VIEW
    158                 numEmptyNode = 1;
    159                 if (mApps.hasPredictedComponents()) {
    160                     if (viewType == PREDICTION_ICON_VIEW_TYPE) {
    161                         numEmptyNode = 1;
    162                     } else if (viewType == ICON_VIEW_TYPE) {
    163                         numEmptyNode = 2;
    164                     }
    165                 } else {
    166                     if (viewType == ICON_VIEW_TYPE) {
    167                         numEmptyNode = 1;
    168                     }
    169                 }
    170             }
    171             return numEmptyNode;
    172         }
    173     }
    174 
    175     /**
    176      * Helper class to size the grid items.
    177      */
    178     public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup {
    179 
    180         public GridSpanSizer() {
    181             super();
    182             setSpanIndexCacheEnabled(true);
    183         }
    184 
    185         @Override
    186         public int getSpanSize(int position) {
    187             switch (mApps.getAdapterItems().get(position).viewType) {
    188                 case AllAppsGridAdapter.ICON_VIEW_TYPE:
    189                 case AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE:
    190                     return 1;
    191                 default:
    192                     // Section breaks span the full width
    193                     return mAppsPerRow;
    194             }
    195         }
    196     }
    197 
    198     /**
    199      * Helper class to draw the section headers
    200      */
    201     public class GridItemDecoration extends RecyclerView.ItemDecoration {
    202 
    203         private static final boolean DEBUG_SECTION_MARGIN = false;
    204         private static final boolean FADE_OUT_SECTIONS = false;
    205 
    206         private HashMap<String, PointF> mCachedSectionBounds = new HashMap<>();
    207         private Rect mTmpBounds = new Rect();
    208 
    209         @Override
    210         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    211             if (mApps.hasFilter() || mAppsPerRow == 0) {
    212                 return;
    213             }
    214 
    215             if (DEBUG_SECTION_MARGIN) {
    216                 Paint p = new Paint();
    217                 p.setColor(0x33ff0000);
    218                 c.drawRect(mBackgroundPadding.left, 0, mBackgroundPadding.left + mSectionNamesMargin,
    219                         parent.getMeasuredHeight(), p);
    220             }
    221 
    222             List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
    223             boolean hasDrawnPredictedAppsDivider = false;
    224             boolean showSectionNames = mSectionNamesMargin > 0;
    225             int childCount = parent.getChildCount();
    226             int lastSectionTop = 0;
    227             int lastSectionHeight = 0;
    228             for (int i = 0; i < childCount; i++) {
    229                 View child = parent.getChildAt(i);
    230                 ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child);
    231                 if (!isValidHolderAndChild(holder, child, items)) {
    232                     continue;
    233                 }
    234 
    235                 if (shouldDrawItemDivider(holder, items) && !hasDrawnPredictedAppsDivider) {
    236                     // Draw the divider under the predicted apps
    237                     int top = child.getTop() + child.getHeight() + mPredictionBarDividerOffset;
    238                     c.drawLine(mBackgroundPadding.left, top,
    239                             parent.getWidth() - mBackgroundPadding.right, top,
    240                             mPredictedAppsDividerPaint);
    241                     hasDrawnPredictedAppsDivider = true;
    242 
    243                 } else if (showSectionNames && shouldDrawItemSection(holder, i, items)) {
    244                     // At this point, we only draw sections for each section break;
    245                     int viewTopOffset = (2 * child.getPaddingTop());
    246                     int pos = holder.getPosition();
    247                     AlphabeticalAppsList.AdapterItem item = items.get(pos);
    248                     AlphabeticalAppsList.SectionInfo sectionInfo = item.sectionInfo;
    249 
    250                     // Draw all the sections for this index
    251                     String lastSectionName = item.sectionName;
    252                     for (int j = item.sectionAppIndex; j < sectionInfo.numApps; j++, pos++) {
    253                         AlphabeticalAppsList.AdapterItem nextItem = items.get(pos);
    254                         String sectionName = nextItem.sectionName;
    255                         if (nextItem.sectionInfo != sectionInfo) {
    256                             break;
    257                         }
    258                         if (j > item.sectionAppIndex && sectionName.equals(lastSectionName)) {
    259                             continue;
    260                         }
    261 
    262 
    263                         // Find the section name bounds
    264                         PointF sectionBounds = getAndCacheSectionBounds(sectionName);
    265 
    266                         // Calculate where to draw the section
    267                         int sectionBaseline = (int) (viewTopOffset + sectionBounds.y);
    268                         int x = mIsRtl ?
    269                                 parent.getWidth() - mBackgroundPadding.left - mSectionNamesMargin :
    270                                         mBackgroundPadding.left;
    271                         x += (int) ((mSectionNamesMargin - sectionBounds.x) / 2f);
    272                         int y = child.getTop() + sectionBaseline;
    273 
    274                         // Determine whether this is the last row with apps in that section, if
    275                         // so, then fix the section to the row allowing it to scroll past the
    276                         // baseline, otherwise, bound it to the baseline so it's in the viewport
    277                         int appIndexInSection = items.get(pos).sectionAppIndex;
    278                         int nextRowPos = Math.min(items.size() - 1,
    279                                 pos + mAppsPerRow - (appIndexInSection % mAppsPerRow));
    280                         AlphabeticalAppsList.AdapterItem nextRowItem = items.get(nextRowPos);
    281                         boolean fixedToRow = !sectionName.equals(nextRowItem.sectionName);
    282                         if (!fixedToRow) {
    283                             y = Math.max(sectionBaseline, y);
    284                         }
    285 
    286                         // In addition, if it overlaps with the last section that was drawn, then
    287                         // offset it so that it does not overlap
    288                         if (lastSectionHeight > 0 && y <= (lastSectionTop + lastSectionHeight)) {
    289                             y += lastSectionTop - y + lastSectionHeight;
    290                         }
    291 
    292                         // Draw the section header
    293                         if (FADE_OUT_SECTIONS) {
    294                             int alpha = 255;
    295                             if (fixedToRow) {
    296                                 alpha = Math.min(255,
    297                                         (int) (255 * (Math.max(0, y) / (float) sectionBaseline)));
    298                             }
    299                             mSectionTextPaint.setAlpha(alpha);
    300                         }
    301                         c.drawText(sectionName, x, y, mSectionTextPaint);
    302 
    303                         lastSectionTop = y;
    304                         lastSectionHeight = (int) (sectionBounds.y + mSectionHeaderOffset);
    305                         lastSectionName = sectionName;
    306                     }
    307                     i += (sectionInfo.numApps - item.sectionAppIndex);
    308                 }
    309             }
    310         }
    311 
    312         @Override
    313         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
    314                 RecyclerView.State state) {
    315             // Do nothing
    316         }
    317 
    318         /**
    319          * Given a section name, return the bounds of the given section name.
    320          */
    321         private PointF getAndCacheSectionBounds(String sectionName) {
    322             PointF bounds = mCachedSectionBounds.get(sectionName);
    323             if (bounds == null) {
    324                 mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds);
    325                 bounds = new PointF(mSectionTextPaint.measureText(sectionName), mTmpBounds.height());
    326                 mCachedSectionBounds.put(sectionName, bounds);
    327             }
    328             return bounds;
    329         }
    330 
    331         /**
    332          * Returns whether we consider this a valid view holder for us to draw a divider or section for.
    333          */
    334         private boolean isValidHolderAndChild(ViewHolder holder, View child,
    335                 List<AlphabeticalAppsList.AdapterItem> items) {
    336             // Ensure item is not already removed
    337             GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
    338                     child.getLayoutParams();
    339             if (lp.isItemRemoved()) {
    340                 return false;
    341             }
    342             // Ensure we have a valid holder
    343             if (holder == null) {
    344                 return false;
    345             }
    346             // Ensure we have a holder position
    347             int pos = holder.getPosition();
    348             if (pos < 0 || pos >= items.size()) {
    349                 return false;
    350             }
    351             return true;
    352         }
    353 
    354         /**
    355          * Returns whether to draw the divider for a given child.
    356          */
    357         private boolean shouldDrawItemDivider(ViewHolder holder,
    358                 List<AlphabeticalAppsList.AdapterItem> items) {
    359             int pos = holder.getPosition();
    360             return items.get(pos).viewType == AllAppsGridAdapter.PREDICTION_ICON_VIEW_TYPE;
    361         }
    362 
    363         /**
    364          * Returns whether to draw the section for the given child.
    365          */
    366         private boolean shouldDrawItemSection(ViewHolder holder, int childIndex,
    367                 List<AlphabeticalAppsList.AdapterItem> items) {
    368             int pos = holder.getPosition();
    369             AlphabeticalAppsList.AdapterItem item = items.get(pos);
    370 
    371             // Ensure it's an icon
    372             if (item.viewType != AllAppsGridAdapter.ICON_VIEW_TYPE) {
    373                 return false;
    374             }
    375             // Draw the section header for the first item in each section
    376             return (childIndex == 0) ||
    377                     (items.get(pos - 1).viewType == AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE);
    378         }
    379     }
    380 
    381     private final Launcher mLauncher;
    382     private final LayoutInflater mLayoutInflater;
    383     private final AlphabeticalAppsList mApps;
    384     private final GridLayoutManager mGridLayoutMgr;
    385     private final GridSpanSizer mGridSizer;
    386     private final GridItemDecoration mItemDecoration;
    387     private final View.OnTouchListener mTouchListener;
    388     private final View.OnClickListener mIconClickListener;
    389     private final View.OnLongClickListener mIconLongClickListener;
    390 
    391     private final Rect mBackgroundPadding = new Rect();
    392     private final boolean mIsRtl;
    393 
    394     // Section drawing
    395     private final int mSectionNamesMargin;
    396     private final int mSectionHeaderOffset;
    397     private final Paint mSectionTextPaint;
    398     private final Paint mPredictedAppsDividerPaint;
    399 
    400     private final int mPredictionBarDividerOffset;
    401     private int mAppsPerRow;
    402     private BindViewCallback mBindViewCallback;
    403     private AllAppsSearchBarController mSearchController;
    404 
    405     // The text to show when there are no search results and no market search handler.
    406     private String mEmptySearchMessage;
    407     // The name of the market app which handles searches, to be used in the format str
    408     // below when updating the search-market view.  Only needs to be loaded once.
    409     private String mMarketAppName;
    410     // The text to show when there is a market app which can handle a specific query, updated
    411     // each time the search query changes.
    412     private String mMarketSearchMessage;
    413     // The intent to send off to the market app, updated each time the search query changes.
    414     private Intent mMarketSearchIntent;
    415 
    416     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps,
    417             View.OnTouchListener touchListener, View.OnClickListener iconClickListener,
    418             View.OnLongClickListener iconLongClickListener) {
    419         Resources res = launcher.getResources();
    420         mLauncher = launcher;
    421         mApps = apps;
    422         mEmptySearchMessage = res.getString(R.string.all_apps_loading_message);
    423         mGridSizer = new GridSpanSizer();
    424         mGridLayoutMgr = new AppsGridLayoutManager(launcher);
    425         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
    426         mItemDecoration = new GridItemDecoration();
    427         mLayoutInflater = LayoutInflater.from(launcher);
    428         mTouchListener = touchListener;
    429         mIconClickListener = iconClickListener;
    430         mIconLongClickListener = iconLongClickListener;
    431         mSectionNamesMargin = res.getDimensionPixelSize(R.dimen.all_apps_grid_view_start_margin);
    432         mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.all_apps_grid_section_y_offset);
    433         mIsRtl = Utilities.isRtl(res);
    434 
    435         mSectionTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    436         mSectionTextPaint.setTextSize(res.getDimensionPixelSize(
    437                 R.dimen.all_apps_grid_section_text_size));
    438         mSectionTextPaint.setColor(res.getColor(R.color.all_apps_grid_section_text_color));
    439 
    440         mPredictedAppsDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    441         mPredictedAppsDividerPaint.setStrokeWidth(Utilities.pxFromDp(1f, res.getDisplayMetrics()));
    442         mPredictedAppsDividerPaint.setColor(0x1E000000);
    443         mPredictionBarDividerOffset =
    444                 (-res.getDimensionPixelSize(R.dimen.all_apps_prediction_icon_bottom_padding) +
    445                         res.getDimensionPixelSize(R.dimen.all_apps_icon_top_bottom_padding)) / 2;
    446     }
    447 
    448     /**
    449      * Sets the number of apps per row.
    450      */
    451     public void setNumAppsPerRow(int appsPerRow) {
    452         mAppsPerRow = appsPerRow;
    453         mGridLayoutMgr.setSpanCount(appsPerRow);
    454     }
    455 
    456     public void setSearchController(AllAppsSearchBarController searchController) {
    457         mSearchController = searchController;
    458 
    459         // Resolve the market app handling additional searches
    460         PackageManager pm = mLauncher.getPackageManager();
    461         ResolveInfo marketInfo = pm.resolveActivity(mSearchController.createMarketSearchIntent(""),
    462                 PackageManager.MATCH_DEFAULT_ONLY);
    463         if (marketInfo != null) {
    464             mMarketAppName = marketInfo.loadLabel(pm).toString();
    465         }
    466     }
    467 
    468     /**
    469      * Sets the last search query that was made, used to show when there are no results and to also
    470      * seed the intent for searching the market.
    471      */
    472     public void setLastSearchQuery(String query) {
    473         Resources res = mLauncher.getResources();
    474         String formatStr = res.getString(R.string.all_apps_no_search_results);
    475         mEmptySearchMessage = String.format(formatStr, query);
    476         if (mMarketAppName != null) {
    477             mMarketSearchMessage = String.format(res.getString(R.string.all_apps_search_market_message),
    478                     mMarketAppName);
    479             mMarketSearchIntent = mSearchController.createMarketSearchIntent(query);
    480         }
    481     }
    482 
    483     /**
    484      * Sets the callback for when views are bound.
    485      */
    486     public void setBindViewCallback(BindViewCallback cb) {
    487         mBindViewCallback = cb;
    488     }
    489 
    490     /**
    491      * Notifies the adapter of the background padding so that it can draw things correctly in the
    492      * item decorator.
    493      */
    494     public void updateBackgroundPadding(Rect padding) {
    495         mBackgroundPadding.set(padding);
    496     }
    497 
    498     /**
    499      * Returns the grid layout manager.
    500      */
    501     public GridLayoutManager getLayoutManager() {
    502         return mGridLayoutMgr;
    503     }
    504 
    505     /**
    506      * Returns the item decoration for the recycler view.
    507      */
    508     public RecyclerView.ItemDecoration getItemDecoration() {
    509         // We don't draw any headers when we are uncomfortably dense
    510         return mItemDecoration;
    511     }
    512 
    513     @Override
    514     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    515         switch (viewType) {
    516             case SECTION_BREAK_VIEW_TYPE:
    517                 return new ViewHolder(new View(parent.getContext()));
    518             case ICON_VIEW_TYPE: {
    519                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
    520                         R.layout.all_apps_icon, parent, false);
    521                 icon.setOnTouchListener(mTouchListener);
    522                 icon.setOnClickListener(mIconClickListener);
    523                 icon.setOnLongClickListener(mIconLongClickListener);
    524                 icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
    525                         .getLongPressTimeout());
    526                 icon.setFocusable(true);
    527                 return new ViewHolder(icon);
    528             }
    529             case PREDICTION_ICON_VIEW_TYPE: {
    530                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
    531                         R.layout.all_apps_prediction_bar_icon, parent, false);
    532                 icon.setOnTouchListener(mTouchListener);
    533                 icon.setOnClickListener(mIconClickListener);
    534                 icon.setOnLongClickListener(mIconLongClickListener);
    535                 icon.setLongPressTimeout(ViewConfiguration.get(parent.getContext())
    536                         .getLongPressTimeout());
    537                 icon.setFocusable(true);
    538                 return new ViewHolder(icon);
    539             }
    540             case EMPTY_SEARCH_VIEW_TYPE:
    541                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
    542                         parent, false));
    543             case SEARCH_MARKET_DIVIDER_VIEW_TYPE:
    544                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_search_market_divider,
    545                         parent, false));
    546             case SEARCH_MARKET_VIEW_TYPE:
    547                 View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
    548                         parent, false);
    549                 searchMarketView.setOnClickListener(new View.OnClickListener() {
    550                     @Override
    551                     public void onClick(View v) {
    552                         mLauncher.startActivitySafely(v, mMarketSearchIntent, null);
    553                     }
    554                 });
    555                 return new ViewHolder(searchMarketView);
    556             default:
    557                 throw new RuntimeException("Unexpected view type");
    558         }
    559     }
    560 
    561     @Override
    562     public void onBindViewHolder(ViewHolder holder, int position) {
    563         switch (holder.getItemViewType()) {
    564             case ICON_VIEW_TYPE: {
    565                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
    566                 BubbleTextView icon = (BubbleTextView) holder.mContent;
    567                 icon.applyFromApplicationInfo(info);
    568                 icon.setAccessibilityDelegate(
    569                         LauncherAppState.getInstance().getAccessibilityDelegate());
    570                 break;
    571             }
    572             case PREDICTION_ICON_VIEW_TYPE: {
    573                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
    574                 BubbleTextView icon = (BubbleTextView) holder.mContent;
    575                 icon.applyFromApplicationInfo(info);
    576                 icon.setAccessibilityDelegate(
    577                         LauncherAppState.getInstance().getAccessibilityDelegate());
    578                 break;
    579             }
    580             case EMPTY_SEARCH_VIEW_TYPE:
    581                 TextView emptyViewText = (TextView) holder.mContent;
    582                 emptyViewText.setText(mEmptySearchMessage);
    583                 emptyViewText.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
    584                         Gravity.START | Gravity.CENTER_VERTICAL);
    585                 break;
    586             case SEARCH_MARKET_VIEW_TYPE:
    587                 TextView searchView = (TextView) holder.mContent;
    588                 if (mMarketSearchIntent != null) {
    589                     searchView.setVisibility(View.VISIBLE);
    590                     searchView.setContentDescription(mMarketSearchMessage);
    591                     searchView.setGravity(mApps.hasNoFilteredResults() ? Gravity.CENTER :
    592                             Gravity.START | Gravity.CENTER_VERTICAL);
    593                     searchView.setText(mMarketSearchMessage);
    594                 } else {
    595                     searchView.setVisibility(View.GONE);
    596                 }
    597                 break;
    598         }
    599         if (mBindViewCallback != null) {
    600             mBindViewCallback.onBindView(holder);
    601         }
    602     }
    603 
    604     @Override
    605     public boolean onFailedToRecycleView(ViewHolder holder) {
    606         // Always recycle and we will reset the view when it is bound
    607         return true;
    608     }
    609 
    610     @Override
    611     public int getItemCount() {
    612         return mApps.getAdapterItems().size();
    613     }
    614 
    615     @Override
    616     public int getItemViewType(int position) {
    617         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
    618         return item.viewType;
    619     }
    620 }
    621