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 {
onBindView(ViewHolder holder)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 
ViewHolder(View v)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 
AppsGridLayoutManager(Context context)97         public AppsGridLayoutManager(Context context) {
98             super(context, 1, GridLayoutManager.VERTICAL, false);
99         }
100 
101         @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)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
onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, View host, AccessibilityNodeInfoCompat info)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
getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)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          */
getEmptyRowForAccessibility(int viewType)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 
GridSpanSizer()180         public GridSpanSizer() {
181             super();
182             setSpanIndexCacheEnabled(true);
183         }
184 
185         @Override
getSpanSize(int position)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
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)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
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)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          */
getAndCacheSectionBounds(String sectionName)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          */
isValidHolderAndChild(ViewHolder holder, View child, List<AlphabeticalAppsList.AdapterItem> items)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          */
shouldDrawItemDivider(ViewHolder holder, List<AlphabeticalAppsList.AdapterItem> items)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          */
shouldDrawItemSection(ViewHolder holder, int childIndex, List<AlphabeticalAppsList.AdapterItem> items)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 
AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnTouchListener touchListener, View.OnClickListener iconClickListener, View.OnLongClickListener iconLongClickListener)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      */
setNumAppsPerRow(int appsPerRow)451     public void setNumAppsPerRow(int appsPerRow) {
452         mAppsPerRow = appsPerRow;
453         mGridLayoutMgr.setSpanCount(appsPerRow);
454     }
455 
setSearchController(AllAppsSearchBarController searchController)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      */
setLastSearchQuery(String query)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      */
setBindViewCallback(BindViewCallback cb)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      */
updateBackgroundPadding(Rect padding)494     public void updateBackgroundPadding(Rect padding) {
495         mBackgroundPadding.set(padding);
496     }
497 
498     /**
499      * Returns the grid layout manager.
500      */
getLayoutManager()501     public GridLayoutManager getLayoutManager() {
502         return mGridLayoutMgr;
503     }
504 
505     /**
506      * Returns the item decoration for the recycler view.
507      */
getItemDecoration()508     public RecyclerView.ItemDecoration getItemDecoration() {
509         // We don't draw any headers when we are uncomfortably dense
510         return mItemDecoration;
511     }
512 
513     @Override
onCreateViewHolder(ViewGroup parent, int viewType)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
onBindViewHolder(ViewHolder holder, int position)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
onFailedToRecycleView(ViewHolder holder)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
getItemCount()611     public int getItemCount() {
612         return mApps.getAdapterItems().size();
613     }
614 
615     @Override
getItemViewType(int position)616     public int getItemViewType(int position) {
617         AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
618         return item.viewType;
619     }
620 }
621