Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java |
| similarity index 34% |
| copy from chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java |
| copy to chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java |
| index aa2fab2040a2760bd113edb1fdf1afa7f78708a6..2143c590c1b5ea768d29bf718f04b28ba84f0b89 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/NewTabPageAdapter.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java |
| @@ -4,28 +4,12 @@ |
| package org.chromium.chrome.browser.ntp.cards; |
| -import android.annotation.SuppressLint; |
| -import android.graphics.Canvas; |
| -import android.support.annotation.StringRes; |
| -import android.support.v7.widget.RecyclerView; |
| -import android.support.v7.widget.RecyclerView.Adapter; |
| -import android.support.v7.widget.RecyclerView.ViewHolder; |
| -import android.support.v7.widget.helper.ItemTouchHelper; |
| -import android.view.View; |
| -import android.view.ViewGroup; |
| - |
| import org.chromium.base.Log; |
| -import org.chromium.base.VisibleForTesting; |
| -import org.chromium.chrome.R; |
| -import org.chromium.chrome.browser.ntp.NewTabPage.DestructionObserver; |
| import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager; |
| -import org.chromium.chrome.browser.ntp.UiConfig; |
| import org.chromium.chrome.browser.ntp.snippets.CategoryInt; |
| import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; |
| import org.chromium.chrome.browser.ntp.snippets.CategoryStatus.CategoryStatusEnum; |
| -import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; |
| import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; |
| -import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; |
| import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; |
| import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig; |
| import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource; |
| @@ -37,146 +21,35 @@ import java.util.List; |
| import java.util.Map; |
| /** |
| - * A class that handles merging above the fold elements and below the fold cards into an adapter |
| - * that will be used to back the NTP RecyclerView. The first element in the adapter should always be |
| - * the above-the-fold view (containing the logo, search box, and most visited tiles) and subsequent |
| - * elements will be the cards shown to the user |
| + * A node in the tree containing a list of all suggestions sections. It listens to changes in the |
| + * suggestions source and updates the corresponding sections. |
| */ |
| -public class NewTabPageAdapter |
| - extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Observer, NodeParent { |
| +public class SectionList extends InnerNode implements SuggestionsSource.Observer { |
| private static final String TAG = "Ntp"; |
| - private final NewTabPageManager mNewTabPageManager; |
| - private final View mAboveTheFoldView; |
| - private final UiConfig mUiConfig; |
| - private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallbacks(); |
| - private final OfflinePageBridge mOfflinePageBridge; |
| - private NewTabPageRecyclerView mRecyclerView; |
| - |
| - /** |
| - * List of all child nodes (which can themselves contain multiple child nodes). |
| - */ |
| - private final List<TreeNode> mChildren = new ArrayList<>(); |
| - private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); |
| - private final SignInPromo mSigninPromo; |
| - private final AllDismissedItem mAllDismissed; |
| - private final Footer mFooter; |
| - private final SpacingItem mBottomSpacer = new SpacingItem(); |
| - private final InnerNode mRoot; |
| - |
| /** Maps suggestion categories to sections, with stable iteration ordering. */ |
| private final Map<Integer, SuggestionsSection> mSections = new LinkedHashMap<>(); |
| + private final List<TreeNode> mChildren = new ArrayList<>(); |
| + private final NewTabPageManager mNewTabPageManager; |
| + private final OfflinePageBridge mOfflinePageBridge; |
| - private class ItemTouchCallbacks extends ItemTouchHelper.Callback { |
| - @Override |
| - public void onSwiped(ViewHolder viewHolder, int direction) { |
| - mRecyclerView.onItemDismissStarted(viewHolder); |
| - NewTabPageAdapter.this.dismissItem(viewHolder.getAdapterPosition()); |
| - } |
| - |
| - @Override |
| - public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) { |
| - // clearView() is called when an interaction with the item is finished, which does |
| - // not mean that the user went all the way and dismissed the item before releasing it. |
| - // We need to check that the item has been removed. |
| - if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) { |
| - mRecyclerView.onItemDismissFinished(viewHolder); |
| - } |
| - |
| - super.clearView(recyclerView, viewHolder); |
| - } |
| - |
| - @Override |
| - public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target) { |
| - assert false; // Drag and drop not supported, the method will never be called. |
| - return false; |
| - } |
| - |
| - @Override |
| - public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) { |
| - assert viewHolder instanceof NewTabPageViewHolder; |
| - |
| - int swipeFlags = 0; |
| - if (((NewTabPageViewHolder) viewHolder).isDismissable()) { |
| - swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; |
| - } |
| - |
| - return makeMovementFlags(0 /* dragFlags */, swipeFlags); |
| - } |
| + public SectionList(NodeParent parent, NewTabPageManager newTabPageManager, |
| + OfflinePageBridge offlinePageBridge) { |
| + super(parent); |
| + mNewTabPageManager = newTabPageManager; |
| + mNewTabPageManager.getSuggestionsSource().setObserver(this); |
|
dgn
2016/12/13 14:54:47
setObserver will hook into the native code and mig
|
| + mOfflinePageBridge = offlinePageBridge; |
| + } |
| - @Override |
| - public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, |
| - float dX, float dY, int actionState, boolean isCurrentlyActive) { |
| - assert viewHolder instanceof NewTabPageViewHolder; |
| - |
| - // The item has already been removed. We have nothing more to do. |
| - // In some cases a removed children may call this method when unrelated items are |
| - // interacted with, but this check also covers the case. |
| - // See https://crbug.com/664466, b/32900699 |
| - if (viewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) return; |
| - |
| - // We use our own implementation of the dismissal animation, so we don't call the |
| - // parent implementation. (by default it changes the translation-X and elevation) |
| - mRecyclerView.updateViewStateForDismiss(dX, (NewTabPageViewHolder) viewHolder); |
| - |
| - // If there is another item that should be animated at the same time, do the same to it. |
| - NewTabPageViewHolder siblingViewHolder = getDismissSibling(viewHolder); |
| - if (siblingViewHolder != null) { |
| - mRecyclerView.updateViewStateForDismiss(dX, siblingViewHolder); |
| - } |
| - } |
| + @Override |
| + public void init() { |
| + super.init(); |
| + resetSections(/* alwaysAllowEmptySections = */ false); |
| } |
| - /** |
| - * Creates the adapter that will manage all the cards to display on the NTP. |
| - * |
| - * @param manager the NewTabPageManager to use to interact with the rest of the system. |
| - * @param aboveTheFoldView the layout encapsulating all the above-the-fold elements |
| - * (logo, search box, most visited tiles) |
| - * @param uiConfig the NTP UI configuration, to be passed to created views. |
| - * @param offlinePageBridge the OfflinePageBridge used to determine if articles are available |
| - * offline. |
| - * |
| - */ |
| - public NewTabPageAdapter(NewTabPageManager manager, View aboveTheFoldView, UiConfig uiConfig, |
| - OfflinePageBridge offlinePageBridge) { |
| - mNewTabPageManager = manager; |
| - mAboveTheFoldView = aboveTheFoldView; |
| - mUiConfig = uiConfig; |
| - mOfflinePageBridge = offlinePageBridge; |
| - mRoot = new InnerNode(this) { |
| - @Override |
| - protected List<TreeNode> getChildren() { |
| - return mChildren; |
| - } |
| - |
| - @Override |
| - public void onItemRangeChanged(TreeNode child, int index, int count) { |
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet. |
| - super.onItemRangeChanged(child, index, count); |
| - } |
| - |
| - @Override |
| - public void onItemRangeInserted(TreeNode child, int index, int count) { |
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet. |
| - super.onItemRangeInserted(child, index, count); |
| - } |
| - |
| - @Override |
| - public void onItemRangeRemoved(TreeNode child, int index, int count) { |
| - if (mChildren.isEmpty()) return; // The sections have not been initialised yet. |
| - super.onItemRangeRemoved(child, index, count); |
| - } |
| - }; |
| - |
| - mSigninPromo = new SignInPromo(mRoot); |
| - mAllDismissed = new AllDismissedItem(mRoot); |
| - mFooter = new Footer(mRoot); |
| - DestructionObserver signInObserver = mSigninPromo.getObserver(); |
| - if (signInObserver != null) mNewTabPageManager.addDestructionObserver(signInObserver); |
| - |
| - resetSections(/*alwaysAllowEmptySections=*/false); |
| - mNewTabPageManager.getSuggestionsSource().setObserver(this); |
| + @Override |
| + protected List<TreeNode> getChildren() { |
| + return mChildren; |
| } |
| /** |
| @@ -204,8 +77,6 @@ public class NewTabPageAdapter |
| } |
| mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPerCategory); |
| - |
| - updateChildren(); |
| } |
| /** |
| @@ -224,17 +95,20 @@ public class NewTabPageAdapter |
| List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCategory(category); |
| SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(category); |
| + SuggestionsSection section = mSections.get(category); |
| + |
| // Do not show an empty section if not allowed. |
| if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySections) { |
| - mSections.remove(category); |
| + if (section != null) removeSection(section); |
| return 0; |
| } |
| // Create the section if needed. |
| - SuggestionsSection section = mSections.get(category); |
| if (section == null) { |
| - section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mOfflinePageBridge); |
| + section = new SuggestionsSection(this, mNewTabPageManager, mOfflinePageBridge, info); |
| mSections.put(category, section); |
| + mChildren.add(section); |
| + didAddChild(section); |
| } |
| // Add the new suggestions. |
| @@ -243,11 +117,6 @@ public class NewTabPageAdapter |
| return suggestions.size(); |
| } |
| - /** Returns callbacks to configure the interactions with the RecyclerView's items. */ |
| - public ItemTouchHelper.Callback getItemTouchCallbacks() { |
| - return mItemTouchCallbacks; |
| - } |
| - |
| @Override |
| public void onNewSuggestions(@CategoryInt int category) { |
| @CategoryStatusEnum |
| @@ -298,7 +167,9 @@ public class NewTabPageAdapter |
| return; |
| case CategoryStatus.SIGNED_OUT: |
| - // TODO(dgn): We currently can only reach this through an old variation parameter. |
| + resetSection(category, status, /* alwaysAllowEmptySections = */ false); |
| + return; |
| + |
| default: |
| mSections.get(category).setStatus(status); |
| return; |
| @@ -313,86 +184,7 @@ public class NewTabPageAdapter |
| @Override |
| public void onFullRefreshRequired() { |
| - resetSections(/*alwaysAllowEmptySections=*/false); |
| - } |
| - |
| - @Override |
| - @ItemViewType |
| - public int getItemViewType(int position) { |
| - return mRoot.getItemViewType(position); |
| - } |
| - |
| - @Override |
| - public NewTabPageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| - assert parent == mRecyclerView; |
| - |
| - switch (viewType) { |
| - case ItemViewType.ABOVE_THE_FOLD: |
| - return new NewTabPageViewHolder(mAboveTheFoldView); |
| - |
| - case ItemViewType.HEADER: |
| - return new SectionHeaderViewHolder(mRecyclerView, mUiConfig); |
| - |
| - case ItemViewType.SNIPPET: |
| - return new SnippetArticleViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig); |
| - |
| - case ItemViewType.SPACING: |
| - return new NewTabPageViewHolder(SpacingItem.createView(parent)); |
| - |
| - case ItemViewType.STATUS: |
| - return new StatusCardViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig); |
| - |
| - case ItemViewType.PROGRESS: |
| - return new ProgressViewHolder(mRecyclerView); |
| - |
| - case ItemViewType.ACTION: |
| - return new ActionItem.ViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig); |
| - |
| - case ItemViewType.PROMO: |
| - return new SignInPromo.ViewHolder(mRecyclerView, mNewTabPageManager, mUiConfig); |
| - |
| - case ItemViewType.FOOTER: |
| - return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager); |
| - |
| - case ItemViewType.ALL_DISMISSED: |
| - return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPageManager, this); |
| - } |
| - |
| - assert false : viewType; |
| - return null; |
| - } |
| - |
| - @Override |
| - public void onBindViewHolder(NewTabPageViewHolder holder, final int position) { |
| - mRoot.onBindViewHolder(holder, position); |
| - } |
| - |
| - @Override |
| - public int getItemCount() { |
| - return mRoot.getItemCount(); |
| - } |
| - |
| - public int getAboveTheFoldPosition() { |
| - return getChildPositionOffset(mAboveTheFold); |
| - } |
| - |
| - public int getFirstHeaderPosition() { |
| - return getFirstPositionForType(ItemViewType.HEADER); |
| - } |
| - |
| - public int getFirstCardPosition() { |
| - for (int i = 0; i < getItemCount(); ++i) { |
| - if (CardViewHolder.isCard(getItemViewType(i))) return i; |
| - } |
| - return RecyclerView.NO_POSITION; |
| - } |
| - |
| - int getBottomSpacerPosition() { |
| - return getChildPositionOffset(mBottomSpacer); |
| - } |
| - |
| - int getLastContentItemPosition() { |
| - return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter); |
| + resetSections(/* alwaysAllowEmptySections = */false); |
| } |
| private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions, |
| @@ -411,155 +203,6 @@ public class NewTabPageAdapter |
| mSections.get(category).addSuggestions(suggestions, status); |
| } |
| - private void updateChildren() { |
| - mChildren.clear(); |
| - mChildren.add(mAboveTheFold); |
| - mChildren.addAll(mSections.values()); |
| - mChildren.add(mSigninPromo); |
| - mChildren.add(mAllDismissed); |
| - mChildren.add(mFooter); |
| - mChildren.add(mBottomSpacer); |
| - |
| - updateAllDismissedVisibility(); |
| - |
| - // TODO(mvanouwerkerk): Notify about the subset of changed items. At least |mAboveTheFold| |
| - // has not changed when refreshing from the all dismissed state. |
| - notifyDataSetChanged(); |
| - } |
| - |
| - private void updateAllDismissedVisibility() { |
| - boolean showAllDismissed = hasAllBeenDismissed(); |
| - mAllDismissed.setVisible(showAllDismissed); |
| - mFooter.setVisible(!showAllDismissed); |
| - } |
| - |
| - private void removeSection(SuggestionsSection section) { |
| - mSections.remove(section.getCategory()); |
| - int startPos = getChildPositionOffset(section); |
| - mChildren.remove(section); |
| - notifyItemRangeRemoved(startPos, section.getItemCount()); |
| - |
| - updateAllDismissedVisibility(); |
| - |
| - notifyItemChanged(getBottomSpacerPosition()); |
| - } |
| - |
| - @Override |
| - public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCount) { |
| - assert child == mRoot; |
| - notifyItemRangeChanged(itemPosition, itemCount); |
| - } |
| - |
| - @Override |
| - public void onItemRangeInserted(TreeNode child, int itemPosition, int itemCount) { |
| - assert child == mRoot; |
| - notifyItemRangeInserted(itemPosition, itemCount); |
| - notifyItemChanged(getItemCount() - 1); // Refresh the spacer too. |
| - |
| - updateAllDismissedVisibility(); |
| - } |
| - |
| - @Override |
| - public void onItemRangeRemoved(TreeNode child, int itemPosition, int itemCount) { |
| - assert child == mRoot; |
| - notifyItemRangeRemoved(itemPosition, itemCount); |
| - notifyItemChanged(getItemCount() - 1); // Refresh the spacer too. |
| - |
| - updateAllDismissedVisibility(); |
| - } |
| - |
| - @Override |
| - public void onAttachedToRecyclerView(RecyclerView recyclerView) { |
| - super.onAttachedToRecyclerView(recyclerView); |
| - |
| - // We are assuming for now that the adapter is used with a single RecyclerView. |
| - // Getting the reference as we are doing here is going to be broken if that changes. |
| - assert mRecyclerView == null; |
| - |
| - // FindBugs chokes on the cast below when not checked, raising BC_UNCONFIRMED_CAST |
| - assert recyclerView instanceof NewTabPageRecyclerView; |
| - |
| - mRecyclerView = (NewTabPageRecyclerView) recyclerView; |
| - } |
| - |
| - /** |
| - * Dismisses the item at the provided adapter position. Can also cause the dismissal of other |
| - * items or even entire sections. |
| - */ |
| - // TODO(crbug.com/635567): Fix this properly. |
| - @SuppressLint("SwitchIntDef") |
| - public void dismissItem(int position) { |
| - int itemViewType = getItemViewType(position); |
| - |
| - // TODO(dgn): Polymorphism is supposed to allow to avoid that kind of stuff. |
| - switch (itemViewType) { |
| - case ItemViewType.STATUS: |
| - case ItemViewType.ACTION: |
| - dismissSection(getSuggestionsSection(position)); |
| - return; |
| - |
| - case ItemViewType.SNIPPET: |
| - dismissSuggestion(position); |
| - return; |
| - |
| - case ItemViewType.PROMO: |
| - dismissPromo(); |
| - return; |
| - |
| - default: |
| - Log.wtf(TAG, "Unsupported dismissal of item of type %d", itemViewType); |
| - return; |
| - } |
| - } |
| - |
| - private void dismissSection(SuggestionsSection section) { |
| - assert SnippetsConfig.isSectionDismissalEnabled(); |
| - |
| - announceItemRemoved(section.getHeaderText()); |
| - |
| - mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory()); |
| - removeSection(section); |
| - } |
| - |
| - private void dismissSuggestion(int position) { |
| - SnippetArticle suggestion = mRoot.getSuggestionAt(position); |
| - SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource(); |
| - if (suggestionsSource == null) { |
| - // It is possible for this method to be called after the NewTabPage has had destroy() |
| - // called. This can happen when NewTabPageRecyclerView.dismissWithAnimation() is called |
| - // and the animation ends after the user has navigated away. In this case we cannot |
| - // inform the native side that the snippet has been dismissed (http://crbug.com/649299). |
| - return; |
| - } |
| - |
| - announceItemRemoved(suggestion.mTitle); |
| - |
| - suggestionsSource.dismissSuggestion(suggestion); |
| - SuggestionsSection section = getSuggestionsSection(position); |
| - section.removeSuggestion(suggestion); |
| - } |
| - |
| - private void dismissPromo() { |
| - announceItemRemoved(mSigninPromo.getHeader()); |
| - mSigninPromo.dismiss(); |
| - } |
| - |
| - /** |
| - * Returns another view holder that should be dismissed at the same time as the provided one. |
| - */ |
| - public NewTabPageViewHolder getDismissSibling(ViewHolder viewHolder) { |
| - int swipePos = viewHolder.getAdapterPosition(); |
| - int siblingPosDelta = mRoot.getDismissSiblingPosDelta(swipePos); |
| - if (siblingPosDelta == 0) return null; |
| - |
| - return (NewTabPageViewHolder) mRecyclerView.findViewHolderForAdapterPosition( |
| - siblingPosDelta + swipePos); |
| - } |
| - |
| - private boolean hasAllBeenDismissed() { |
| - return mSections.isEmpty() && !mSigninPromo.isVisible(); |
| - } |
| - |
| private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatusEnum int status) { |
| // We never want to add suggestions from unknown categories. |
| if (!mSections.containsKey(category)) return false; |
| @@ -576,54 +219,39 @@ public class NewTabPageAdapter |
| } |
| /** |
| - * @param itemPosition The position of an item in the adapter. |
| - * @return Returns the {@link SuggestionsSection} that contains the item at |
| - * {@code itemPosition}, or null if the item is not part of one. |
| + * Dismisses a section. |
| + * @param section The section to be dismissed. |
| */ |
| - private SuggestionsSection getSuggestionsSection(int itemPosition) { |
| - TreeNode child = mRoot.getChildForPosition(itemPosition); |
| - if (!(child instanceof SuggestionsSection)) return null; |
| - return (SuggestionsSection) child; |
| - } |
| - |
| - private int getChildPositionOffset(TreeNode child) { |
| - return mRoot.getStartingOffsetForChild(child); |
| - } |
| - |
| - @VisibleForTesting |
| - SnippetArticle getSuggestionAt(int position) { |
| - return mRoot.getSuggestionAt(position); |
| - } |
| + public void dismissSection(SuggestionsSection section) { |
| + assert SnippetsConfig.isSectionDismissalEnabled(); |
| - @VisibleForTesting |
| - int getFirstPositionForType(@ItemViewType int viewType) { |
| - int count = getItemCount(); |
| - for (int i = 0; i < count; i++) { |
| - if (getItemViewType(i) == viewType) return i; |
| - } |
| - return RecyclerView.NO_POSITION; |
| + mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory()); |
| + removeSection(section); |
| } |
| - SuggestionsSection getSectionForTesting(@CategoryInt int category) { |
| - return mSections.get(category); |
| + private void removeSection(SuggestionsSection section) { |
| + mSections.remove(section.getCategory()); |
| + willRemoveChild(section); |
| + mChildren.remove(section); |
| } |
| - InnerNode getRootForTesting() { |
| - return mRoot; |
| + /** |
| + * Restores any sections that have been dismissed and triggers a new fetch. |
| + */ |
| + public void restoreDismissedSections() { |
| + mNewTabPageManager.getSuggestionsSource().restoreDismissedCategories(); |
| + resetSections(/* allowEmptySections = */ true); |
| + mNewTabPageManager.getSuggestionsSource().fetchRemoteSuggestions(); |
| } |
| - private void announceItemRemoved(String itemTitle) { |
| - // In tests the RecyclerView can be null. |
| - if (mRecyclerView == null) return; |
| - |
| - mRecyclerView.announceForAccessibility(mRecyclerView.getResources().getString( |
| - R.string.ntp_accessibility_item_removed, itemTitle)); |
| + /** |
| + * @return Whether the list of sections is empty. |
| + */ |
| + public boolean isEmpty() { |
| + return mSections.isEmpty(); |
| } |
| - private void announceItemRemoved(@StringRes int stringToAnnounce) { |
| - // In tests the RecyclerView can be null. |
| - if (mRecyclerView == null) return; |
| - |
| - announceItemRemoved(mRecyclerView.getResources().getString(stringToAnnounce)); |
| + SuggestionsSection getSectionForTesting(@CategoryInt int categoryId) { |
| + return mSections.get(categoryId); |
| } |
| } |