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