Commit 139a79ec authored by Nicolas Dossou-gbete's avatar Nicolas Dossou-gbete Committed by Commit Bot

[Suggestions] Remove CTAs when feature is disabled

Stops showing the Sign in Promo and the All Dismissed item when remote
suggestions are disabled and performing the advertised actions would not
provide the user with new suggestions.

Part of this CL involves making the SuggestionsSources notifies a list
of observer rather than a single one.

Bug: 738872
Change-Id: I16c300fb14a1d5780578387d48d848570339befe
Reviewed-on: https://chromium-review.googlesource.com/567182Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Commit-Queue: Nicolas Dossou-Gbété <dgn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486034}
parent cd235628
...@@ -32,7 +32,8 @@ public class ActionItem extends OptionalLeaf { ...@@ -32,7 +32,8 @@ public class ActionItem extends OptionalLeaf {
mCategoryInfo = section.getCategoryInfo(); mCategoryInfo = section.getCategoryInfo();
mParentSection = section; mParentSection = section;
mSuggestionsRanker = ranker; mSuggestionsRanker = ranker;
setVisible(mCategoryInfo.getAdditionalAction() != ContentSuggestionsAdditionalAction.NONE); setVisibilityInternal(
mCategoryInfo.getAdditionalAction() != ContentSuggestionsAdditionalAction.NONE);
} }
@Override @Override
......
...@@ -40,6 +40,10 @@ public class AllDismissedItem extends OptionalLeaf { ...@@ -40,6 +40,10 @@ public class AllDismissedItem extends OptionalLeaf {
visitor.visitAllDismissedItem(); visitor.visitAllDismissedItem();
} }
public void setVisible(boolean visible) {
setVisibilityInternal(visible);
}
/** /**
* ViewHolder for an item of type {@link ItemViewType#ALL_DISMISSED}. * ViewHolder for an item of type {@link ItemViewType#ALL_DISMISSED}.
*/ */
......
...@@ -37,6 +37,10 @@ public class Footer extends OptionalLeaf { ...@@ -37,6 +37,10 @@ public class Footer extends OptionalLeaf {
visitor.visitFooter(); visitor.visitFooter();
} }
public void setVisible(boolean visible) {
setVisibilityInternal(visible);
}
/** /**
* The {@code ViewHolder} for the {@link Footer}. * The {@code ViewHolder} for the {@link Footer}.
*/ */
......
...@@ -15,9 +15,14 @@ import org.chromium.base.VisibleForTesting; ...@@ -15,9 +15,14 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ntp.ContextMenuManager; import org.chromium.chrome.browser.ntp.ContextMenuManager;
import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.PartialBindCallback; import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.PartialBindCallback;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder; import org.chromium.chrome.browser.ntp.snippets.SectionHeaderViewHolder;
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.SuggestionsSource;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.suggestions.DestructionObserver;
import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView; import org.chromium.chrome.browser.suggestions.SuggestionsRecyclerView;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate; import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.chrome.browser.suggestions.TileGrid; import org.chromium.chrome.browser.suggestions.TileGrid;
...@@ -103,6 +108,9 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements ...@@ -103,6 +108,9 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements
mRoot.addChild(mBottomSpacer); mRoot.addChild(mBottomSpacer);
} }
RemoteSuggestionsStatusObserver suggestionsObserver = new RemoteSuggestionsStatusObserver();
mUiDelegate.addDestructionObserver(suggestionsObserver);
updateAllDismissedVisibility(); updateAllDismissedVisibility();
mRoot.setParent(this); mRoot.setParent(this);
} }
...@@ -222,7 +230,8 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements ...@@ -222,7 +230,8 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements
} }
private void updateAllDismissedVisibility() { private void updateAllDismissedVisibility() {
boolean showAllDismissed = hasAllBeenDismissed(); boolean showAllDismissed = hasAllBeenDismissed()
&& mUiDelegate.getSuggestionsSource().areRemoteSuggestionsEnabled();
mAllDismissed.setVisible(showAllDismissed); mAllDismissed.setVisible(showAllDismissed);
mFooter.setVisible(!showAllDismissed); mFooter.setVisible(!showAllDismissed);
} }
...@@ -309,4 +318,24 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements ...@@ -309,4 +318,24 @@ public class NewTabPageAdapter extends Adapter<NewTabPageViewHolder> implements
InnerNode getRootForTesting() { InnerNode getRootForTesting() {
return mRoot; return mRoot;
} }
private class RemoteSuggestionsStatusObserver
extends SuggestionsSource.EmptyObserver implements DestructionObserver {
public RemoteSuggestionsStatusObserver() {
mUiDelegate.getSuggestionsSource().addObserver(this);
}
@Override
public void onCategoryStatusChanged(
@CategoryInt int category, @CategoryStatus int newStatus) {
if (!SnippetsBridge.isCategoryRemote(category)) return;
updateAllDismissedVisibility();
}
@Override
public void onDestroy() {
mUiDelegate.getSuggestionsSource().removeObserver(this);
}
}
} }
...@@ -67,7 +67,7 @@ public abstract class OptionalLeaf extends ChildNode { ...@@ -67,7 +67,7 @@ public abstract class OptionalLeaf extends ChildNode {
* initially considered hidden. * initially considered hidden.
*/ */
@CallSuper @CallSuper
public void setVisible(boolean visible) { protected void setVisibilityInternal(boolean visible) {
if (mVisible == visible) return; if (mVisible == visible) return;
mVisible = visible; mVisible = visible;
......
...@@ -28,4 +28,8 @@ class ProgressItem extends OptionalLeaf { ...@@ -28,4 +28,8 @@ class ProgressItem extends OptionalLeaf {
protected void visitOptionalItem(NodeVisitor visitor) { protected void visitOptionalItem(NodeVisitor visitor) {
visitor.visitProgressItem(); visitor.visitProgressItem();
} }
public void setVisible(boolean visible) {
setVisibilityInternal(visible);
}
} }
...@@ -43,7 +43,7 @@ public class SectionList ...@@ -43,7 +43,7 @@ public class SectionList
public SectionList(SuggestionsUiDelegate uiDelegate, OfflinePageBridge offlinePageBridge) { public SectionList(SuggestionsUiDelegate uiDelegate, OfflinePageBridge offlinePageBridge) {
mUiDelegate = uiDelegate; mUiDelegate = uiDelegate;
mUiDelegate.getSuggestionsSource().setObserver(this); mUiDelegate.getSuggestionsSource().addObserver(this);
mOfflinePageBridge = offlinePageBridge; mOfflinePageBridge = offlinePageBridge;
mUiDelegate.addDestructionObserver(new DestructionObserver() { mUiDelegate.addDestructionObserver(new DestructionObserver() {
......
...@@ -15,6 +15,10 @@ import org.chromium.base.VisibleForTesting; ...@@ -15,6 +15,10 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ntp.ContextMenuManager; import org.chromium.chrome.browser.ntp.ContextMenuManager;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager; import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.signin.AccountSigninActivity; import org.chromium.chrome.browser.signin.AccountSigninActivity;
import org.chromium.chrome.browser.signin.SigninAccessPoint; import org.chromium.chrome.browser.signin.SigninAccessPoint;
...@@ -39,23 +43,37 @@ public class SignInPromo extends OptionalLeaf ...@@ -39,23 +43,37 @@ public class SignInPromo extends OptionalLeaf
*/ */
private boolean mDismissed; private boolean mDismissed;
/**
* Whether the signin status means that the user has the possibility to sign in.
*/
private boolean mCanSignIn;
/**
* Whether personalized suggestions can be shown. If it's not the case, we have no reason to
* offer the user to sign in.
*/
private boolean mCanShowPersonalizedSuggestions;
private final ImpressionTracker mImpressionTracker = new ImpressionTracker(null, this); private final ImpressionTracker mImpressionTracker = new ImpressionTracker(null, this);
@Nullable @Nullable
private final SigninObserver mObserver; private final SigninObserver mSigninObserver;
public SignInPromo(SuggestionsUiDelegate uiDelegate) { public SignInPromo(SuggestionsUiDelegate uiDelegate) {
mDismissed = ChromePreferenceManager.getInstance().getNewTabPageSigninPromoDismissed(); mDismissed = ChromePreferenceManager.getInstance().getNewTabPageSigninPromoDismissed();
SuggestionsSource suggestionsSource = uiDelegate.getSuggestionsSource();
SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext()); SigninManager signinManager = SigninManager.get(ContextUtils.getApplicationContext());
if (mDismissed) { if (mDismissed) {
mObserver = null; mSigninObserver = null;
} else { } else {
mObserver = new SigninObserver(signinManager); mSigninObserver = new SigninObserver(signinManager, suggestionsSource);
uiDelegate.addDestructionObserver(mObserver); uiDelegate.addDestructionObserver(mSigninObserver);
} }
setVisible(signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative()); mCanSignIn = signinManager.isSignInAllowed() && !signinManager.isSignedInOnNative();
mCanShowPersonalizedSuggestions = suggestionsSource.areRemoteSuggestionsEnabled();
updateVisibility();
} }
@Override @Override
...@@ -70,7 +88,7 @@ public class SignInPromo extends OptionalLeaf ...@@ -70,7 +88,7 @@ public class SignInPromo extends OptionalLeaf
*/ */
@Nullable @Nullable
public DestructionObserver getObserver() { public DestructionObserver getObserver() {
return mObserver; return mSigninObserver;
} }
@Override @Override
...@@ -114,9 +132,8 @@ public class SignInPromo extends OptionalLeaf ...@@ -114,9 +132,8 @@ public class SignInPromo extends OptionalLeaf
mImpressionTracker.reset(null); mImpressionTracker.reset(null);
} }
@Override private void updateVisibility() {
public void setVisible(boolean visible) { setVisibilityInternal(!mDismissed && mCanSignIn && mCanShowPersonalizedSuggestions);
super.setVisible(!mDismissed && visible);
} }
@Override @Override
...@@ -127,26 +144,31 @@ public class SignInPromo extends OptionalLeaf ...@@ -127,26 +144,31 @@ public class SignInPromo extends OptionalLeaf
/** Hides the sign in promo and sets a preference to make sure it is not shown again. */ /** Hides the sign in promo and sets a preference to make sure it is not shown again. */
@Override @Override
public void dismiss(Callback<String> itemRemovedCallback) { public void dismiss(Callback<String> itemRemovedCallback) {
assert mSigninObserver != null;
mDismissed = true; mDismissed = true;
setVisible(false); updateVisibility();
ChromePreferenceManager.getInstance().setNewTabPageSigninPromoDismissed(true); ChromePreferenceManager.getInstance().setNewTabPageSigninPromoDismissed(true);
mObserver.unregister(); mSigninObserver.unregister();
itemRemovedCallback.onResult(ContextUtils.getApplicationContext().getString(getHeader())); itemRemovedCallback.onResult(ContextUtils.getApplicationContext().getString(getHeader()));
} }
@VisibleForTesting @VisibleForTesting
class SigninObserver class SigninObserver extends SuggestionsSource.EmptyObserver
implements SignInStateObserver, SignInAllowedObserver, DestructionObserver { implements SignInStateObserver, SignInAllowedObserver, DestructionObserver {
private final SigninManager mSigninManager; private final SigninManager mSigninManager;
private final SuggestionsSource mSuggestionsSource;
/** Guards {@link #unregister()}, which can be called multiple times. */ /** Guards {@link #unregister()}, which can be called multiple times. */
private boolean mUnregistered; private boolean mUnregistered;
private SigninObserver(SigninManager signinManager) { private SigninObserver(SigninManager signinManager, SuggestionsSource suggestionsSource) {
mSigninManager = signinManager; mSigninManager = signinManager;
mSigninManager.addSignInAllowedObserver(this); mSigninManager.addSignInAllowedObserver(this);
mSigninManager.addSignInStateObserver(this); mSigninManager.addSignInStateObserver(this);
mSuggestionsSource = suggestionsSource;
mSuggestionsSource.addObserver(this);
} }
private void unregister() { private void unregister() {
...@@ -155,6 +177,8 @@ public class SignInPromo extends OptionalLeaf ...@@ -155,6 +177,8 @@ public class SignInPromo extends OptionalLeaf
mSigninManager.removeSignInAllowedObserver(this); mSigninManager.removeSignInAllowedObserver(this);
mSigninManager.removeSignInStateObserver(this); mSigninManager.removeSignInStateObserver(this);
mSuggestionsSource.removeObserver(this);
} }
@Override @Override
...@@ -167,17 +191,31 @@ public class SignInPromo extends OptionalLeaf ...@@ -167,17 +191,31 @@ public class SignInPromo extends OptionalLeaf
// Listening to onSignInAllowedChanged is important for the FRE. Sign in is not allowed // Listening to onSignInAllowedChanged is important for the FRE. Sign in is not allowed
// until it is completed, but the NTP is initialised before the FRE is even shown. By // until it is completed, but the NTP is initialised before the FRE is even shown. By
// implementing this we can show the promo if the user did not sign in during the FRE. // implementing this we can show the promo if the user did not sign in during the FRE.
setVisible(mSigninManager.isSignInAllowed()); mCanSignIn = mSigninManager.isSignInAllowed();
updateVisibility();
} }
@Override @Override
public void onSignedIn() { public void onSignedIn() {
setVisible(false); mCanSignIn = false;
updateVisibility();
} }
@Override @Override
public void onSignedOut() { public void onSignedOut() {
setVisible(true); mCanSignIn = mSigninManager.isSignInAllowed();
updateVisibility();
}
@Override
public void onCategoryStatusChanged(
@CategoryInt int category, @CategoryStatus int newStatus) {
if (!SnippetsBridge.isCategoryRemote(category)) return;
// Checks whether the category is enabled first to avoid unnecessary calls across JNI.
mCanShowPersonalizedSuggestions = SnippetsBridge.isCategoryEnabled(category)
|| mSuggestionsSource.areRemoteSuggestionsEnabled();
updateVisibility();
} }
} }
......
...@@ -63,4 +63,8 @@ public abstract class StatusItem extends OptionalLeaf implements StatusCardViewH ...@@ -63,4 +63,8 @@ public abstract class StatusItem extends OptionalLeaf implements StatusCardViewH
assert holder instanceof StatusCardViewHolder; assert holder instanceof StatusCardViewHolder;
((StatusCardViewHolder) holder).onBindViewHolder(this); ((StatusCardViewHolder) holder).onBindViewHolder(this);
} }
public void setVisible(boolean visible) {
setVisibilityInternal(visible);
}
} }
...@@ -18,7 +18,7 @@ public class SectionHeader extends OptionalLeaf { ...@@ -18,7 +18,7 @@ public class SectionHeader extends OptionalLeaf {
public SectionHeader(String headerText) { public SectionHeader(String headerText) {
this.mHeaderText = headerText; this.mHeaderText = headerText;
setVisible(true); setVisibilityInternal(true);
} }
@Override @Override
...@@ -41,4 +41,8 @@ public class SectionHeader extends OptionalLeaf { ...@@ -41,4 +41,8 @@ public class SectionHeader extends OptionalLeaf {
public void visitOptionalItem(NodeVisitor visitor) { public void visitOptionalItem(NodeVisitor visitor) {
visitor.visitHeader(); visitor.visitHeader();
} }
public void setVisible(boolean visible) {
setVisibilityInternal(visible);
}
} }
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.ntp.snippets; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.ntp.snippets;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo; import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
...@@ -22,13 +23,17 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -22,13 +23,17 @@ public class SnippetsBridge implements SuggestionsSource {
private static final String TAG = "SnippetsBridge"; private static final String TAG = "SnippetsBridge";
private long mNativeSnippetsBridge; private long mNativeSnippetsBridge;
private SuggestionsSource.Observer mObserver; private final ObserverList<Observer> mObserverList = new ObserverList<>();
public static boolean isCategoryStatusAvailable(@CategoryStatus int status) { public static boolean isCategoryStatusAvailable(@CategoryStatus int status) {
// Note: This code is duplicated in content_suggestions_category_status.cc. // Note: This code is duplicated in category_status.cc.
return status == CategoryStatus.AVAILABLE_LOADING || status == CategoryStatus.AVAILABLE; return status == CategoryStatus.AVAILABLE_LOADING || status == CategoryStatus.AVAILABLE;
} }
public static boolean isCategoryRemote(@CategoryInt int category) {
return category > KnownCategories.REMOTE_CATEGORIES_OFFSET;
}
/** Returns whether the category is considered "enabled", and can show content suggestions. */ /** Returns whether the category is considered "enabled", and can show content suggestions. */
public static boolean isCategoryEnabled(@CategoryStatus int status) { public static boolean isCategoryEnabled(@CategoryStatus int status) {
switch (status) { switch (status) {
...@@ -62,7 +67,7 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -62,7 +67,7 @@ public class SnippetsBridge implements SuggestionsSource {
assert mNativeSnippetsBridge != 0; assert mNativeSnippetsBridge != 0;
nativeDestroy(mNativeSnippetsBridge); nativeDestroy(mNativeSnippetsBridge);
mNativeSnippetsBridge = 0; mNativeSnippetsBridge = 0;
mObserver = null; mObserverList.clear();
} }
/** /**
...@@ -83,8 +88,9 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -83,8 +88,9 @@ public class SnippetsBridge implements SuggestionsSource {
nativeSetRemoteSuggestionsEnabled(enabled); nativeSetRemoteSuggestionsEnabled(enabled);
} }
public static boolean areRemoteSuggestionsEnabled() { @Override
return nativeAreRemoteSuggestionsEnabled(); public boolean areRemoteSuggestionsEnabled() {
return nativeAreRemoteSuggestionsEnabled(mNativeSnippetsBridge);
} }
public static boolean areRemoteSuggestionsManaged() { public static boolean areRemoteSuggestionsManaged() {
...@@ -174,9 +180,14 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -174,9 +180,14 @@ public class SnippetsBridge implements SuggestionsSource {
} }
@Override @Override
public void setObserver(Observer observer) { public void addObserver(Observer observer) {
assert observer != null; assert observer != null;
mObserver = observer; mObserverList.addObserver(observer);
}
@Override
public void removeObserver(Observer observer) {
mObserverList.removeObserver(observer);
} }
@Override @Override
...@@ -229,22 +240,26 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -229,22 +240,26 @@ public class SnippetsBridge implements SuggestionsSource {
@CalledByNative @CalledByNative
private void onNewSuggestions(@CategoryInt int category) { private void onNewSuggestions(@CategoryInt int category) {
if (mObserver != null) mObserver.onNewSuggestions(category); for (Observer observer : mObserverList) observer.onNewSuggestions(category);
} }
@CalledByNative @CalledByNative
private void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatus int newStatus) { private void onCategoryStatusChanged(@CategoryInt int category, @CategoryStatus int newStatus) {
if (mObserver != null) mObserver.onCategoryStatusChanged(category, newStatus); for (Observer observer : mObserverList) {
observer.onCategoryStatusChanged(category, newStatus);
}
} }
@CalledByNative @CalledByNative
private void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) { private void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {
if (mObserver != null) mObserver.onSuggestionInvalidated(category, idWithinCategory); for (Observer observer : mObserverList) {
observer.onSuggestionInvalidated(category, idWithinCategory);
}
} }
@CalledByNative @CalledByNative
private void onFullRefreshRequired() { private void onFullRefreshRequired() {
if (mObserver != null) mObserver.onFullRefreshRequired(); for (Observer observer : mObserverList) observer.onFullRefreshRequired();
} }
private native long nativeInit(Profile profile); private native long nativeInit(Profile profile);
...@@ -253,7 +268,7 @@ public class SnippetsBridge implements SuggestionsSource { ...@@ -253,7 +268,7 @@ public class SnippetsBridge implements SuggestionsSource {
private static native void nativeRemoteSuggestionsSchedulerOnFetchDue(); private static native void nativeRemoteSuggestionsSchedulerOnFetchDue();
private static native void nativeRemoteSuggestionsSchedulerRescheduleFetching(); private static native void nativeRemoteSuggestionsSchedulerRescheduleFetching();
private static native void nativeSetRemoteSuggestionsEnabled(boolean enabled); private static native void nativeSetRemoteSuggestionsEnabled(boolean enabled);
private static native boolean nativeAreRemoteSuggestionsEnabled(); private native boolean nativeAreRemoteSuggestionsEnabled(long nativeNTPSnippetsBridge);
private static native boolean nativeAreRemoteSuggestionsManaged(); private static native boolean nativeAreRemoteSuggestionsManaged();
private static native boolean nativeAreRemoteSuggestionsManagedByCustodian(); private static native boolean nativeAreRemoteSuggestionsManagedByCustodian();
private static native void nativeSetContentSuggestionsNotificationsEnabled(boolean enabled); private static native void nativeSetContentSuggestionsNotificationsEnabled(boolean enabled);
......
...@@ -42,6 +42,11 @@ public interface SuggestionsSource extends DestructionObserver { ...@@ -42,6 +42,11 @@ public interface SuggestionsSource extends DestructionObserver {
*/ */
void fetchRemoteSuggestions(); void fetchRemoteSuggestions();
/**
* @return Whether remote suggestions are enabled.
*/
boolean areRemoteSuggestionsEnabled();
/** /**
* Gets the categories in the order in which they should be displayed. * Gets the categories in the order in which they should be displayed.
* @return The categories. * @return The categories.
...@@ -120,5 +125,26 @@ public interface SuggestionsSource extends DestructionObserver { ...@@ -120,5 +125,26 @@ public interface SuggestionsSource extends DestructionObserver {
/** /**
* Sets the recipient for update events from the source. * Sets the recipient for update events from the source.
*/ */
void setObserver(Observer observer); void addObserver(Observer observer);
/**
* Removes an observer. Is no-op if the observer was not already registered.
*/
void removeObserver(Observer observer);
/** No-op implementation of {@link SuggestionsSource.Observer}. */
class EmptyObserver implements Observer {
@Override
public void onNewSuggestions(@CategoryInt int category) {}
@Override
public void onCategoryStatusChanged(
@CategoryInt int category, @CategoryStatus int newStatus) {}
@Override
public void onSuggestionInvalidated(@CategoryInt int category, String idWithinCategory) {}
@Override
public void onFullRefreshRequired() {}
}
} }
...@@ -23,7 +23,9 @@ import org.chromium.chrome.browser.ntp.ContentSuggestionsNotificationHelper; ...@@ -23,7 +23,9 @@ import org.chromium.chrome.browser.ntp.ContentSuggestionsNotificationHelper;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction; import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction;
import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationOptOut; import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationOptOut;
import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge; import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.SuggestionsDependencyFactory;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -74,7 +76,12 @@ public class ContentSuggestionsPreferences extends PreferenceFragment { ...@@ -74,7 +76,12 @@ public class ContentSuggestionsPreferences extends PreferenceFragment {
setHasOptionsMenu(true); setHasOptionsMenu(true);
finishSwitchInitialisation(); finishSwitchInitialisation();
boolean isEnabled = SnippetsBridge.areRemoteSuggestionsEnabled(); SuggestionsSource suggestionsSource =
SuggestionsDependencyFactory.getInstance().createSuggestionSource(
Profile.getLastUsedProfile());
boolean isEnabled = suggestionsSource.areRemoteSuggestionsEnabled();
suggestionsSource.onDestroy();
mIsEnabled = !isEnabled; // Opposite so that we trigger side effects below. mIsEnabled = !isEnabled; // Opposite so that we trigger side effects below.
updatePreferences(isEnabled); updatePreferences(isEnabled);
......
...@@ -66,7 +66,7 @@ public class TileGrid extends OptionalLeaf implements TileGroup.Observer { ...@@ -66,7 +66,7 @@ public class TileGrid extends OptionalLeaf implements TileGroup.Observer {
@Override @Override
public void onTileDataChanged() { public void onTileDataChanged() {
setVisible(mTileGroup.getTiles().length != 0); setVisibilityInternal(mTileGroup.getTiles().length != 0);
if (isVisible()) notifyItemChanged(0, new ViewHolder.UpdateTilesCallback(mTileGroup)); if (isVisible()) notifyItemChanged(0, new ViewHolder.UpdateTilesCallback(mTileGroup));
} }
......
...@@ -11,7 +11,6 @@ import static org.junit.Assert.assertTrue; ...@@ -11,7 +11,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
...@@ -63,6 +62,7 @@ import org.chromium.chrome.browser.ntp.snippets.CategoryInt; ...@@ -63,6 +62,7 @@ import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
import org.chromium.chrome.browser.ntp.snippets.CategoryStatus; import org.chromium.chrome.browser.ntp.snippets.CategoryStatus;
import org.chromium.chrome.browser.ntp.snippets.KnownCategories; import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle; import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge; import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager; import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.signin.SigninManager; import org.chromium.chrome.browser.signin.SigninManager;
...@@ -80,6 +80,7 @@ import org.chromium.chrome.test.util.browser.suggestions.FakeSuggestionsSource; ...@@ -80,6 +80,7 @@ import org.chromium.chrome.test.util.browser.suggestions.FakeSuggestionsSource;
import org.chromium.testing.local.LocalRobolectricTestRunner; import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -254,10 +255,7 @@ public class NewTabPageAdapterTest { ...@@ -254,10 +255,7 @@ public class NewTabPageAdapterTest {
mSource.setInfoForCategory( mSource.setInfoForCategory(
TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpty().build()); TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).showIfEmpty().build());
when(mUiDelegate.getSuggestionsSource()).thenReturn(mSource); resetUiDelegate();
when(mUiDelegate.getEventReporter()).thenReturn(mock(SuggestionsEventReporter.class));
when(mUiDelegate.getSuggestionsRanker()).thenReturn(mock(SuggestionsRanker.class));
reloadNtp(); reloadNtp();
} }
...@@ -892,28 +890,26 @@ public class NewTabPageAdapterTest { ...@@ -892,28 +890,26 @@ public class NewTabPageAdapterTest {
@Test @Test
@Feature({"Ntp"}) @Feature({"Ntp"})
public void testSigninPromo() { public void testSigninPromo() {
@CategoryInt
final int remoteCategory = KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY;
when(mMockSigninManager.isSignInAllowed()).thenReturn(true); when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
when(mMockSigninManager.isSignedInOnNative()).thenReturn(false); when(mMockSigninManager.isSignedInOnNative()).thenReturn(false);
ArgumentCaptor<DestructionObserver> observers = mSource.setRemoteSuggestionsEnabled(true);
ArgumentCaptor.forClass(DestructionObserver.class); resetUiDelegate();
doNothing().when(mUiDelegate).addDestructionObserver(observers.capture());
reloadNtp(); reloadNtp();
assertTrue(isSignInPromoVisible()); assertTrue(isSignInPromoVisible());
// Note: As currently implemented, these two variables should point to the same object, a // Note: As currently implemented, these variables should point to the same object, a
// SignInPromo.SigninObserver // SignInPromo.SigninObserver
SignInStateObserver signInStateObserver = null; List<DestructionObserver> observers = getDestructionObserver(mUiDelegate);
SignInAllowedObserver signInAllowedObserver = null; SignInStateObserver signInStateObserver =
for (DestructionObserver observer : observers.getAllValues()) { findFirstInstanceOf(observers, SignInStateObserver.class);
if (observer instanceof SignInStateObserver) { SignInAllowedObserver signInAllowedObserver =
signInStateObserver = (SignInStateObserver) observer; findFirstInstanceOf(observers, SignInAllowedObserver.class);
} SuggestionsSource.Observer suggestionsObserver =
if (observer instanceof SignInAllowedObserver) { findFirstInstanceOf(observers, SuggestionsSource.Observer.class);
signInAllowedObserver = (SignInAllowedObserver) observer;
}
}
signInStateObserver.onSignedIn(); signInStateObserver.onSignedIn();
assertFalse(isSignInPromoVisible()); assertFalse(isSignInPromoVisible());
...@@ -928,6 +924,15 @@ public class NewTabPageAdapterTest { ...@@ -928,6 +924,15 @@ public class NewTabPageAdapterTest {
when(mMockSigninManager.isSignInAllowed()).thenReturn(true); when(mMockSigninManager.isSignInAllowed()).thenReturn(true);
signInAllowedObserver.onSignInAllowedChanged(); signInAllowedObserver.onSignInAllowedChanged();
assertTrue(isSignInPromoVisible()); assertTrue(isSignInPromoVisible());
mSource.setRemoteSuggestionsEnabled(false);
suggestionsObserver.onCategoryStatusChanged(
remoteCategory, CategoryStatus.CATEGORY_EXPLICITLY_DISABLED);
assertFalse(isSignInPromoVisible());
mSource.setRemoteSuggestionsEnabled(true);
suggestionsObserver.onCategoryStatusChanged(remoteCategory, CategoryStatus.AVAILABLE);
assertTrue(isSignInPromoVisible());
} }
@Test @Test
...@@ -960,17 +965,8 @@ public class NewTabPageAdapterTest { ...@@ -960,17 +965,8 @@ public class NewTabPageAdapterTest {
@Test @Test
@Feature({"Ntp"}) @Feature({"Ntp"})
public void testAllDismissedVisibility() { public void testAllDismissedVisibility() {
ArgumentCaptor<DestructionObserver> observers = SigninObserver signinObserver =
ArgumentCaptor.forClass(DestructionObserver.class); findFirstInstanceOf(getDestructionObserver(mUiDelegate), SigninObserver.class);
verify(mUiDelegate, atLeastOnce()).addDestructionObserver(observers.capture());
SigninObserver signinObserver = null;
for (DestructionObserver observer : observers.getAllValues()) {
if (observer instanceof SigninObserver) {
signinObserver = (SigninObserver) observer;
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Callback<String> itemDismissedCallback = mock(Callback.class); Callback<String> itemDismissedCallback = mock(Callback.class);
...@@ -1045,8 +1041,28 @@ public class NewTabPageAdapterTest { ...@@ -1045,8 +1041,28 @@ public class NewTabPageAdapterTest {
assertEquals(RecyclerView.NO_POSITION, assertEquals(RecyclerView.NO_POSITION,
mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED)); mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED));
// Disabling remote suggestions should remove both the promo and the AllDismissed item
mSource.setRemoteSuggestionsEnabled(false);
signinObserver.onCategoryStatusChanged(
KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY,
CategoryStatus.CATEGORY_EXPLICITLY_DISABLED);
// Adapter content:
// Idx | Item
// ----|--------------------
// 0 | Above-the-fold
// 1 | Footer
// 2 | Spacer
assertEquals(ItemViewType.FOOTER, mAdapter.getItemViewType(1));
assertEquals(RecyclerView.NO_POSITION,
mAdapter.getFirstPositionForType(ItemViewType.ALL_DISMISSED));
assertEquals(
RecyclerView.NO_POSITION, mAdapter.getFirstPositionForType(ItemViewType.PROMO));
// Prepare some suggestions. They should not load because the category is dismissed on // Prepare some suggestions. They should not load because the category is dismissed on
// the current NTP. // the current NTP.
mSource.setRemoteSuggestionsEnabled(true);
signinObserver.onCategoryStatusChanged(
KnownCategories.REMOTE_CATEGORIES_OFFSET + TEST_CATEGORY, CategoryStatus.AVAILABLE);
mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE); mSource.setStatusForCategory(TEST_CATEGORY, CategoryStatus.AVAILABLE);
mSource.setSuggestionsForCategory(TEST_CATEGORY, createDummySuggestions(1, TEST_CATEGORY)); mSource.setSuggestionsForCategory(TEST_CATEGORY, createDummySuggestions(1, TEST_CATEGORY));
mSource.setInfoForCategory(TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).build()); mSource.setInfoForCategory(TEST_CATEGORY, new CategoryInfoBuilder(TEST_CATEGORY).build());
...@@ -1137,6 +1153,13 @@ public class NewTabPageAdapterTest { ...@@ -1137,6 +1153,13 @@ public class NewTabPageAdapterTest {
return new SectionDescriptor(Collections.<SnippetArticle>emptyList()); return new SectionDescriptor(Collections.<SnippetArticle>emptyList());
} }
private void resetUiDelegate() {
reset(mUiDelegate);
when(mUiDelegate.getSuggestionsSource()).thenReturn(mSource);
when(mUiDelegate.getEventReporter()).thenReturn(mock(SuggestionsEventReporter.class));
when(mUiDelegate.getSuggestionsRanker()).thenReturn(mock(SuggestionsRanker.class));
}
private void reloadNtp() { private void reloadNtp() {
mAdapter = new NewTabPageAdapter(mUiDelegate, mock(View.class), makeUiConfig(), mAdapter = new NewTabPageAdapter(mUiDelegate, mock(View.class), makeUiConfig(),
mOfflinePageBridge, mock(ContextMenuManager.class), /* tileGroupDelegate = mOfflinePageBridge, mock(ContextMenuManager.class), /* tileGroupDelegate =
...@@ -1151,4 +1174,25 @@ public class NewTabPageAdapterTest { ...@@ -1151,4 +1174,25 @@ public class NewTabPageAdapterTest {
private int getCategory(TreeNode item) { private int getCategory(TreeNode item) {
return ((SuggestionsSection) item).getCategory(); return ((SuggestionsSection) item).getCategory();
} }
/**
* Note: Currently the observers need to be re-registered to be returned again if this method
* has been called, as it relies on argument captors that don't repeatedly capture individual
* calls.
* @return The currently registered destruction observers.
*/
private List<DestructionObserver> getDestructionObserver(SuggestionsUiDelegate delegate) {
ArgumentCaptor<DestructionObserver> observers =
ArgumentCaptor.forClass(DestructionObserver.class);
verify(delegate, atLeastOnce()).addDestructionObserver(observers.capture());
return observers.getAllValues();
}
@SuppressWarnings("unchecked")
private static <T> T findFirstInstanceOf(Collection<?> collection, Class<T> clazz) {
for (Object item : collection) {
if (clazz.isAssignableFrom(item.getClass())) return (T) item;
}
return null;
}
} }
...@@ -163,18 +163,6 @@ static void SetRemoteSuggestionsEnabled(JNIEnv* env, ...@@ -163,18 +163,6 @@ static void SetRemoteSuggestionsEnabled(JNIEnv* env,
content_suggestions_service->SetRemoteSuggestionsEnabled(enabled); content_suggestions_service->SetRemoteSuggestionsEnabled(enabled);
} }
static jboolean AreRemoteSuggestionsEnabled(
JNIEnv* env,
const JavaParamRef<jclass>& caller) {
ntp_snippets::ContentSuggestionsService* content_suggestions_service =
ContentSuggestionsServiceFactory::GetForProfile(
ProfileManager::GetLastUsedProfile());
if (!content_suggestions_service)
return false;
return content_suggestions_service->AreRemoteSuggestionsEnabled();
}
// Returns true if the remote provider is managed by an adminstrator's policy. // Returns true if the remote provider is managed by an adminstrator's policy.
static jboolean AreRemoteSuggestionsManaged( static jboolean AreRemoteSuggestionsManaged(
JNIEnv* env, JNIEnv* env,
...@@ -288,6 +276,12 @@ ScopedJavaLocalRef<jobject> NTPSnippetsBridge::GetSuggestionsForCategory( ...@@ -288,6 +276,12 @@ ScopedJavaLocalRef<jobject> NTPSnippetsBridge::GetSuggestionsForCategory(
content_suggestions_service_->GetSuggestionsForCategory(category)); content_suggestions_service_->GetSuggestionsForCategory(category));
} }
jboolean NTPSnippetsBridge::AreRemoteSuggestionsEnabled(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
return content_suggestions_service_->AreRemoteSuggestionsEnabled();
}
void NTPSnippetsBridge::FetchSuggestionImage( void NTPSnippetsBridge::FetchSuggestionImage(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& obj,
......
...@@ -53,6 +53,10 @@ class NTPSnippetsBridge ...@@ -53,6 +53,10 @@ class NTPSnippetsBridge
const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& obj,
jint j_category_id); jint j_category_id);
jboolean AreRemoteSuggestionsEnabled(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
void FetchSuggestionImage( void FetchSuggestionImage(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& obj,
......
...@@ -9,6 +9,7 @@ import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio ...@@ -9,6 +9,7 @@ import static org.chromium.chrome.test.util.browser.suggestions.ContentSuggestio
import android.graphics.Bitmap; import android.graphics.Bitmap;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo; import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
import org.chromium.chrome.browser.ntp.snippets.CategoryInt; import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
...@@ -28,7 +29,7 @@ import java.util.Map; ...@@ -28,7 +29,7 @@ import java.util.Map;
* A fake Suggestions source for use in unit and instrumentation tests. * A fake Suggestions source for use in unit and instrumentation tests.
*/ */
public class FakeSuggestionsSource implements SuggestionsSource { public class FakeSuggestionsSource implements SuggestionsSource {
private SuggestionsSource.Observer mObserver; private ObserverList<Observer> mObserverList = new ObserverList<>();
private final List<Integer> mCategories = new ArrayList<>(); private final List<Integer> mCategories = new ArrayList<>();
private final Map<Integer, List<SnippetArticle>> mSuggestions = new HashMap<>(); private final Map<Integer, List<SnippetArticle>> mSuggestions = new HashMap<>();
private final Map<Integer, Integer> mCategoryStatus = new LinkedHashMap<>(); private final Map<Integer, Integer> mCategoryStatus = new LinkedHashMap<>();
...@@ -45,6 +46,9 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -45,6 +46,9 @@ public class FakeSuggestionsSource implements SuggestionsSource {
new HashMap<>(); new HashMap<>();
private final Map<Integer, Integer> mDismissedCategoryStatus = new LinkedHashMap<>(); private final Map<Integer, Integer> mDismissedCategoryStatus = new LinkedHashMap<>();
private final Map<Integer, SuggestionsCategoryInfo> mDismissedCategoryInfo = new HashMap<>(); private final Map<Integer, SuggestionsCategoryInfo> mDismissedCategoryInfo = new HashMap<>();
private boolean mRemoteSuggestionsEnabled = true;
/** /**
* Sets the status to be returned for a given category. * Sets the status to be returned for a given category.
*/ */
...@@ -55,7 +59,7 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -55,7 +59,7 @@ public class FakeSuggestionsSource implements SuggestionsSource {
} else if (!mCategories.contains(category)) { } else if (!mCategories.contains(category)) {
mCategories.add(category); mCategories.add(category);
} }
if (mObserver != null) mObserver.onCategoryStatusChanged(category, status); for (Observer observer : mObserverList) observer.onCategoryStatusChanged(category, status);
} }
/** /**
...@@ -65,7 +69,7 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -65,7 +69,7 @@ public class FakeSuggestionsSource implements SuggestionsSource {
@CategoryInt int category, List<SnippetArticle> suggestions) { @CategoryInt int category, List<SnippetArticle> suggestions) {
// Copy the suggestions list so that it can't be modified anymore. // Copy the suggestions list so that it can't be modified anymore.
mSuggestions.put(category, new ArrayList<>(suggestions)); mSuggestions.put(category, new ArrayList<>(suggestions));
if (mObserver != null) mObserver.onNewSuggestions(category); for (Observer observer : mObserverList) observer.onNewSuggestions(category);
} }
/** /**
...@@ -136,14 +140,16 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -136,14 +140,16 @@ public class FakeSuggestionsSource implements SuggestionsSource {
break; break;
} }
} }
mObserver.onSuggestionInvalidated(category, idWithinCategory); for (Observer observer : mObserverList) {
observer.onSuggestionInvalidated(category, idWithinCategory);
}
} }
/** /**
* Notifies the observer that a full refresh is required. * Notifies the observer that a full refresh is required.
*/ */
public void fireFullRefreshRequired() { public void fireFullRefreshRequired() {
mObserver.onFullRefreshRequired(); for (Observer observer : mObserverList) observer.onFullRefreshRequired();
} }
/** /**
...@@ -159,6 +165,15 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -159,6 +165,15 @@ public class FakeSuggestionsSource implements SuggestionsSource {
@Override @Override
public void fetchRemoteSuggestions() {} public void fetchRemoteSuggestions() {}
@Override
public boolean areRemoteSuggestionsEnabled() {
return mRemoteSuggestionsEnabled;
}
public void setRemoteSuggestionsEnabled(boolean enabled) {
mRemoteSuggestionsEnabled = enabled;
}
@Override @Override
public void dismissSuggestion(SnippetArticle suggestion) { public void dismissSuggestion(SnippetArticle suggestion) {
for (List<SnippetArticle> suggestions : mSuggestions.values()) { for (List<SnippetArticle> suggestions : mSuggestions.values()) {
...@@ -229,8 +244,13 @@ public class FakeSuggestionsSource implements SuggestionsSource { ...@@ -229,8 +244,13 @@ public class FakeSuggestionsSource implements SuggestionsSource {
} }
@Override @Override
public void setObserver(Observer observer) { public void addObserver(Observer observer) {
mObserver = observer; mObserverList.addObserver(observer);
}
@Override
public void removeObserver(Observer observer) {
mObserverList.removeObserver(observer);
} }
@Override @Override
......
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