chromium/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedFeatures.java

// Copyright 2020 The Chromium Authors
// 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.feed;

import androidx.annotation.NonNull;

import org.chromium.base.CommandLine;
import org.chromium.base.LocaleUtils;
import org.chromium.base.ResettersForTesting;
import org.chromium.chrome.browser.feed.componentinterfaces.SurfaceCoordinator.StreamTabId;
import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.user_prefs.UserPrefs;

import java.util.concurrent.TimeUnit;

/** Helper methods covering more complex Feed related feature checks and states. */
public final class FeedFeatures {
    private static final long ONE_DAY_DELTA_MILLIS = TimeUnit.DAYS.toMillis(1L);

    private static PrefService sFakePrefServiceForTest;
    private static boolean sIsFirstFeedTabStickinessCheckSinceLaunch = true;

    /**
     * @param profile the profile of the current user.
     * @return Whether the feed is allowed to be used. Returns false if the feed is disabled due to
     *     enterprise policy, or by flag. The value returned should not be cached as it may change.
     */
    public static boolean isFeedEnabled(Profile profile) {
        return FeedServiceBridge.isEnabled() && isFeedEnabledByDSE(profile);
    }

    /**
     * @param profile the profile of the current user.
     * @return Whether the WebFeed UI should be enabled. Checks for the WEB_FEED flag, if the user
     *     is signed in and confirms it's not a child profile.
     */
    public static boolean isWebFeedUIEnabled(@NonNull Profile profile) {
        // TODO(b/197354832, b/188188861): change consent check to SIGNIN.
        boolean isPrimaryAccountSignedIn = false;
        if (IdentityServicesProvider.get().getSigninManager(profile) != null) {
            isPrimaryAccountSignedIn =
                    IdentityServicesProvider.get()
                            .getSigninManager(profile)
                            .getIdentityManager()
                            .hasPrimaryAccount(ConsentLevel.SIGNIN);
        }
        return WebFeedBridge.isWebFeedEnabled()
                && isPrimaryAccountSignedIn
                && !profile.isChild()
                && isFeedEnabledByDSE(profile);
    }

    private static boolean isFeedEnabledByDSE(Profile profile) {
        return getPrefService(profile).getBoolean(Pref.ENABLE_SNIPPETS_BY_DSE);
    }

    public static boolean shouldUseWebFeedAwarenessIPH() {
        String awarenessStyleParam =
                ChromeFeatureList.getFieldTrialParamByFeature(
                        ChromeFeatureList.WEB_FEED_AWARENESS, "awareness_style");
        return WebFeedBridge.isWebFeedEnabled()
                && (awarenessStyleParam.equals("IPH") || awarenessStyleParam.isEmpty());
    }

    public static boolean shouldUseNewIndicator(Profile profile) {
        // Return true if we are not rate limited.
        if (ChromeFeatureList.getFieldTrialParamByFeature(
                        ChromeFeatureList.WEB_FEED_AWARENESS, "awareness_style")
                .equals("new_animation_no_limit")) {
            return true;
        }
        // Otherwise, the rate limit is:
        // 1. We have never seen the web feed.
        // 2. It's been > 1 day since we last seen the new indicator.
        if (ChromeFeatureList.getFieldTrialParamByFeature(
                                ChromeFeatureList.WEB_FEED_AWARENESS, "awareness_style")
                        .equals("new_animation")
                && !getPrefService(profile).getBoolean(Pref.HAS_SEEN_WEB_FEED)) {
            String timestamp = getPrefService(profile).getString(Pref.LAST_BADGE_ANIMATION_TIME);
            long currentTime = System.currentTimeMillis();
            long parsedTime;
            try {
                parsedTime = Long.parseLong(timestamp);
            } catch (NumberFormatException e) {
                parsedTime = 0L;
            }
            // Ignore parsed timestamps in the future.
            return currentTime < parsedTime || currentTime - parsedTime > ONE_DAY_DELTA_MILLIS;
        }
        return false;
    }

    public static boolean isFeedFollowUiUpdateEnabled() {
        if (LocaleUtils.getDefaultCountryCode().equals("US")) {
            return true;
        }
        return ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_FOLLOW_UI_UPDATE);
    }

    /** Updates the timestamp for the last time the new indicator was seen to now. */
    public static void updateNewIndicatorTimestamp(Profile profile) {
        getPrefService(profile)
                .setString(Pref.LAST_BADGE_ANIMATION_TIME, "" + System.currentTimeMillis());
    }

    /** Updates that the following feed has been seen. */
    public static void updateFollowingFeedSeen(Profile profile) {
        getPrefService(profile).setBoolean(Pref.HAS_SEEN_WEB_FEED, true);
    }

    /**
     * @return Whether the feed should automatically scroll down when it first loads so that the
     *         first card is at the top of the screen. This is for use with screenshot utilities.
     */
    public static boolean isAutoScrollToTopEnabled() {
        CommandLine commandLine = CommandLine.getInstance();
        if (commandLine == null) return false;
        return commandLine.hasSwitch("feed-screenshot-mode");
    }

    public static PrefService getPrefService(Profile profile) {
        if (sFakePrefServiceForTest != null) {
            return sFakePrefServiceForTest;
        }
        return UserPrefs.get(profile);
    }

    public static void setFakePrefsForTest(PrefService fakePref) {
        sFakePrefServiceForTest = fakePref;
        ResettersForTesting.register(() -> sFakePrefServiceForTest = null);
    }

    /**
     * Returns the feed tab ID to restore depending on the configured logic controlling the
     * "stickiness" of the selected feed tab.
     */
    public static @StreamTabId int getFeedTabIdToRestore(Profile profile) {
        // Default behavior (reset_for_every_new_ntp).
        setLastSeenFeedTabId(profile, StreamTabId.FOR_YOU);
        return StreamTabId.FOR_YOU;
    }

    public static void setLastSeenFeedTabId(Profile profile, @StreamTabId int tabId) {
        // Note: the "first check" flag is updated here to make sure that if setLastSeenFeedTabId is
        // called before getFeedTabIdToRestore, the value set here is taken into account in by the
        // latter at least for some of the restore logic atlernatives.
        sIsFirstFeedTabStickinessCheckSinceLaunch = false;
        getPrefService(profile).setInteger(Pref.LAST_SEEN_FEED_TYPE, tabId);
    }

    private static @StreamTabId int getLastSeenFeedTabId(Profile profile) {
        return getPrefService(profile).getInteger(Pref.LAST_SEEN_FEED_TYPE);
    }

    static void resetInternalStateForTesting() {
        sIsFirstFeedTabStickinessCheckSinceLaunch = true;
    }
}