blob: c71f3bdcd591a6578b522d67c32689e6060180fb [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.customtabs;
import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK;
import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSessionToken;
import org.chromium.base.Callback;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantFacade;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider.CustomTabsUiType;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController;
import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider;
import org.chromium.chrome.browser.customtabs.features.CustomTabNavigationBarController;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.metrics.LaunchCauseMetrics;
import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.page_info.ChromePageInfoControllerDelegate;
import org.chromium.chrome.browser.page_info.ChromePermissionParamsListBuilderDelegate;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.page_info.PageInfoController;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.util.ColorUtils;
/**
* The activity for custom tabs. It will be launched on top of a client's task.
*/
public class CustomTabActivity extends BaseCustomTabActivity {
private CustomTabsSessionToken mSession;
private final CustomTabsConnection mConnection = CustomTabsConnection.getInstance();
private CustomTabActivityTabProvider.Observer mTabChangeObserver =
new CustomTabActivityTabProvider.Observer() {
@Override
public void onInitialTabCreated(@NonNull Tab tab, int mode) {
resetPostMessageHandlersForCurrentSession();
}
@Override
public void onTabSwapped(@NonNull Tab tab) {
resetPostMessageHandlersForCurrentSession();
}
@Override
public void onAllTabsClosed() {
resetPostMessageHandlersForCurrentSession();
}
};
@Override
protected Drawable getBackgroundDrawable() {
int initialBackgroundColor = mIntentDataProvider.getInitialBackgroundColor();
if (mIntentDataProvider.isTrustedIntent() && initialBackgroundColor != Color.TRANSPARENT) {
return new ColorDrawable(initialBackgroundColor);
} else {
return super.getBackgroundDrawable();
}
}
@Override
public void performPreInflationStartup() {
super.performPreInflationStartup();
mTabProvider.addObserver(mTabChangeObserver);
// We might have missed an onInitialTabCreated event.
resetPostMessageHandlersForCurrentSession();
mSession = mIntentDataProvider.getSession();
// shouldHideOmniboxSuggestionsForCctVisits() can not be called immediately as it depends
// upon FeatureList, which has not been initialized yet.
getStartupTabPreloader().setTabCreatedCallback(new Callback<Tab>() {
@Override
public void onResult(Tab tab) {
CustomTabActivityNavigationController.applyExperimentsToNewTab(
tab, mIntentDataProvider);
}
});
CustomTabNavigationBarController.update(getWindow(), mIntentDataProvider, getResources());
}
@Override
public void performPostInflationStartup() {
super.performPostInflationStartup();
getStatusBarColorController().updateStatusBarColor();
// Properly attach tab's InfoBarContainer to the view hierarchy if the tab is already
// attached to a ChromeActivity, as the main tab might have been initialized prior to
// inflation.
if (mTabProvider.getTab() != null) {
ViewGroup bottomContainer = (ViewGroup) findViewById(R.id.bottom_container);
InfoBarContainer.get(mTabProvider.getTab()).setParentView(bottomContainer);
}
// Setting task title and icon to be null will preserve the client app's title and icon.
setTaskDescription(new ActivityManager.TaskDescription(
null, null, mIntentDataProvider.getToolbarColor()));
getComponent().resolveBottomBarDelegate().showBottomBarIfNecessary();
}
@Override
public void finishNativeInitialization() {
if (!mIntentDataProvider.isInfoPage()) FirstRunSignInProcessor.start(this);
mConnection.showSignInToastIfNecessary(mSession, getIntent());
new CustomTabTrustedCdnPublisherUrlVisibility(
getWindowAndroid(), getLifecycleDispatcher(), () -> {
String urlPackage = mConnection.getTrustedCdnPublisherUrlPackage();
return urlPackage != null
&& urlPackage.equals(
mConnection.getClientPackageNameForSession(mSession));
});
super.finishNativeInitialization();
// We start the Autofill Assistant after the call to super.finishNativeInitialization() as
// this will initialize the BottomSheet that is used to embed the Autofill Assistant bottom
// bar.
if (AutofillAssistantFacade.isAutofillAssistantEnabled(getInitialIntent())) {
AutofillAssistantFacade.start(this);
}
}
private void resetPostMessageHandlersForCurrentSession() {
Tab tab = mTabProvider.getTab();
WebContents webContents = tab == null ? null : tab.getWebContents();
mConnection.resetPostMessageHandlerForSession(
mIntentDataProvider.getSession(), webContents);
}
@Override
public void createContextualSearchTab(String searchUrl) {
if (getActivityTab() == null) return;
getActivityTab().loadUrl(new LoadUrlParams(searchUrl));
}
@Override
public String getPackageName() {
if (mShouldOverridePackage) return mIntentDataProvider.getClientPackageName();
return super.getPackageName();
}
@Override
public boolean onOptionsItemSelected(int itemId, @Nullable Bundle menuItemData) {
int menuIndex =
CustomTabAppMenuPropertiesDelegate.getIndexOfMenuItemFromBundle(menuItemData);
if (menuIndex >= 0) {
((CustomTabIntentDataProvider) mIntentDataProvider)
.clickMenuItemWithUrlAndTitle(this, menuIndex, getActivityTab().getUrlString(),
getActivityTab().getTitle());
RecordUserAction.record("CustomTabsMenuCustomMenuItem");
return true;
}
return super.onOptionsItemSelected(itemId, menuItemData);
}
@Override
public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) {
if (id == R.id.bookmark_this_page_id) {
addOrEditBookmark(getActivityTab());
RecordUserAction.record("MobileMenuAddToBookmarks");
return true;
} else if (id == R.id.open_in_browser_id) {
// Need to get tab before calling openCurrentUrlInBrowser or else it will be null.
Tab tab = mTabProvider.getTab();
if (mNavigationController.openCurrentUrlInBrowser(false)) {
RecordUserAction.record("CustomTabsMenuOpenInChrome");
WebContents webContents = tab == null ? null : tab.getWebContents();
if (tab != null) {
tab.setAddApi2TransitionToFutureNavigations(false);
tab.setHideFutureNavigations(false);
tab.setShouldBlockNewNotificationRequests(false);
}
mConnection.notifyOpenInBrowser(mSession, webContents);
}
return true;
} else if (id == R.id.info_menu_id) {
Tab tab = getTabModelSelector().getCurrentTab();
if (tab == null) return false;
WebContents webContents = tab.getWebContents();
PageInfoController.show(this, webContents, getToolbarManager().getContentPublisher(),
PageInfoController.OpenedFromSource.MENU,
new ChromePageInfoControllerDelegate(this, webContents,
this::getModalDialogManager,
/*offlinePageLoadUrlDelegate=*/
new OfflinePageUtils.TabOfflinePageLoadUrlDelegate(tab)),
new ChromePermissionParamsListBuilderDelegate());
return true;
}
return super.onMenuOrKeyboardAction(id, fromMenu);
}
@Override
protected BrowserServicesIntentDataProvider buildIntentDataProvider(
Intent intent, @CustomTabsIntent.ColorScheme int colorScheme) {
if (IncognitoCustomTabIntentDataProvider.isValidIncognitoIntent(intent)) {
return new IncognitoCustomTabIntentDataProvider(intent, this, colorScheme);
}
return new CustomTabIntentDataProvider(intent, this, colorScheme);
}
@Override
public boolean supportsAppMenu() {
// The media viewer has no default menu items, so if there are also no custom items, we
// should disable the menu altogether.
if (mIntentDataProvider.isMediaViewer() && mIntentDataProvider.getMenuTitles().isEmpty()) {
return false;
}
return super.supportsAppMenu();
}
/**
* Show the web page with CustomTabActivity, without any navigation control.
* Used in showing the terms of services page or help pages for Chrome.
* @param context The current activity context.
* @param url The url of the web page.
*/
public static void showInfoPage(Context context, String url) {
// TODO(xingliu): The title text will be the html document title, figure out if we want to
// use Chrome strings here as EmbedContentViewActivity does.
CustomTabsIntent customTabIntent =
new CustomTabsIntent.Builder()
.setShowTitle(true)
.setColorScheme(ColorUtils.inNightMode(context) ? COLOR_SCHEME_DARK
: COLOR_SCHEME_LIGHT)
.build();
customTabIntent.intent.setData(Uri.parse(url));
Intent intent = LaunchIntentDispatcher.createCustomTabActivityIntent(
context, customTabIntent.intent);
intent.setPackage(context.getPackageName());
intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, CustomTabsUiType.INFO_PAGE);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
if (!(context instanceof Activity)) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentHandler.addTrustedIntentExtras(intent);
context.startActivity(intent);
}
@Override
protected boolean requiresFirstRunToBeCompleted(Intent intent) {
// Custom Tabs can be used to open Chrome help pages before the ToS has been accepted.
if (CustomTabIntentDataProvider.isTrustedCustomTab(intent, mSession)
&& IntentUtils.safeGetIntExtra(intent, CustomTabIntentDataProvider.EXTRA_UI_TYPE,
CustomTabIntentDataProvider.CustomTabsUiType.DEFAULT)
== CustomTabIntentDataProvider.CustomTabsUiType.INFO_PAGE) {
return false;
}
return super.requiresFirstRunToBeCompleted(intent);
}
/**
* @return The package name of the Trusted Web Activity, if the activity is a TWA; null
* otherwise.
*/
@Nullable
public String getTwaPackage() {
return mTwaCoordinator == null ? null : mTwaCoordinator.getTwaPackage();
}
@Override
protected LaunchCauseMetrics createLaunchCauseMetrics() {
return new CustomTabLaunchCauseMetrics(this);
}
@VisibleForTesting
NightModeStateProvider getNightModeStateProviderForTesting() {
return super.getNightModeStateProvider();
}
}