// Copyright 2015 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.native_page;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.jank_tracker.JankTracker;
import org.chromium.base.supplier.DestroyableObservableSupplier;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.app.download.home.DownloadPage;
import org.chromium.chrome.browser.bookmarks.BookmarkPage;
import org.chromium.chrome.browser.browser_controls.BrowserControlsMarginSupplier;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
import org.chromium.chrome.browser.history.HistoryManagerUtils;
import org.chromium.chrome.browser.history.HistoryPage;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.magic_stack.ModuleRegistry;
import org.chromium.chrome.browser.management.ManagementPage;
import org.chromium.chrome.browser.ntp.IncognitoNewTabPage;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.ntp.RecentTabsManager;
import org.chromium.chrome.browser.ntp.RecentTabsPage;
import org.chromium.chrome.browser.pdf.PdfInfo;
import org.chromium.chrome.browser.pdf.PdfPage;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.HomeSurfaceTracker;
import org.chromium.chrome.browser.toolbar.top.Toolbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.chrome.browser.ui.native_page.NativePage.NativePageType;
import org.chromium.chrome.browser.ui.native_page.NativePageHost;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.util.ColorUtils;
/**
* Creates NativePage objects to show chrome-native:// URLs using the native Android view system.
*/
public class NativePageFactory {
private final Activity mActivity;
private final BottomSheetController mBottomSheetController;
private final BrowserControlsManager mBrowserControlsManager;
private final Supplier<Tab> mCurrentTabSupplier;
private final Supplier<SnackbarManager> mSnackbarManagerSupplier;
private final ActivityLifecycleDispatcher mLifecycleDispatcher;
private final TabModelSelector mTabModelSelector;
private final Supplier<ShareDelegate> mShareDelegateSupplier;
private final WindowAndroid mWindowAndroid;
private final JankTracker mJankTracker;
private final Supplier<Toolbar> mToolbarSupplier;
private final HomeSurfaceTracker mHomeSurfaceTracker;
private final ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
private final ObservableSupplier<Integer> mTabStripHeightSupplier;
private final OneshotSupplier<ModuleRegistry> mModuleRegistrySupplier;
private NewTabPageUma mNewTabPageUma;
private NativePageBuilder mNativePageBuilder;
private static NativePage sTestPage;
public NativePageFactory(
@NonNull Activity activity,
@NonNull BottomSheetController sheetController,
@NonNull BrowserControlsManager browserControlsManager,
@NonNull Supplier<Tab> currentTabSupplier,
@NonNull Supplier<SnackbarManager> snackbarManagerSupplier,
@NonNull ActivityLifecycleDispatcher lifecycleDispatcher,
@NonNull TabModelSelector tabModelSelector,
@NonNull Supplier<ShareDelegate> shareDelegateSupplier,
@NonNull WindowAndroid windowAndroid,
@NonNull JankTracker jankTracker,
@NonNull Supplier<Toolbar> toolbarSupplier,
@Nullable HomeSurfaceTracker homeSurfaceTracker,
@Nullable ObservableSupplier<TabContentManager> tabContentManagerSupplier,
@NonNull ObservableSupplier<Integer> tabStripHeightSupplier,
@NonNull OneshotSupplier<ModuleRegistry> moduleRegistrySupplier) {
mActivity = activity;
mBottomSheetController = sheetController;
mBrowserControlsManager = browserControlsManager;
mCurrentTabSupplier = currentTabSupplier;
mSnackbarManagerSupplier = snackbarManagerSupplier;
mLifecycleDispatcher = lifecycleDispatcher;
mTabModelSelector = tabModelSelector;
mShareDelegateSupplier = shareDelegateSupplier;
mWindowAndroid = windowAndroid;
mJankTracker = jankTracker;
mToolbarSupplier = toolbarSupplier;
mHomeSurfaceTracker = homeSurfaceTracker;
mTabContentManagerSupplier = tabContentManagerSupplier;
mTabStripHeightSupplier = tabStripHeightSupplier;
mModuleRegistrySupplier = moduleRegistrySupplier;
}
private NativePageBuilder getBuilder() {
if (mNativePageBuilder == null) {
mNativePageBuilder =
new NativePageBuilder(
mActivity,
this::getNewTabPageUma,
mBottomSheetController,
mBrowserControlsManager,
mCurrentTabSupplier,
mSnackbarManagerSupplier,
mLifecycleDispatcher,
mTabModelSelector,
mShareDelegateSupplier,
mWindowAndroid,
mJankTracker,
mToolbarSupplier,
mHomeSurfaceTracker,
mTabContentManagerSupplier,
mTabStripHeightSupplier,
mModuleRegistrySupplier);
}
return mNativePageBuilder;
}
private NewTabPageUma getNewTabPageUma() {
if (mNewTabPageUma == null) {
mNewTabPageUma = new NewTabPageUma(mTabModelSelector);
mNewTabPageUma.monitorNtpCreation();
}
return mNewTabPageUma;
}
@VisibleForTesting
static class NativePageBuilder {
private final Activity mActivity;
private final BottomSheetController mBottomSheetController;
private final Supplier<NewTabPageUma> mUma;
private final BrowserControlsManager mBrowserControlsManager;
private final Supplier<Tab> mCurrentTabSupplier;
private final Supplier<SnackbarManager> mSnackbarManagerSupplier;
private final ActivityLifecycleDispatcher mLifecycleDispatcher;
private final TabModelSelector mTabModelSelector;
private final Supplier<ShareDelegate> mShareDelegateSupplier;
private final WindowAndroid mWindowAndroid;
private final JankTracker mJankTracker;
private final Supplier<Toolbar> mToolbarSupplier;
private final HomeSurfaceTracker mHomeSurfaceTracker;
private final ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
private final ObservableSupplier<Integer> mTabStripHeightSupplier;
private final OneshotSupplier<ModuleRegistry> mModuleRegistrySupplier;
public NativePageBuilder(
Activity activity,
Supplier<NewTabPageUma> uma,
BottomSheetController sheetController,
BrowserControlsManager browserControlsManager,
Supplier<Tab> currentTabSupplier,
Supplier<SnackbarManager> snackbarManagerSupplier,
ActivityLifecycleDispatcher lifecycleDispatcher,
TabModelSelector tabModelSelector,
Supplier<ShareDelegate> shareDelegateSupplier,
WindowAndroid windowAndroid,
JankTracker jankTracker,
Supplier<Toolbar> toolbarSupplier,
HomeSurfaceTracker homeSurfaceTracker,
ObservableSupplier<TabContentManager> tabContentManagerSupplier,
ObservableSupplier<Integer> tabStripHeightSupplier,
OneshotSupplier<ModuleRegistry> moduleRegistrySupplier) {
mActivity = activity;
mUma = uma;
mBottomSheetController = sheetController;
mBrowserControlsManager = browserControlsManager;
mCurrentTabSupplier = currentTabSupplier;
mSnackbarManagerSupplier = snackbarManagerSupplier;
mLifecycleDispatcher = lifecycleDispatcher;
mTabModelSelector = tabModelSelector;
mShareDelegateSupplier = shareDelegateSupplier;
mWindowAndroid = windowAndroid;
mJankTracker = jankTracker;
mToolbarSupplier = toolbarSupplier;
mHomeSurfaceTracker = homeSurfaceTracker;
mTabContentManagerSupplier = tabContentManagerSupplier;
mTabStripHeightSupplier = tabStripHeightSupplier;
mModuleRegistrySupplier = moduleRegistrySupplier;
}
protected NativePage buildNewTabPage(Tab tab, String url) {
NativePageHost nativePageHost =
new TabShim(tab, mBrowserControlsManager, mTabModelSelector);
if (tab.isIncognito()) {
return new IncognitoNewTabPage(mActivity, nativePageHost, tab.getProfile());
}
return new NewTabPage(
mActivity,
mBrowserControlsManager,
mCurrentTabSupplier,
mWindowAndroid.getModalDialogManager(),
mSnackbarManagerSupplier.get(),
mLifecycleDispatcher,
mTabModelSelector,
DeviceFormFactor.isWindowOnTablet(mWindowAndroid),
mUma.get(),
ColorUtils.inNightMode(mActivity),
nativePageHost,
tab,
url,
mBottomSheetController,
mShareDelegateSupplier,
mWindowAndroid,
mJankTracker,
mToolbarSupplier,
mHomeSurfaceTracker,
mTabContentManagerSupplier,
mTabStripHeightSupplier,
mModuleRegistrySupplier);
}
protected NativePage buildBookmarksPage(Tab tab) {
return new BookmarkPage(
mActivity.getComponentName(),
mSnackbarManagerSupplier.get(),
tab.getProfile(),
new TabShim(tab, mBrowserControlsManager, mTabModelSelector));
}
protected NativePage buildDownloadsPage(Tab tab) {
Profile profile = tab.getProfile();
return new DownloadPage(
mActivity,
mSnackbarManagerSupplier.get(),
mWindowAndroid.getModalDialogManager(),
profile.getOTRProfileID(),
new TabShim(tab, mBrowserControlsManager, mTabModelSelector));
}
protected NativePage buildHistoryPage(Tab tab, String url) {
return new HistoryPage(
mActivity,
new TabShim(tab, mBrowserControlsManager, mTabModelSelector),
mSnackbarManagerSupplier.get(),
tab.getProfile(),
mBottomSheetController,
mCurrentTabSupplier,
url);
}
protected NativePage buildRecentTabsPage(Tab tab) {
RecentTabsManager recentTabsManager =
new RecentTabsManager(
tab,
mTabModelSelector,
tab.getProfile(),
mActivity,
() ->
HistoryManagerUtils.showHistoryManager(
mActivity,
tab,
mTabModelSelector.isIncognitoSelected()));
return new RecentTabsPage(
mActivity,
recentTabsManager,
new TabShim(tab, mBrowserControlsManager, mTabModelSelector),
mBrowserControlsManager,
mTabStripHeightSupplier);
}
protected NativePage buildManagementPage(Tab tab) {
return new ManagementPage(
new TabShim(tab, mBrowserControlsManager, mTabModelSelector), tab.getProfile());
}
protected NativePage buildPdfPage(Tab tab, String url, PdfInfo pdfInfo) {
return NativePageFactory.buildPdfPage(
url, tab, pdfInfo, mBrowserControlsManager, mTabModelSelector, mActivity);
}
}
/**
* Returns a NativePage for displaying the given URL if the URL is a valid chrome-native URL, or
* represents a pdf file. Otherwise returns null. If candidatePage is non-null and corresponds
* to the URL, it will be returned. Otherwise, a new NativePage will be constructed.
*
* @param url The URL to be handled.
* @param candidatePage A NativePage to be reused if it matches the url, or null.
* @param tab The Tab that will show the page.
* @param pdfInfo Information of the pdf, or null if there is no associated pdf download.
* @return A NativePage showing the specified url or null.
*/
public NativePage createNativePage(
String url, NativePage candidatePage, Tab tab, PdfInfo pdfInfo) {
return createNativePageForURL(url, candidatePage, tab, tab.isIncognito(), pdfInfo);
}
@VisibleForTesting
NativePage createNativePageForURL(
String url, NativePage candidatePage, Tab tab, boolean isIncognito, PdfInfo pdfInfo) {
NativePage page;
switch (NativePage.nativePageType(url, candidatePage, isIncognito, pdfInfo != null)) {
case NativePageType.NONE:
return null;
case NativePageType.CANDIDATE:
page = candidatePage;
break;
case NativePageType.NTP:
page = getBuilder().buildNewTabPage(tab, url);
break;
case NativePageType.BOOKMARKS:
page = getBuilder().buildBookmarksPage(tab);
break;
case NativePageType.DOWNLOADS:
page = getBuilder().buildDownloadsPage(tab);
break;
case NativePageType.HISTORY:
page = getBuilder().buildHistoryPage(tab, url);
break;
case NativePageType.RECENT_TABS:
page = getBuilder().buildRecentTabsPage(tab);
break;
case NativePageType.MANAGEMENT:
page = getBuilder().buildManagementPage(tab);
break;
case NativePageType.PDF:
page = getBuilder().buildPdfPage(tab, url, pdfInfo);
break;
default:
assert false;
return null;
}
if (page != null) page.updateForUrl(url);
return page;
}
void setNativePageBuilderForTesting(NativePageBuilder builder) {
mNativePageBuilder = builder;
}
/**
* Returns a NativePage for displaying the given URL if the URL represents a pdf file. Otherwise
* returns null. If candidatePage is non-null and corresponds to the URL, it will be returned.
* Otherwise, a new NativePage will be constructed.
*
* @param url The URL to be handled.
* @param candidatePage A NativePage to be reused if it matches the url, or null.
* @param tab The Tab that will show the page.
* @param pdfInfo Information of the pdf, or null if not pdf.
* @param browserControlsManager Manages the browser controls.
* @param tabModelSelector The current {@link TabModelSelector}.
* @param activity The current activity which owns the tab.
* @return A NativePage showing the specified url or null.
*/
public static NativePage createNativePageForCustomTab(
String url,
NativePage candidatePage,
Tab tab,
PdfInfo pdfInfo,
BrowserControlsManager browserControlsManager,
TabModelSelector tabModelSelector,
Activity activity) {
// Only pdf native page is supported on custom tab.
if (url == null || pdfInfo == null) {
return null;
}
NativePage page;
if (candidatePage != null && candidatePage.getUrl().equals(url)) {
page = candidatePage;
} else {
page =
buildPdfPage(
url, tab, pdfInfo, browserControlsManager, tabModelSelector, activity);
}
page.updateForUrl(url);
return page;
}
private static NativePage buildPdfPage(
String url,
Tab tab,
PdfInfo pdfInfo,
BrowserControlsManager browserControlsManager,
TabModelSelector tabModelSelector,
Activity activity) {
if (sTestPage instanceof PdfPage) {
return sTestPage;
}
return new PdfPage(
new TabShim(tab, browserControlsManager, tabModelSelector),
tab.getProfile(),
activity,
url,
pdfInfo,
activity.getString(R.string.pdf_transient_tab_title));
}
/** Simple implementation of NativePageHost backed by a {@link Tab} */
private static class TabShim implements NativePageHost {
private final Tab mTab;
private final BrowserControlsStateProvider mBrowserControlsStateProvider;
private final TabModelSelector mTabModelSelector;
public TabShim(
Tab tab,
BrowserControlsStateProvider browserControlsStateProvider,
TabModelSelector tabModelSelector) {
mTab = tab;
mBrowserControlsStateProvider = browserControlsStateProvider;
mTabModelSelector = tabModelSelector;
}
@Override
public Context getContext() {
return mTab.getContext();
}
@Override
public void loadUrl(LoadUrlParams urlParams, boolean incognito) {
if (incognito && !mTab.isIncognito()) {
mTabModelSelector.openNewTab(
urlParams,
TabLaunchType.FROM_LONGPRESS_FOREGROUND,
mTab,
/* incognito= */ true);
return;
}
mTab.loadUrl(urlParams);
}
@Override
public void openNewTab(LoadUrlParams urlParams) {
mTabModelSelector.openNewTab(
urlParams, TabLaunchType.FROM_LINK, mTab, mTab.isIncognito());
}
@Override
public int getParentId() {
return mTab.getParentId();
}
@Override
public boolean isVisible() {
return mTab == mTabModelSelector.getCurrentTab();
}
@Override
public DestroyableObservableSupplier<Rect> createDefaultMarginSupplier() {
return new BrowserControlsMarginSupplier(mBrowserControlsStateProvider);
}
}
/** Destroy and unhook objects at destruction. */
public void destroy() {
if (mNewTabPageUma != null) mNewTabPageUma.destroy();
}
public static void setPdfPageForTesting(PdfPage pdfPage) {
sTestPage = pdfPage;
}
}