chromium/chrome/android/java/src/org/chromium/chrome/browser/page_info/PageInfoHistoryController.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.page_info;

import android.content.res.Resources;
import android.text.format.DateUtils;
import android.view.View;
import android.view.ViewGroup;

import org.chromium.base.ResettersForTesting;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.history.BrowsingHistoryBridge;
import org.chromium.chrome.browser.history.HistoryContentManager;
import org.chromium.chrome.browser.history.HistoryItem;
import org.chromium.chrome.browser.history.HistoryProvider;
import org.chromium.chrome.browser.history.HistoryUmaRecorder;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.browser_ui.util.date.CalendarUtils;
import org.chromium.components.browser_ui.util.date.StringUtils;
import org.chromium.components.page_info.PageInfoAction;
import org.chromium.components.page_info.PageInfoControllerDelegate;
import org.chromium.components.page_info.PageInfoMainController;
import org.chromium.components.page_info.PageInfoRowView;
import org.chromium.components.page_info.PageInfoSubpageController;

import java.util.Date;

/** Class for controlling the page info history section. */
public class PageInfoHistoryController
        implements PageInfoSubpageController, HistoryContentManager.Observer {
    public static final int HISTORY_ROW_ID = View.generateViewId();

    private static HistoryProvider sProviderForTests;

    /** Clock to use so we can mock time in tests. */
    public interface Clock {
        long currentTimeMillis();
    }

    private static Clock sClock = System::currentTimeMillis;

    private final PageInfoMainController mMainController;
    private final PageInfoRowView mRowView;
    private final PageInfoControllerDelegate mDelegate;
    private final Supplier<Tab> mTabSupplier;
    private final String mTitle;
    private final String mHost;
    private boolean mDataIsStale;
    private HistoryProvider mHistoryProvider;
    private HistoryContentManager mContentManager;
    private long mLastVisitedTimestamp;

    public PageInfoHistoryController(
            PageInfoMainController mainController,
            PageInfoRowView rowView,
            PageInfoControllerDelegate delegate,
            Supplier<Tab> tabSupplier) {
        mMainController = mainController;
        mRowView = rowView;
        mDelegate = delegate;
        mTitle = mRowView.getContext().getResources().getString(R.string.page_info_history_title);
        mHost = mainController.getURL().getHost();
        mTabSupplier = tabSupplier;

        updateLastVisit();
    }

    private void launchSubpage() {
        mMainController.recordAction(PageInfoAction.PAGE_INFO_HISTORY_OPENED);
        mMainController.launchSubpage(this);
    }

    @Override
    public String getSubpageTitle() {
        return mTitle;
    }

    @Override
    public View createViewForSubpage(ViewGroup parent) {
        assert !mDelegate.isIncognito();
        Profile profile = (Profile) mDelegate.getBrowserContext();
        mContentManager =
                new HistoryContentManager(
                        mMainController.getActivity(),
                        this,
                        /* isSeparateActivity= */ false,
                        /* profile= */ profile,
                        /* shouldShowPrivacyDisclaimers= */ true,
                        /* shouldShowClearDataIfAvailable= */ false,
                        mHost,
                        /* selectionDelegate= */ null,
                        /* bottomSheetController= */ null,
                        mTabSupplier,
                        /* hideSoftKeyboard= */ null,
                        /* umaRecorder= */ new HistoryUmaRecorder(),
                        new BrowsingHistoryBridge(profile),
                        null,
                        /* launchedForApp= */ false,
                        /* showAppFilter= */ false);
        mContentManager.startLoadingItems();
        return mContentManager.getRecyclerView();
    }

    @Override
    public void onSubpageRemoved() {
        if (mContentManager != null) {
            mContentManager.onDestroyed();
            mContentManager = null;
        }
    }

    private void updateLastVisit() {
        mHistoryProvider =
                sProviderForTests != null
                        ? sProviderForTests
                        : new BrowsingHistoryBridge((Profile) mDelegate.getBrowserContext());
        mHistoryProvider.getLastVisitToHostBeforeRecentNavigations(
                mHost,
                (timestamp) -> {
                    mLastVisitedTimestamp = timestamp;
                    if (mHistoryProvider != null) {
                        mHistoryProvider.destroy();
                        mHistoryProvider = null;
                    }
                    setupHistoryRow();
                });
    }

    private void setupHistoryRow() {
        PageInfoRowView.ViewParams rowParams = new PageInfoRowView.ViewParams();
        rowParams.title = getRowTitle();
        rowParams.visible =
                rowParams.title != null
                        && mDelegate.isSiteSettingsAvailable()
                        && !mDelegate.isIncognito();
        rowParams.iconResId = R.drawable.ic_history_googblue_24dp;
        rowParams.decreaseIconSize = true;
        rowParams.clickCallback = this::launchSubpage;

        mRowView.setParams(rowParams);
    }

    private String getRowTitle() {
        if (mLastVisitedTimestamp == 0) {
            return null;
        }
        long today = CalendarUtils.getStartOfDay(sClock.currentTimeMillis()).getTime().getTime();
        long lastVisitedDay =
                CalendarUtils.getStartOfDay(mLastVisitedTimestamp).getTime().getTime();
        long difference = today - lastVisitedDay;
        Resources resources = mRowView.getContext().getResources();
        if (difference < 0) {
            return null;
        } else if (difference == 0) {
            return resources.getString(R.string.page_info_history_last_visit_today);
        } else if (difference == DateUtils.DAY_IN_MILLIS) {
            return resources.getString(R.string.page_info_history_last_visit_yesterday);
        } else if (difference > DateUtils.DAY_IN_MILLIS
                && difference <= DateUtils.DAY_IN_MILLIS * 7) {
            return resources.getString(
                    R.string.page_info_history_last_visit_days,
                    (int) (difference / DateUtils.DAY_IN_MILLIS));
        } else {
            return resources.getString(
                    R.string.page_info_history_last_visit_date,
                    StringUtils.dateToHeaderString(new Date(mLastVisitedTimestamp)));
        }
    }

    @Override
    public void clearData() {
        // TODO(crbug.com/40746014): Add functionality for clear history for this site.
        return;
    }

    @Override
    public void updateRowIfNeeded() {
        if (mDataIsStale) {
            updateLastVisit();
        }
        mDataIsStale = false;
    }

    // HistoryContentManager.Observer
    @Override
    public void onScrolledCallback(boolean loadedMore) {}

    // HistoryContentManager.Observer
    @Override
    public void onItemClicked(HistoryItem item) {
        mMainController.recordAction(PageInfoAction.PAGE_INFO_HISTORY_ENTRY_CLICKED);
        return;
    }

    // HistoryContentManager.Observer
    @Override
    public void onItemRemoved(HistoryItem item) {
        mMainController.recordAction(PageInfoAction.PAGE_INFO_HISTORY_ENTRY_REMOVED);
        mDataIsStale = true;
        if (mContentManager.getItemCount() == 0) {
            // Do the update right away if there are no entries left.
            mLastVisitedTimestamp = 0;
            setupHistoryRow();
            mMainController.exitSubpage();
        }
    }

    // HistoryContentManager.Observer
    @Override
    public void onClearBrowsingDataClicked() {
        // TODO(crbug.com/40746014): Add functionality for "clear history" button click and
        // change the name of the current clear browsing data button.
        return;
    }

    // HistoryContentManager.Observer
    @Override
    public void onOpenFullChromeHistoryClicked() {}

    // HistoryContentManager.Observer
    @Override
    public void onPrivacyDisclaimerHasChanged() {}

    // HistoryContentManager.Observer
    @Override
    public void onUserAccountStateChanged() {}

    // HistoryContentManager.Observer
    @Override
    public void onHistoryDeletedExternally() {}

    /** @param provider The {@link HistoryProvider} that is used in place of a real one. */
    public static void setProviderForTests(HistoryProvider provider) {
        sProviderForTests = provider;
        ResettersForTesting.register(() -> sProviderForTests = null);
    }

    static void setClockForTesting(Clock clock) {
        var oldValue = sClock;
        sClock = clock;
        ResettersForTesting.register(() -> sClock = oldValue);
    }
}