Commit 67606628 authored by bauerb's avatar bauerb Committed by Commit bot

[Android NTP] Move suggestion sections into a separate node.

Also, define how initialization of nodes works, by adding an init()
method that is called after creating a node.

Initialization now happens after the full tree structure has been
created, and recursively processes all nodes in the tree. Subtrees that
are added later use the same pattern; InnerNode now has helper methods
for this.

BUG=616090

Review-Url: https://codereview.chromium.org/2513453004
Cr-Commit-Position: refs/heads/master@{#438135}
parent 61b14954
...@@ -14,7 +14,6 @@ import android.widget.TextView; ...@@ -14,7 +14,6 @@ import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.NewTabPageUma; import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
import java.util.Calendar; import java.util.Calendar;
...@@ -48,8 +47,7 @@ public class AllDismissedItem extends OptionalLeaf { ...@@ -48,8 +47,7 @@ public class AllDismissedItem extends OptionalLeaf {
public static class ViewHolder extends NewTabPageViewHolder { public static class ViewHolder extends NewTabPageViewHolder {
private final TextView mBodyTextView; private final TextView mBodyTextView;
public ViewHolder( public ViewHolder(ViewGroup root, final SectionList sections) {
ViewGroup root, final NewTabPageManager manager, final NewTabPageAdapter adapter) {
super(LayoutInflater.from(root.getContext()) super(LayoutInflater.from(root.getContext())
.inflate(R.layout.new_tab_page_all_dismissed, root, false)); .inflate(R.layout.new_tab_page_all_dismissed, root, false));
mBodyTextView = (TextView) itemView.findViewById(R.id.body_text); mBodyTextView = (TextView) itemView.findViewById(R.id.body_text);
...@@ -60,9 +58,7 @@ public class AllDismissedItem extends OptionalLeaf { ...@@ -60,9 +58,7 @@ public class AllDismissedItem extends OptionalLeaf {
public void onClick(View v) { public void onClick(View v) {
NewTabPageUma.recordAction( NewTabPageUma.recordAction(
NewTabPageUma.ACTION_CLICKED_ALL_DISMISSED_REFRESH); NewTabPageUma.ACTION_CLICKED_ALL_DISMISSED_REFRESH);
manager.getSuggestionsSource().restoreDismissedCategories(); sections.restoreDismissedSections();
adapter.resetSections(/*allowEmptySections=*/true);
manager.getSuggestionsSource().fetchRemoteSuggestions();
} }
}); });
} }
......
...@@ -16,6 +16,9 @@ public abstract class ChildNode implements TreeNode { ...@@ -16,6 +16,9 @@ public abstract class ChildNode implements TreeNode {
mParent = parent; mParent = parent;
} }
@Override
public void init() {}
protected void notifyItemRangeChanged(int index, int count) { protected void notifyItemRangeChanged(int index, int count) {
mParent.onItemRangeChanged(this, index, count); mParent.onItemRangeChanged(this, index, count);
} }
......
...@@ -108,4 +108,34 @@ public abstract class InnerNode extends ChildNode implements NodeParent { ...@@ -108,4 +108,34 @@ public abstract class InnerNode extends ChildNode implements NodeParent {
public void onItemRangeRemoved(TreeNode child, int index, int count) { public void onItemRangeRemoved(TreeNode child, int index, int count) {
notifyItemRangeRemoved(getStartingOffsetForChild(child) + index, count); notifyItemRangeRemoved(getStartingOffsetForChild(child) + index, count);
} }
@Override
public void init() {
super.init();
for (TreeNode child : getChildren()) {
child.init();
}
}
/**
* Helper method for adding a new child node. Notifies about the inserted items and initializes
* the child.
*
* @param child The child node to be added.
*/
protected void didAddChild(TreeNode child) {
int count = child.getItemCount();
if (count > 0) onItemRangeInserted(child, 0, count);
child.init();
}
/**
* Helper method for removing a child node. Notifies about the removed items.
*
* @param child The child node to be removed.
*/
protected void willRemoveChild(TreeNode child) {
int count = child.getItemCount();
if (count > 0) onItemRangeRemoved(child, 0, count);
}
} }
...@@ -42,6 +42,9 @@ public abstract class Leaf implements TreeNode { ...@@ -42,6 +42,9 @@ public abstract class Leaf implements TreeNode {
return 0; return 0;
} }
@Override
public void init() {}
/** /**
* Display the data for this item. * Display the data for this item.
* @param holder The view holder that should be updated. * @param holder The view holder that should be updated.
......
...@@ -17,24 +17,16 @@ import android.view.ViewGroup; ...@@ -17,24 +17,16 @@ import android.view.ViewGroup;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R; 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.NewTabPageView.NewTabPageManager;
import org.chromium.chrome.browser.ntp.UiConfig; 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.SectionHeaderViewHolder;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticleViewHolder; 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; import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import java.util.ArrayList; import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List; 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 * A class that handles merging above the fold elements and below the fold cards into an adapter
...@@ -42,30 +34,27 @@ import java.util.Map; ...@@ -42,30 +34,27 @@ import java.util.Map;
* the above-the-fold view (containing the logo, search box, and most visited tiles) and subsequent * 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 * elements will be the cards shown to the user
*/ */
public class NewTabPageAdapter public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements NodeParent {
extends Adapter<NewTabPageViewHolder> implements SuggestionsSource.Observer, NodeParent {
private static final String TAG = "Ntp"; private static final String TAG = "Ntp";
private final NewTabPageManager mNewTabPageManager; private final NewTabPageManager mNewTabPageManager;
private final View mAboveTheFoldView; private final View mAboveTheFoldView;
private final UiConfig mUiConfig; private final UiConfig mUiConfig;
private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallbacks(); private final ItemTouchCallbacks mItemTouchCallbacks = new ItemTouchCallbacks();
private final OfflinePageBridge mOfflinePageBridge;
private NewTabPageRecyclerView mRecyclerView; private NewTabPageRecyclerView mRecyclerView;
/** /**
* List of all child nodes (which can themselves contain multiple child nodes). * List of all child nodes (which can themselves contain multiple child nodes).
*/ */
private final List<TreeNode> mChildren = new ArrayList<>(); private final List<TreeNode> mChildren;
private final InnerNode mRoot;
private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem(); private final AboveTheFoldItem mAboveTheFold = new AboveTheFoldItem();
private final SectionList mSections;
private final SignInPromo mSigninPromo; private final SignInPromo mSigninPromo;
private final AllDismissedItem mAllDismissed; private final AllDismissedItem mAllDismissed;
private final Footer mFooter; private final Footer mFooter;
private final SpacingItem mBottomSpacer = new SpacingItem(); 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 class ItemTouchCallbacks extends ItemTouchHelper.Callback { private class ItemTouchCallbacks extends ItemTouchHelper.Callback {
@Override @Override
...@@ -143,104 +132,23 @@ public class NewTabPageAdapter ...@@ -143,104 +132,23 @@ public class NewTabPageAdapter
mNewTabPageManager = manager; mNewTabPageManager = manager;
mAboveTheFoldView = aboveTheFoldView; mAboveTheFoldView = aboveTheFoldView;
mUiConfig = uiConfig; mUiConfig = uiConfig;
mOfflinePageBridge = offlinePageBridge;
mRoot = new InnerNode(this) { mRoot = new InnerNode(this) {
@Override @Override
protected List<TreeNode> getChildren() { protected List<TreeNode> getChildren() {
return mChildren; 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); mSections = new SectionList(mRoot, mNewTabPageManager, offlinePageBridge);
mSigninPromo = new SignInPromo(mRoot, mNewTabPageManager);
mAllDismissed = new AllDismissedItem(mRoot); mAllDismissed = new AllDismissedItem(mRoot);
mFooter = new Footer(mRoot); mFooter = new Footer(mRoot);
DestructionObserver signInObserver = mSigninPromo.getObserver();
if (signInObserver != null) mNewTabPageManager.addDestructionObserver(signInObserver);
resetSections(/*alwaysAllowEmptySections=*/false);
mNewTabPageManager.getSuggestionsSource().setObserver(this);
}
/**
* Resets the sections, reloading the whole new tab page content.
* @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
* they are empty, even when they are normally not.
*/
public void resetSections(boolean alwaysAllowEmptySections) {
mSections.clear();
mChildren.clear();
SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
int[] categories = suggestionsSource.getCategories();
int[] suggestionsPerCategory = new int[categories.length];
int i = 0;
for (int category : categories) {
int categoryStatus = suggestionsSource.getCategoryStatus(category);
if (categoryStatus == CategoryStatus.LOADING_ERROR
|| categoryStatus == CategoryStatus.NOT_PROVIDED
|| categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISABLED)
continue;
suggestionsPerCategory[i++] =
resetSection(category, categoryStatus, alwaysAllowEmptySections);
}
mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPerCategory);
updateChildren();
}
/**
* Resets the section for {@code category}. Removes the section if there are no suggestions for
* it and it is not allowed to be empty. Otherwise, creates the section if it is not present
* yet. Sets the available suggestions on the section.
* @param category The category for which the section must be reset.
* @param categoryStatus The category status.
* @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
* they are empty, even when they are normally not.
* @return The number of suggestions for the section.
*/
private int resetSection(@CategoryInt int category, @CategoryStatusEnum int categoryStatus,
boolean alwaysAllowEmptySections) {
SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
List<SnippetArticle> suggestions = suggestionsSource.getSuggestionsForCategory(category);
SuggestionsCategoryInfo info = suggestionsSource.getCategoryInfo(category);
// Do not show an empty section if not allowed.
if (suggestions.isEmpty() && !info.showIfEmpty() && !alwaysAllowEmptySections) {
mSections.remove(category);
return 0;
}
// Create the section if needed.
SuggestionsSection section = mSections.get(category);
if (section == null) {
section = new SuggestionsSection(mRoot, info, mNewTabPageManager, mOfflinePageBridge);
mSections.put(category, section);
}
// Add the new suggestions. mChildren = Arrays.asList(
setSuggestions(category, suggestions, categoryStatus); mAboveTheFold, mSections, mSigninPromo, mAllDismissed, mFooter, mBottomSpacer);
mRoot.init();
return suggestions.size(); updateAllDismissedVisibility();
} }
/** Returns callbacks to configure the interactions with the RecyclerView's items. */ /** Returns callbacks to configure the interactions with the RecyclerView's items. */
...@@ -248,74 +156,6 @@ public class NewTabPageAdapter ...@@ -248,74 +156,6 @@ public class NewTabPageAdapter
return mItemTouchCallbacks; return mItemTouchCallbacks;
} }
@Override
public void onNewSuggestions(@CategoryInt int category) {
@CategoryStatusEnum
int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
if (!canLoadSuggestions(category, status)) return;
// We never want to refresh the suggestions if we already have some content.
if (mSections.get(category).hasSuggestions()) return;
List<SnippetArticle> suggestions =
mNewTabPageManager.getSuggestionsSource().getSuggestionsForCategory(category);
Log.d(TAG, "Received %d new suggestions for category %d.", suggestions.size(), category);
// At first, there might be no suggestions available, we wait until they have been fetched.
if (suggestions.isEmpty()) return;
setSuggestions(category, suggestions, status);
}
@Override
public void onMoreSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions) {
@CategoryStatusEnum
int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
if (!canLoadSuggestions(category, status)) return;
setSuggestions(category, suggestions, status);
}
@Override
public void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatusEnum int status) {
// Observers should not be registered for this state.
assert status != CategoryStatus.ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
// If there is no section for this category there is nothing to do.
if (!mSections.containsKey(category)) return;
switch (status) {
case CategoryStatus.NOT_PROVIDED:
// The section provider has gone away. Keep open UIs as they are.
return;
case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
case CategoryStatus.LOADING_ERROR:
// Need to remove the entire section from the UI immediately.
removeSection(mSections.get(category));
return;
case CategoryStatus.SIGNED_OUT:
// TODO(dgn): We currently can only reach this through an old variation parameter.
default:
mSections.get(category).setStatus(status);
return;
}
}
@Override
public void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {
if (!mSections.containsKey(category)) return;
mSections.get(category).removeSuggestionById(idWithinCategory);
}
@Override
public void onFullRefreshRequired() {
resetSections(/*alwaysAllowEmptySections=*/false);
}
@Override @Override
@ItemViewType @ItemViewType
public int getItemViewType(int position) { public int getItemViewType(int position) {
...@@ -355,7 +195,7 @@ public class NewTabPageAdapter ...@@ -355,7 +195,7 @@ public class NewTabPageAdapter
return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager); return new Footer.ViewHolder(mRecyclerView, mNewTabPageManager);
case ItemViewType.ALL_DISMISSED: case ItemViewType.ALL_DISMISSED:
return new AllDismissedItem.ViewHolder(mRecyclerView, mNewTabPageManager, this); return new AllDismissedItem.ViewHolder(mRecyclerView, mSections);
} }
assert false : viewType; assert false : viewType;
...@@ -387,44 +227,12 @@ public class NewTabPageAdapter ...@@ -387,44 +227,12 @@ public class NewTabPageAdapter
return RecyclerView.NO_POSITION; return RecyclerView.NO_POSITION;
} }
int getBottomSpacerPosition() {
return getChildPositionOffset(mBottomSpacer);
}
int getLastContentItemPosition() { int getLastContentItemPosition() {
return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter); return getChildPositionOffset(hasAllBeenDismissed() ? mAllDismissed : mFooter);
} }
private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions, int getBottomSpacerPosition() {
@CategoryStatusEnum int status) { return getChildPositionOffset(mBottomSpacer);
// Count the number of suggestions before this category.
int globalPositionOffset = 0;
for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()) {
if (entry.getKey() == category) break;
globalPositionOffset += entry.getValue().getSuggestionsCount();
}
// Assign global indices to the new suggestions.
for (SnippetArticle suggestion : suggestions) {
suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosition;
}
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() { private void updateAllDismissedVisibility() {
...@@ -433,17 +241,6 @@ public class NewTabPageAdapter ...@@ -433,17 +241,6 @@ public class NewTabPageAdapter
mFooter.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 @Override
public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCount) { public void onItemRangeChanged(TreeNode child, int itemPosition, int itemCount) {
assert child == mRoot; assert child == mRoot;
...@@ -495,7 +292,7 @@ public class NewTabPageAdapter ...@@ -495,7 +292,7 @@ public class NewTabPageAdapter
switch (itemViewType) { switch (itemViewType) {
case ItemViewType.STATUS: case ItemViewType.STATUS:
case ItemViewType.ACTION: case ItemViewType.ACTION:
dismissSection(getSuggestionsSection(position)); dismissSection(position);
return; return;
case ItemViewType.SNIPPET: case ItemViewType.SNIPPET:
...@@ -512,13 +309,10 @@ public class NewTabPageAdapter ...@@ -512,13 +309,10 @@ public class NewTabPageAdapter
} }
} }
private void dismissSection(SuggestionsSection section) { private void dismissSection(int position) {
assert SnippetsConfig.isSectionDismissalEnabled(); SuggestionsSection section = getSuggestionsSection(position);
mSections.dismissSection(section);
announceItemRemoved(section.getHeaderText()); announceItemRemoved(section.getHeaderText());
mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory());
removeSection(section);
} }
private void dismissSuggestion(int position) { private void dismissSuggestion(int position) {
...@@ -560,28 +354,15 @@ public class NewTabPageAdapter ...@@ -560,28 +354,15 @@ public class NewTabPageAdapter
return mSections.isEmpty() && !mSigninPromo.isVisible(); 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;
// The status may have changed while the suggestions were loading, perhaps they should not
// be displayed any more.
if (!SnippetsBridge.isCategoryEnabled(status)) {
Log.w(TAG, "Received suggestions for a disabled category (id=%d, status=%d)", category,
status);
return false;
}
return true;
}
/** /**
* @param itemPosition The position of an item in the adapter. * @param itemPosition The position of an item in the adapter.
* @return Returns the {@link SuggestionsSection} that contains the item at * @return Returns the {@link SuggestionsSection} that contains the item at
* {@code itemPosition}, or null if the item is not part of one. * {@code itemPosition}, or null if the item is not part of one.
*/ */
private SuggestionsSection getSuggestionsSection(int itemPosition) { private SuggestionsSection getSuggestionsSection(int itemPosition) {
TreeNode child = mRoot.getChildForPosition(itemPosition); int relativePosition = itemPosition - mRoot.getStartingOffsetForChild(mSections);
assert relativePosition >= 0;
TreeNode child = mSections.getChildForPosition(relativePosition);
if (!(child instanceof SuggestionsSection)) return null; if (!(child instanceof SuggestionsSection)) return null;
return (SuggestionsSection) child; return (SuggestionsSection) child;
} }
...@@ -604,8 +385,8 @@ public class NewTabPageAdapter ...@@ -604,8 +385,8 @@ public class NewTabPageAdapter
return RecyclerView.NO_POSITION; return RecyclerView.NO_POSITION;
} }
SuggestionsSection getSectionForTesting(@CategoryInt int category) { SectionList getSectionListForTesting() {
return mSections.get(category); return mSections;
} }
InnerNode getRootForTesting() { InnerNode getRootForTesting() {
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.ntp.cards;
import org.chromium.base.Log;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
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.SnippetArticle;
import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
import org.chromium.chrome.browser.ntp.snippets.SnippetsConfig;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 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 SectionList extends InnerNode implements SuggestionsSource.Observer {
private static final String TAG = "Ntp";
/** 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;
public SectionList(NodeParent parent, NewTabPageManager newTabPageManager,
OfflinePageBridge offlinePageBridge) {
super(parent);
mNewTabPageManager = newTabPageManager;
mNewTabPageManager.getSuggestionsSource().setObserver(this);
mOfflinePageBridge = offlinePageBridge;
}
@Override
public void init() {
super.init();
resetSections(/* alwaysAllowEmptySections = */ false);
}
@Override
protected List<TreeNode> getChildren() {
return mChildren;
}
/**
* Resets the sections, reloading the whole new tab page content.
* @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
* they are empty, even when they are normally not.
*/
public void resetSections(boolean alwaysAllowEmptySections) {
mSections.clear();
mChildren.clear();
SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
int[] categories = suggestionsSource.getCategories();
int[] suggestionsPerCategory = new int[categories.length];
int i = 0;
for (int category : categories) {
int categoryStatus = suggestionsSource.getCategoryStatus(category);
if (categoryStatus == CategoryStatus.LOADING_ERROR
|| categoryStatus == CategoryStatus.NOT_PROVIDED
|| categoryStatus == CategoryStatus.CATEGORY_EXPLICITLY_DISABLED)
continue;
suggestionsPerCategory[i++] =
resetSection(category, categoryStatus, alwaysAllowEmptySections);
}
mNewTabPageManager.trackSnippetsPageImpression(categories, suggestionsPerCategory);
}
/**
* Resets the section for {@code category}. Removes the section if there are no suggestions for
* it and it is not allowed to be empty. Otherwise, creates the section if it is not present
* yet. Sets the available suggestions on the section.
* @param category The category for which the section must be reset.
* @param categoryStatus The category status.
* @param alwaysAllowEmptySections Whether sections are always allowed to be displayed when
* they are empty, even when they are normally not.
* @return The number of suggestions for the section.
*/
private int resetSection(@CategoryInt int category, @CategoryStatusEnum int categoryStatus,
boolean alwaysAllowEmptySections) {
SuggestionsSource suggestionsSource = mNewTabPageManager.getSuggestionsSource();
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) {
if (section != null) removeSection(section);
return 0;
}
// Create the section if needed.
if (section == null) {
section = new SuggestionsSection(this, mNewTabPageManager, mOfflinePageBridge, info);
mSections.put(category, section);
mChildren.add(section);
didAddChild(section);
}
// Add the new suggestions.
setSuggestions(category, suggestions, categoryStatus);
return suggestions.size();
}
@Override
public void onNewSuggestions(@CategoryInt int category) {
@CategoryStatusEnum
int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
if (!canLoadSuggestions(category, status)) return;
// We never want to refresh the suggestions if we already have some content.
if (mSections.get(category).hasSuggestions()) return;
List<SnippetArticle> suggestions =
mNewTabPageManager.getSuggestionsSource().getSuggestionsForCategory(category);
Log.d(TAG, "Received %d new suggestions for category %d.", suggestions.size(), category);
// At first, there might be no suggestions available, we wait until they have been fetched.
if (suggestions.isEmpty()) return;
setSuggestions(category, suggestions, status);
}
@Override
public void onMoreSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions) {
@CategoryStatusEnum
int status = mNewTabPageManager.getSuggestionsSource().getCategoryStatus(category);
if (!canLoadSuggestions(category, status)) return;
setSuggestions(category, suggestions, status);
}
@Override
public void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatusEnum int status) {
// Observers should not be registered for this state.
assert status != CategoryStatus.ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
// If there is no section for this category there is nothing to do.
if (!mSections.containsKey(category)) return;
switch (status) {
case CategoryStatus.NOT_PROVIDED:
// The section provider has gone away. Keep open UIs as they are.
return;
case CategoryStatus.CATEGORY_EXPLICITLY_DISABLED:
case CategoryStatus.LOADING_ERROR:
// Need to remove the entire section from the UI immediately.
removeSection(mSections.get(category));
return;
case CategoryStatus.SIGNED_OUT:
resetSection(category, status, /* alwaysAllowEmptySections = */ false);
return;
default:
mSections.get(category).setStatus(status);
return;
}
}
@Override
public void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {
if (!mSections.containsKey(category)) return;
mSections.get(category).removeSuggestionById(idWithinCategory);
}
@Override
public void onFullRefreshRequired() {
resetSections(/* alwaysAllowEmptySections = */false);
}
private void setSuggestions(@CategoryInt int category, List<SnippetArticle> suggestions,
@CategoryStatusEnum int status) {
// Count the number of suggestions before this category.
int globalPositionOffset = 0;
for (Map.Entry<Integer, SuggestionsSection> entry : mSections.entrySet()) {
if (entry.getKey() == category) break;
globalPositionOffset += entry.getValue().getSuggestionsCount();
}
// Assign global indices to the new suggestions.
for (SnippetArticle suggestion : suggestions) {
suggestion.mGlobalPosition = globalPositionOffset + suggestion.mPosition;
}
mSections.get(category).addSuggestions(suggestions, status);
}
private boolean canLoadSuggestions(@CategoryInt int category, @CategoryStatusEnum int status) {
// We never want to add suggestions from unknown categories.
if (!mSections.containsKey(category)) return false;
// The status may have changed while the suggestions were loading, perhaps they should not
// be displayed any more.
if (!SnippetsBridge.isCategoryEnabled(status)) {
Log.w(TAG, "Received suggestions for a disabled category (id=%d, status=%d)", category,
status);
return false;
}
return true;
}
/**
* Dismisses a section.
* @param section The section to be dismissed.
*/
public void dismissSection(SuggestionsSection section) {
assert SnippetsConfig.isSectionDismissalEnabled();
mNewTabPageManager.getSuggestionsSource().dismissCategory(section.getCategory());
removeSection(section);
}
private void removeSection(SuggestionsSection section) {
mSections.remove(section.getCategory());
willRemoveChild(section);
mChildren.remove(section);
}
/**
* Restores any sections that have been dismissed and triggers a new fetch.
*/
public void restoreDismissedSections() {
mNewTabPageManager.getSuggestionsSource().restoreDismissedCategories();
resetSections(/* allowEmptySections = */ true);
mNewTabPageManager.getSuggestionsSource().fetchRemoteSuggestions();
}
/**
* @return Whether the list of sections is empty.
*/
public boolean isEmpty() {
return mSections.isEmpty();
}
SuggestionsSection getSectionForTesting(@CategoryInt int categoryId) {
return mSections.get(categoryId);
}
}
...@@ -42,13 +42,24 @@ public class SignInPromo extends OptionalLeaf ...@@ -42,13 +42,24 @@ public class SignInPromo extends OptionalLeaf
@Nullable @Nullable
private final SigninObserver mObserver; private final SigninObserver mObserver;
public SignInPromo(NodeParent parent) { public SignInPromo(NodeParent parent, NewTabPageManager newTabPageManager) {
super(parent); super(parent);
mDismissed = ChromePreferenceManager.getInstance(ContextUtils.getApplicationContext()) mDismissed = ChromePreferenceManager.getInstance(ContextUtils.getApplicationContext())
.getNewTabPageSigninPromoDismissed(); .getNewTabPageSigninPromoDismissed();
final SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext()); SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext());
mObserver = mDismissed ? null : new SigninObserver(signinManager); if (mDismissed) {
mObserver = null;
} else {
mObserver = new SigninObserver(signinManager);
newTabPageManager.addDestructionObserver(mObserver);
}
}
@Override
public void init() {
super.init();
SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext());
setVisible(signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative()); setVisible(signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative());
} }
......
...@@ -20,6 +20,7 @@ import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; ...@@ -20,6 +20,7 @@ import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.offlinepages.OfflinePageItem; import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
...@@ -32,19 +33,20 @@ public class SuggestionsSection extends InnerNode { ...@@ -32,19 +33,20 @@ public class SuggestionsSection extends InnerNode {
private final SuggestionsCategoryInfo mCategoryInfo; private final SuggestionsCategoryInfo mCategoryInfo;
private final OfflinePageBridge mOfflinePageBridge; private final OfflinePageBridge mOfflinePageBridge;
private final List<TreeNode> mChildren = new ArrayList<>();
// Children // Children
private final SectionHeader mHeader; private final SectionHeader mHeader;
private final SuggestionsList mSuggestionsList; private final SuggestionsList mSuggestionsList;
private final StatusItem mStatus; private final StatusItem mStatus;
private final ProgressItem mProgressIndicator;
private final ActionItem mMoreButton; private final ActionItem mMoreButton;
private final ProgressItem mProgressIndicator;
private final List<TreeNode> mChildren;
private boolean mIsNtpDestroyed = false; private boolean mIsNtpDestroyed = false;
public SuggestionsSection(NodeParent parent, SuggestionsCategoryInfo info, public SuggestionsSection(NodeParent parent, NewTabPageManager manager,
NewTabPageManager manager, OfflinePageBridge offlinePageBridge) { OfflinePageBridge offlinePageBridge, SuggestionsCategoryInfo info) {
super(parent); super(parent);
mCategoryInfo = info; mCategoryInfo = info;
mOfflinePageBridge = offlinePageBridge; mOfflinePageBridge = offlinePageBridge;
...@@ -54,11 +56,23 @@ public class SuggestionsSection extends InnerNode { ...@@ -54,11 +56,23 @@ public class SuggestionsSection extends InnerNode {
mStatus = StatusItem.createNoSuggestionsItem(this); mStatus = StatusItem.createNoSuggestionsItem(this);
mMoreButton = new ActionItem(this); mMoreButton = new ActionItem(this);
mProgressIndicator = new ProgressItem(this); mProgressIndicator = new ProgressItem(this);
initializeChildren();
mChildren = Arrays.asList(
mHeader,
mSuggestionsList,
mStatus,
mMoreButton,
mProgressIndicator);
setupOfflinePageBridgeObserver(manager); setupOfflinePageBridgeObserver(manager);
} }
@Override
public void init() {
super.init();
refreshChildrenVisibility();
}
private static class SuggestionsList extends ChildNode implements Iterable<SnippetArticle> { private static class SuggestionsList extends ChildNode implements Iterable<SnippetArticle> {
private final List<SnippetArticle> mSuggestions = new ArrayList<>(); private final List<SnippetArticle> mSuggestions = new ArrayList<>();
private final SuggestionsCategoryInfo mCategoryInfo; private final SuggestionsCategoryInfo mCategoryInfo;
...@@ -131,18 +145,6 @@ public class SuggestionsSection extends InnerNode { ...@@ -131,18 +145,6 @@ public class SuggestionsSection extends InnerNode {
return mChildren; return mChildren;
} }
private void initializeChildren() {
mChildren.add(mHeader);
mChildren.add(mSuggestionsList);
// Optional leaves.
mChildren.add(mStatus); // Needs to be refreshed when the status changes.
mChildren.add(mMoreButton); // Needs to be refreshed when the suggestions change.
mChildren.add(mProgressIndicator); // Needs to be refreshed when the suggestions change.
refreshChildrenVisibility();
}
private void setupOfflinePageBridgeObserver(NewTabPageManager manager) { private void setupOfflinePageBridgeObserver(NewTabPageManager manager) {
final OfflinePageBridge.OfflinePageModelObserver observer = final OfflinePageBridge.OfflinePageModelObserver observer =
new OfflinePageBridge.OfflinePageModelObserver() { new OfflinePageBridge.OfflinePageModelObserver() {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.ntp.cards; package org.chromium.chrome.browser.ntp.cards;
import android.support.annotation.CallSuper;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
/** /**
...@@ -11,6 +13,17 @@ import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; ...@@ -11,6 +13,17 @@ import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
*/ */
interface TreeNode { interface TreeNode {
/** /**
* Initialize the node (and any children underneath it). This method should be called after the
* node has been added to the tree, i.e. when it is in the list of its parent's children.
* The node may notify its parent about changes that happen during initialization.
*/
@CallSuper
void init();
/**
* Returns the number of items under this subtree. This method may be called
* before initialization.
*
* @return The number of items under this subtree. * @return The number of items under this subtree.
* @see android.support.v7.widget.RecyclerView.Adapter#getItemCount() * @see android.support.v7.widget.RecyclerView.Adapter#getItemCount()
*/ */
......
...@@ -600,6 +600,7 @@ chrome_java_sources = [ ...@@ -600,6 +600,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/ntp/cards/ProgressIndicatorView.java", "java/src/org/chromium/chrome/browser/ntp/cards/ProgressIndicatorView.java",
"java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java", "java/src/org/chromium/chrome/browser/ntp/cards/ProgressItem.java",
"java/src/org/chromium/chrome/browser/ntp/cards/ProgressViewHolder.java", "java/src/org/chromium/chrome/browser/ntp/cards/ProgressViewHolder.java",
"java/src/org/chromium/chrome/browser/ntp/cards/SectionList.java",
"java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java", "java/src/org/chromium/chrome/browser/ntp/cards/SignInPromo.java",
"java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java", "java/src/org/chromium/chrome/browser/ntp/cards/SpacingItem.java",
"java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java", "java/src/org/chromium/chrome/browser/ntp/cards/StatusCardViewHolder.java",
......
...@@ -8,6 +8,7 @@ import static org.hamcrest.CoreMatchers.is; ...@@ -8,6 +8,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.junit.Before; import org.junit.Before;
...@@ -20,7 +21,7 @@ import org.robolectric.annotation.Config; ...@@ -20,7 +21,7 @@ import org.robolectric.annotation.Config;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.testing.local.LocalRobolectricTestRunner; import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.util.Arrays; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
...@@ -31,7 +32,7 @@ import java.util.List; ...@@ -31,7 +32,7 @@ import java.util.List;
public class InnerNodeTest { public class InnerNodeTest {
private static final int[] ITEM_COUNTS = {1, 2, 3, 0, 3, 2, 1}; private static final int[] ITEM_COUNTS = {1, 2, 3, 0, 3, 2, 1};
private final List<TreeNode> mChildren = Arrays.asList(new TreeNode[ITEM_COUNTS.length]); private final List<TreeNode> mChildren = new ArrayList<>();
@Mock private NodeParent mParent; @Mock private NodeParent mParent;
private InnerNode mInnerNode; private InnerNode mInnerNode;
...@@ -42,7 +43,7 @@ public class InnerNodeTest { ...@@ -42,7 +43,7 @@ public class InnerNodeTest {
for (int i = 0; i < ITEM_COUNTS.length; i++) { for (int i = 0; i < ITEM_COUNTS.length; i++) {
TreeNode child = mock(TreeNode.class); TreeNode child = mock(TreeNode.class);
when(child.getItemCount()).thenReturn(ITEM_COUNTS[i]); when(child.getItemCount()).thenReturn(ITEM_COUNTS[i]);
mChildren.set(i, child); mChildren.add(child);
} }
mInnerNode = new InnerNode(mParent) { mInnerNode = new InnerNode(mParent) {
@Override @Override
...@@ -52,6 +53,14 @@ public class InnerNodeTest { ...@@ -52,6 +53,14 @@ public class InnerNodeTest {
}; };
} }
@Test
public void testInit() {
mInnerNode.init();
for (TreeNode child : mChildren) {
verify(child).init();
}
}
@Test @Test
public void testItemCount() { public void testItemCount() {
assertThat(mInnerNode.getItemCount(), is(12)); assertThat(mInnerNode.getItemCount(), is(12));
...@@ -102,6 +111,43 @@ public class InnerNodeTest { ...@@ -102,6 +111,43 @@ public class InnerNodeTest {
assertThat(mInnerNode.getSuggestionAt(11), is(article4)); assertThat(mInnerNode.getSuggestionAt(11), is(article4));
} }
@Test
public void testDidAddChild() {
TreeNode child = mock(TreeNode.class);
when(child.getItemCount()).thenReturn(23);
mChildren.add(3, child);
mInnerNode.didAddChild(child);
// The child should have been initialized and the parent notified about the added items.
verify(child).init();
verify(mParent).onItemRangeInserted(mInnerNode, 6, 23);
TreeNode child2 = mock(TreeNode.class);
when(child2.getItemCount()).thenReturn(0);
mChildren.add(4, child2);
mInnerNode.didAddChild(child2);
verify(child2).init();
// The empty child should have been initialized, but there should be no change
// notifications.
verifyNoMoreInteractions(mParent);
}
@Test
public void testWillRemoveChild() {
mInnerNode.willRemoveChild(mChildren.get(4));
mChildren.remove(4);
// The parent should have been notified about the removed items.
verify(mParent).onItemRangeRemoved(mInnerNode, 6, 3);
mInnerNode.willRemoveChild(mChildren.get(3));
mChildren.remove(3);
// There should be no change notifications about the empty child.
verifyNoMoreInteractions(mParent);
}
@Test @Test
public void testNotifications() { public void testNotifications() {
mInnerNode.onItemRangeInserted(mChildren.get(0), 0, 23); mInnerNode.onItemRangeInserted(mChildren.get(0), 0, 23);
......
...@@ -308,7 +308,8 @@ public class NewTabPageAdapterTest { ...@@ -308,7 +308,8 @@ public class NewTabPageAdapterTest {
@Test @Test
@Feature({"Ntp"}) @Feature({"Ntp"})
public void testProgressIndicatorDisplay() { public void testProgressIndicatorDisplay() {
SuggestionsSection section = mAdapter.getSectionForTesting(KnownCategories.ARTICLES); SuggestionsSection section =
mAdapter.getSectionListForTesting().getSectionForTesting(KnownCategories.ARTICLES);
ProgressItem progress = section.getProgressItemForTesting(); ProgressItem progress = section.getProgressItemForTesting();
mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.INITIALIZING); mSource.setStatusForCategory(KnownCategories.ARTICLES, CategoryStatus.INITIALIZING);
...@@ -402,7 +403,8 @@ public class NewTabPageAdapterTest { ...@@ -402,7 +403,8 @@ public class NewTabPageAdapterTest {
assertItemsFor(section(3)); assertItemsFor(section(3));
// 1.3 - When all suggestions are dismissed // 1.3 - When all suggestions are dismissed
SuggestionsSection section42 = mAdapter.getSectionForTesting(category); SuggestionsSection section42 =
mAdapter.getSectionListForTesting().getSectionForTesting(category);
assertSectionMatches(section(3), section42); assertSectionMatches(section(3), section42);
section42.removeSuggestion(articles.get(0)); section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1)); section42.removeSuggestion(articles.get(1));
...@@ -456,7 +458,8 @@ public class NewTabPageAdapterTest { ...@@ -456,7 +458,8 @@ public class NewTabPageAdapterTest {
assertItemsFor(section(3).withActionButton()); assertItemsFor(section(3).withActionButton());
// 1.3 - When all suggestions are dismissed. // 1.3 - When all suggestions are dismissed.
SuggestionsSection section42 = mAdapter.getSectionForTesting(category); SuggestionsSection section42 =
mAdapter.getSectionListForTesting().getSectionForTesting(category);
assertSectionMatches(section(3).withActionButton(), section42); assertSectionMatches(section(3).withActionButton(), section42);
section42.removeSuggestion(articles.get(0)); section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1)); section42.removeSuggestion(articles.get(1));
...@@ -480,7 +483,7 @@ public class NewTabPageAdapterTest { ...@@ -480,7 +483,7 @@ public class NewTabPageAdapterTest {
assertItemsFor(section(3)); assertItemsFor(section(3));
// 2.3 - When all suggestions are dismissed. // 2.3 - When all suggestions are dismissed.
section42 = mAdapter.getSectionForTesting(category); section42 = mAdapter.getSectionListForTesting().getSectionForTesting(category);
assertSectionMatches(section(3), section42); assertSectionMatches(section(3), section42);
section42.removeSuggestion(articles.get(0)); section42.removeSuggestion(articles.get(0));
section42.removeSuggestion(articles.get(1)); section42.removeSuggestion(articles.get(1));
...@@ -543,8 +546,6 @@ public class NewTabPageAdapterTest { ...@@ -543,8 +546,6 @@ public class NewTabPageAdapterTest {
@Test @Test
@Feature({"Ntp"}) @Feature({"Ntp"})
public void testCategoryOrder() { public void testCategoryOrder() {
// Above-the-fold, sign in promo, all-dismissed, footer, spacer.
final int basicChildCount = 5;
FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource(); FakeSuggestionsSource suggestionsSource = new FakeSuggestionsSource();
when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource); when(mNewTabPageManager.getSuggestionsSource()).thenReturn(suggestionsSource);
registerCategory(suggestionsSource, KnownCategories.ARTICLES, 0); registerCategory(suggestionsSource, KnownCategories.ARTICLES, 0);
...@@ -553,17 +554,16 @@ public class NewTabPageAdapterTest { ...@@ -553,17 +554,16 @@ public class NewTabPageAdapterTest {
registerCategory(suggestionsSource, KnownCategories.DOWNLOADS, 0); registerCategory(suggestionsSource, KnownCategories.DOWNLOADS, 0);
reloadNtp(); reloadNtp();
List<TreeNode> children = mAdapter.getRootForTesting().getChildren(); List<TreeNode> children = mAdapter.getSectionListForTesting().getChildren();
assertEquals(basicChildCount + 4, children.size()); assertEquals(4, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass()); assertEquals(SuggestionsSection.class, children.get(0).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(0)));
assertEquals(SuggestionsSection.class, children.get(1).getClass()); assertEquals(SuggestionsSection.class, children.get(1).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(1))); assertEquals(KnownCategories.BOOKMARKS, getCategory(children.get(1)));
assertEquals(SuggestionsSection.class, children.get(2).getClass()); assertEquals(SuggestionsSection.class, children.get(2).getClass());
assertEquals(KnownCategories.BOOKMARKS, getCategory(children.get(2))); assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(2)));
assertEquals(SuggestionsSection.class, children.get(3).getClass()); assertEquals(SuggestionsSection.class, children.get(3).getClass());
assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(3))); assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(3)));
assertEquals(SuggestionsSection.class, children.get(4).getClass());
assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(4)));
// With a different order. // With a different order.
suggestionsSource = new FakeSuggestionsSource(); suggestionsSource = new FakeSuggestionsSource();
...@@ -574,17 +574,16 @@ public class NewTabPageAdapterTest { ...@@ -574,17 +574,16 @@ public class NewTabPageAdapterTest {
registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 0); registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 0);
reloadNtp(); reloadNtp();
children = mAdapter.getRootForTesting().getChildren(); children = mAdapter.getSectionListForTesting().getChildren();
assertEquals(basicChildCount + 4, children.size()); assertEquals(4, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass()); assertEquals(SuggestionsSection.class, children.get(0).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(0)));
assertEquals(SuggestionsSection.class, children.get(1).getClass()); assertEquals(SuggestionsSection.class, children.get(1).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(1))); assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(1)));
assertEquals(SuggestionsSection.class, children.get(2).getClass()); assertEquals(SuggestionsSection.class, children.get(2).getClass());
assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(2))); assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(2)));
assertEquals(SuggestionsSection.class, children.get(3).getClass()); assertEquals(SuggestionsSection.class, children.get(3).getClass());
assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(3))); assertEquals(KnownCategories.BOOKMARKS, getCategory(children.get(3)));
assertEquals(SuggestionsSection.class, children.get(4).getClass());
assertEquals(KnownCategories.BOOKMARKS, getCategory(children.get(4)));
// With unknown categories. // With unknown categories.
suggestionsSource = new FakeSuggestionsSource(); suggestionsSource = new FakeSuggestionsSource();
...@@ -598,15 +597,14 @@ public class NewTabPageAdapterTest { ...@@ -598,15 +597,14 @@ public class NewTabPageAdapterTest {
registerCategory(suggestionsSource, 42, 1); registerCategory(suggestionsSource, 42, 1);
registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 1); registerCategory(suggestionsSource, KnownCategories.BOOKMARKS, 1);
children = mAdapter.getRootForTesting().getChildren(); children = mAdapter.getSectionListForTesting().getChildren();
assertEquals(basicChildCount + 3, children.size()); assertEquals(3, children.size());
assertEquals(AboveTheFoldItem.class, children.get(0).getClass()); assertEquals(SuggestionsSection.class, children.get(0).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(0)));
assertEquals(SuggestionsSection.class, children.get(1).getClass()); assertEquals(SuggestionsSection.class, children.get(1).getClass());
assertEquals(KnownCategories.ARTICLES, getCategory(children.get(1))); assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(1)));
assertEquals(SuggestionsSection.class, children.get(2).getClass()); assertEquals(SuggestionsSection.class, children.get(2).getClass());
assertEquals(KnownCategories.PHYSICAL_WEB_PAGES, getCategory(children.get(2))); assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(2)));
assertEquals(SuggestionsSection.class, children.get(3).getClass());
assertEquals(KnownCategories.DOWNLOADS, getCategory(children.get(3)));
} }
@Test @Test
...@@ -665,7 +663,7 @@ public class NewTabPageAdapterTest { ...@@ -665,7 +663,7 @@ public class NewTabPageAdapterTest {
reset(dataObserver); reset(dataObserver);
suggestionsSource.setSuggestionsForCategory( suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(newSuggestionCount)); KnownCategories.ARTICLES, createDummySuggestions(newSuggestionCount));
mAdapter.onNewSuggestions(KnownCategories.ARTICLES); mAdapter.getSectionListForTesting().onNewSuggestions(KnownCategories.ARTICLES);
verify(dataObserver).onItemRangeInserted(2, newSuggestionCount); verify(dataObserver).onItemRangeInserted(2, newSuggestionCount);
verify(dataObserver).onItemRangeChanged(5 + newSuggestionCount, 1, null); // Spacer refresh verify(dataObserver).onItemRangeChanged(5 + newSuggestionCount, 1, null); // Spacer refresh
verify(dataObserver, times(2)).onItemRangeRemoved(2 + newSuggestionCount, 1); verify(dataObserver, times(2)).onItemRangeRemoved(2 + newSuggestionCount, 1);
...@@ -685,7 +683,8 @@ public class NewTabPageAdapterTest { ...@@ -685,7 +683,8 @@ public class NewTabPageAdapterTest {
reset(dataObserver); reset(dataObserver);
suggestionsSource.setSuggestionsForCategory( suggestionsSource.setSuggestionsForCategory(
KnownCategories.ARTICLES, createDummySuggestions(0)); KnownCategories.ARTICLES, createDummySuggestions(0));
mAdapter.onCategoryStatusChanged(KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT); mAdapter.getSectionListForTesting().onCategoryStatusChanged(
KnownCategories.ARTICLES, CategoryStatus.SIGNED_OUT);
verify(dataObserver).onItemRangeRemoved(2, newSuggestionCount); verify(dataObserver).onItemRangeRemoved(2, newSuggestionCount);
verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh verify(dataObserver).onItemRangeChanged(3, 1, null); // Spacer refresh
verify(dataObserver).onItemRangeInserted(2, 1); // Status card added verify(dataObserver).onItemRangeInserted(2, 1); // Status card added
...@@ -850,7 +849,7 @@ public class NewTabPageAdapterTest { ...@@ -850,7 +849,7 @@ public class NewTabPageAdapterTest {
// On Sign in, we should reset the sections, bring back suggestions instead of the All // On Sign in, we should reset the sections, bring back suggestions instead of the All
// Dismissed item. // Dismissed item.
mAdapter.onFullRefreshRequired(); mAdapter.getSectionListForTesting().onFullRefreshRequired();
when(mMockSigninManager.isSignInAllowed()).thenReturn(true); when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
signinObserver.onSignedIn(); signinObserver.onSignedIn();
// Adapter content: // Adapter content:
......
...@@ -347,7 +347,9 @@ public class SuggestionsSectionTest { ...@@ -347,7 +347,9 @@ public class SuggestionsSectionTest {
} }
private SuggestionsSection createSection(SuggestionsCategoryInfo info) { private SuggestionsSection createSection(SuggestionsCategoryInfo info) {
return new SuggestionsSection(mParent, info, mManager, mBridge); SuggestionsSection section = new SuggestionsSection(mParent, mManager, mBridge, info);
section.init();
return section;
} }
private OfflinePageItem createOfflinePageItem(String url, long offlineId) { private OfflinePageItem createOfflinePageItem(String url, long offlineId) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment