chromium/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java

// Copyright 2021 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.app.feed;

import android.app.Activity;
import android.content.Intent;

import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.app.creator.CreatorActivity;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.device_lock.DeviceLockActivityLauncherImpl;
import org.chromium.chrome.browser.feed.FeedActionDelegate;
import org.chromium.chrome.browser.feed.R;
import org.chromium.chrome.browser.feed.SingleWebFeedEntryPoint;
import org.chromium.chrome.browser.feed.signinbottomsheet.SigninBottomSheetCoordinator;
import org.chromium.chrome.browser.feed.webfeed.CreatorIntentConstants;
import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.offlinepages.RequestCoordinatorBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.SigninAndHistorySyncActivityLauncherImpl;
import org.chromium.chrome.browser.signin.SyncConsentActivityLauncherImpl;
import org.chromium.chrome.browser.signin.services.SigninMetricsUtils;
import org.chromium.chrome.browser.suggestions.SuggestionsConfig;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.signin.SigninAndHistorySyncCoordinator;
import org.chromium.chrome.browser.ui.signin.account_picker.AccountPickerBottomSheetStrings;
import org.chromium.chrome.browser.util.BrowserUiUtils;
import org.chromium.chrome.browser.util.BrowserUiUtils.ModuleTypeOnStartAndNtp;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.signin.metrics.SigninAccessPoint;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.common.Referrer;
import org.chromium.net.NetError;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.mojom.WindowOpenDisposition;
import org.chromium.url.GURL;

/** Implements some actions for the Feed */
public class FeedActionDelegateImpl implements FeedActionDelegate {
    private static final String NEW_TAB_URL_HELP = "https://support.google.com/chrome/?p=new_tab";
    private final NativePageNavigationDelegate mNavigationDelegate;
    private final BookmarkModel mBookmarkModel;
    private final Activity mActivity;
    private final SnackbarManager mSnackbarManager;
    private final TabModelSelector mTabModelSelector;
    private final Profile mProfile;
    private final BottomSheetController mBottomSheetController;

    public FeedActionDelegateImpl(
            Activity activity,
            SnackbarManager snackbarManager,
            NativePageNavigationDelegate navigationDelegate,
            BookmarkModel bookmarkModel,
            TabModelSelector tabModelSelector,
            Profile profile,
            BottomSheetController bottomSheetController) {
        mActivity = activity;
        mNavigationDelegate = navigationDelegate;
        mBookmarkModel = bookmarkModel;
        mSnackbarManager = snackbarManager;
        mTabModelSelector = tabModelSelector;
        mProfile = profile;
        mBottomSheetController = bottomSheetController;
    }

    @Override
    public void downloadPage(String url) {
        RequestCoordinatorBridge.getForProfile(mProfile)
                .savePageLater(
                        url, OfflinePageBridge.NTP_SUGGESTIONS_NAMESPACE, true /* user requested*/);
    }

    @Override
    public void openSuggestionUrl(
            int disposition,
            LoadUrlParams params,
            boolean inGroup,
            int pageId,
            PageLoadObserver pageLoadObserver,
            Callback<VisitResult> onVisitComplete) {
        params.setReferrer(
                new Referrer(
                        SuggestionsConfig.getReferrerUrl(),
                        // WARNING: ReferrerPolicy.ALWAYS is assumed by other Chrome code for NTP
                        // tiles to set consider_for_ntp_most_visited.
                        org.chromium.network.mojom.ReferrerPolicy.ALWAYS));

        Tab tab =
                inGroup
                        ? mNavigationDelegate.openUrlInGroup(disposition, params)
                        : mNavigationDelegate.openUrl(disposition, params);

        NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);

        boolean inNewTab =
                (disposition == WindowOpenDisposition.NEW_BACKGROUND_TAB
                        || disposition == WindowOpenDisposition.OFF_THE_RECORD);

        if (tab != null) {
            tab.addObserver(new FeedTabNavigationObserver(inNewTab, pageId, pageLoadObserver));
            NavigationRecorder.record(
                    tab,
                    navigationResult -> {
                        FeedActionDelegate.VisitResult result =
                                new FeedActionDelegate.VisitResult();
                        result.visitTimeMs = navigationResult.duration;
                        onVisitComplete.onResult(result);
                    });
        }

        BrowserUiUtils.recordModuleClickHistogram(ModuleTypeOnStartAndNtp.FEED);
    }

    @Override
    public void openUrl(int disposition, LoadUrlParams params) {
        mNavigationDelegate.openUrl(disposition, params);
    }

    @Override
    public void openHelpPage() {
        mNavigationDelegate.openUrl(
                WindowOpenDisposition.CURRENT_TAB,
                new LoadUrlParams(NEW_TAB_URL_HELP, PageTransition.AUTO_BOOKMARK));

        BrowserUiUtils.recordModuleClickHistogram(ModuleTypeOnStartAndNtp.FEED);
    }

    @Override
    public void addToReadingList(String title, String url) {
        mBookmarkModel.finishLoadingBookmarkModel(
                () -> {
                    assert ThreadUtils.runningOnUiThread();
                    BookmarkUtils.addToReadingList(
                            mActivity,
                            mBookmarkModel,
                            title,
                            new GURL(url),
                            mSnackbarManager,
                            mProfile,
                            mBottomSheetController);
                });
    }

    @Override
    public void openWebFeed(String webFeedName, @SingleWebFeedEntryPoint int entryPoint) {
        if (!WebFeedBridge.isCormorantEnabledForLocale()) {
            return;
        }

        assert ThreadUtils.runningOnUiThread();
        Class<?> creatorActivityClass = CreatorActivity.class;
        Intent intent = new Intent(mActivity, creatorActivityClass);
        intent.putExtra(CreatorIntentConstants.CREATOR_WEB_FEED_ID, webFeedName.getBytes());
        intent.putExtra(CreatorIntentConstants.CREATOR_ENTRY_POINT, entryPoint);
        intent.putExtra(CreatorIntentConstants.CREATOR_TAB_ID, mTabModelSelector.getCurrentTabId());
        mActivity.startActivity(intent);
    }

    @Override
    public void showSyncConsentActivity(@SigninAccessPoint int signinAccessPoint) {
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_SHOW_SIGN_IN_COMMAND)) {
            SyncConsentActivityLauncherImpl.get()
                    .launchActivityIfAllowed(mActivity, signinAccessPoint);
        }
    }

    @Override
    public void startSigninFlow(@SigninAccessPoint int signinAccessPoint) {
        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_SHOW_SIGN_IN_COMMAND)) {
            return;
        }
        AccountPickerBottomSheetStrings bottomSheetStrings =
                new AccountPickerBottomSheetStrings.Builder(
                                R.string.signin_account_picker_bottom_sheet_title)
                        .build();
        SigninAndHistorySyncActivityLauncherImpl.get()
                .launchActivityIfAllowed(
                        mActivity,
                        mProfile,
                        bottomSheetStrings,
                        SigninAndHistorySyncCoordinator.NoAccountSigninMode.BOTTOM_SHEET,
                        SigninAndHistorySyncCoordinator.WithAccountSigninMode
                                .DEFAULT_ACCOUNT_BOTTOM_SHEET,
                        SigninAndHistorySyncCoordinator.HistoryOptInMode.NONE,
                        signinAccessPoint);
    }

    @Override
    public void showSignInInterstitial(
            @SigninAccessPoint int signinAccessPoint,
            BottomSheetController bottomSheetController,
            WindowAndroid windowAndroid) {
        if (ChromeFeatureList.isEnabled(
                ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)) {
            AccountPickerBottomSheetStrings bottomSheetStrings =
                    new AccountPickerBottomSheetStrings.Builder(
                                    R.string
                                            .signin_account_picker_bottom_sheet_title_for_back_of_card_menu_signin)
                            .setSubtitleStringId(
                                    R.string
                                            .signin_account_picker_bottom_sheet_subtitle_for_back_of_card_menu_signin)
                            .setDismissButtonStringId(R.string.cancel)
                            .build();
            SigninAndHistorySyncActivityLauncherImpl.get()
                    .launchActivityIfAllowed(
                            mActivity,
                            mProfile,
                            bottomSheetStrings,
                            SigninAndHistorySyncCoordinator.NoAccountSigninMode.BOTTOM_SHEET,
                            SigninAndHistorySyncCoordinator.WithAccountSigninMode
                                    .DEFAULT_ACCOUNT_BOTTOM_SHEET,
                            SigninAndHistorySyncCoordinator.HistoryOptInMode.NONE,
                            signinAccessPoint);
            return;
        }
        AccountPickerBottomSheetStrings bottomSheetStrings =
                new AccountPickerBottomSheetStrings.Builder(
                                R.string
                                        .signin_account_picker_bottom_sheet_title_for_back_of_card_menu_signin)
                        .setSubtitleStringId(
                                R.string
                                        .signin_account_picker_bottom_sheet_subtitle_for_back_of_card_menu_signin_old)
                        .setDismissButtonStringId(R.string.close)
                        .build();
        SigninMetricsUtils.logSigninStarted(signinAccessPoint);
        SigninMetricsUtils.logSigninUserActionForAccessPoint(signinAccessPoint);
        SigninBottomSheetCoordinator signinCoordinator =
                new SigninBottomSheetCoordinator(
                        windowAndroid,
                        DeviceLockActivityLauncherImpl.get(),
                        bottomSheetController,
                        mProfile,
                        bottomSheetStrings,
                        null,
                        signinAccessPoint);
        signinCoordinator.show();
    }

    /**
     * A {@link TabObserver} that observes navigation related events that originate from Feed
     * interactions. Calls reportPageLoaded when navigation completes.
     */
    private class FeedTabNavigationObserver extends EmptyTabObserver {
        private final boolean mInNewTab;
        private final int mPageId;
        private final PageLoadObserver mPageLoadObserver;

        FeedTabNavigationObserver(boolean inNewTab, int pageId, PageLoadObserver pageLoadObserver) {
            mInNewTab = inNewTab;
            mPageId = pageId;
            mPageLoadObserver = pageLoadObserver;
        }

        @Override
        public void onPageLoadStarted(Tab tab, GURL url) {
            mPageLoadObserver.onPageLoadStarted(mPageId);
        }

        @Override
        public void onPageLoadFinished(Tab tab, GURL url) {
            // TODO(jianli): onPageLoadFinished is called on successful load, and if a user manually
            // stops the page load. We should only capture successful page loads.
            mPageLoadObserver.onPageLoadFinished(mPageId, mInNewTab);
            tab.removeObserver(this);
        }

        @Override
        public void onPageLoadFailed(Tab tab, @NetError int errorCode) {
            mPageLoadObserver.onPageLoadFailed(mPageId, errorCode);
            tab.removeObserver(this);
        }

        @Override
        public void onCrash(Tab tab) {
            tab.removeObserver(this);
        }

        @Override
        public void onDestroyed(Tab tab) {
            tab.removeObserver(this);
        }

        @Override
        public void didFirstVisuallyNonEmptyPaint(Tab tab) {
            mPageLoadObserver.onPageFirstContentfulPaint(mPageId);
        }
    }
}