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 */
16package com.android.launcher3.allapps;
17
18import android.content.Context;
19import android.content.Intent;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
22import android.content.res.Resources;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.PointF;
26import android.graphics.Rect;
27import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
28import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat;
29import android.support.v4.view.accessibility.AccessibilityRecordCompat;
30import android.support.v4.view.accessibility.AccessibilityEventCompat;
31import android.net.Uri;
32import android.support.v7.widget.GridLayoutManager;
33import android.support.v7.widget.RecyclerView;
34import android.support.v7.widget.RecyclerView.Recycler;
35import android.support.v7.widget.RecyclerView.State;
36import android.util.Log;
37import android.view.Gravity;
38import android.view.LayoutInflater;
39import android.view.View;
40import android.view.ViewConfiguration;
41import android.view.ViewGroup;
42import android.view.accessibility.AccessibilityEvent;
43import android.widget.TextView;
44import com.android.launcher3.AppInfo;
45import com.android.launcher3.BubbleTextView;
46import com.android.launcher3.Launcher;
47import com.android.launcher3.LauncherAppState;
48import com.android.launcher3.R;
49import com.android.launcher3.Utilities;
50
51import java.util.HashMap;
52import java.util.List;
53
54
55/**
56 * The grid view adapter of all the apps.
57 */
58public 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