| // Copyright 2019 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.features.toolbar; |
| |
| import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT; |
| |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.text.TextUtils; |
| import android.view.View; |
| |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.browser.customtabs.CustomTabsIntent; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.cc.input.BrowserControlsState; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.browser_controls.BrowserControlsVisibilityManager; |
| import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider; |
| import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; |
| import org.chromium.chrome.browser.customtabs.CloseButtonVisibilityManager; |
| import org.chromium.chrome.browser.customtabs.CustomButtonParams; |
| import org.chromium.chrome.browser.customtabs.CustomTabCompositorContentInitializer; |
| import org.chromium.chrome.browser.customtabs.CustomTabsConnection; |
| import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController; |
| import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabController; |
| import org.chromium.chrome.browser.customtabs.content.CustomTabActivityTabProvider; |
| import org.chromium.chrome.browser.dependency_injection.ActivityScope; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.toolbar.ToolbarManager; |
| import org.chromium.ui.util.TokenHolder; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| |
| import dagger.Lazy; |
| |
| /** |
| * Works with the toolbar in a Custom Tab. Encapsulates interactions with Chrome's toolbar-related |
| * classes such as {@link ToolbarManager} and {@link BrowserControlsVisibilityManager}. |
| * |
| * TODO(pshmakov): |
| * 1. Reduce the coupling between Custom Tab toolbar and Chrome's common code. In particular, |
| * ToolbarLayout has Custom Tab specific methods that throw unless we're in a Custom Tab - we need a |
| * better design. |
| * 2. Make toolbar lazy. E.g. in Trusted Web Activities we always start without toolbar - delay |
| * executing any initialization code and creating {@link ToolbarManager} until the toolbar needs |
| * to appear. |
| * 3. Refactor to MVC. |
| */ |
| @ActivityScope |
| public class CustomTabToolbarCoordinator { |
| private final BrowserServicesIntentDataProvider mIntentDataProvider; |
| private final CustomTabActivityTabProvider mTabProvider; |
| private final CustomTabsConnection mConnection; |
| private final Activity mActivity; |
| private final Context mAppContext; |
| private final CustomTabActivityTabController mTabController; |
| private final Lazy<BrowserControlsVisibilityManager> mBrowserControlsVisibilityManager; |
| private final CustomTabActivityNavigationController mNavigationController; |
| private final CloseButtonVisibilityManager mCloseButtonVisibilityManager; |
| private final CustomTabBrowserControlsVisibilityDelegate mVisibilityDelegate; |
| private final CustomTabToolbarColorController mToolbarColorController; |
| |
| @Nullable |
| private ToolbarManager mToolbarManager; |
| |
| private int mControlsHidingToken = TokenHolder.INVALID_TOKEN; |
| private boolean mInitializedToolbarWithNative; |
| private PendingIntent.OnFinished mCustomButtonClickOnFinished; |
| |
| private static final String TAG = "CustomTabToolbarCoor"; |
| |
| @Inject |
| public CustomTabToolbarCoordinator(BrowserServicesIntentDataProvider intentDataProvider, |
| CustomTabActivityTabProvider tabProvider, CustomTabsConnection connection, |
| Activity activity, @Named(APP_CONTEXT) Context appContext, |
| CustomTabActivityTabController tabController, |
| Lazy<BrowserControlsVisibilityManager> controlsVisiblityManager, |
| CustomTabActivityNavigationController navigationController, |
| CloseButtonVisibilityManager closeButtonVisibilityManager, |
| CustomTabBrowserControlsVisibilityDelegate visibilityDelegate, |
| CustomTabCompositorContentInitializer compositorContentInitializer, |
| CustomTabToolbarColorController toolbarColorController) { |
| mIntentDataProvider = intentDataProvider; |
| mTabProvider = tabProvider; |
| mConnection = connection; |
| mActivity = activity; |
| mAppContext = appContext; |
| mTabController = tabController; |
| mBrowserControlsVisibilityManager = controlsVisiblityManager; |
| mNavigationController = navigationController; |
| mCloseButtonVisibilityManager = closeButtonVisibilityManager; |
| mVisibilityDelegate = visibilityDelegate; |
| mToolbarColorController = toolbarColorController; |
| |
| compositorContentInitializer.addCallback(this::onCompositorContentInitialized); |
| } |
| |
| /** |
| * Notifies the navigation controller that the ToolbarManager has been created and is ready for |
| * use. ToolbarManager isn't passed directly to the constructor because it's not guaranteed to |
| * be initialized yet. |
| */ |
| public void onToolbarInitialized(ToolbarManager manager) { |
| assert manager != null : "Toolbar manager not initialized"; |
| mToolbarManager = manager; |
| mToolbarColorController.onToolbarInitialized(manager); |
| mCloseButtonVisibilityManager.onToolbarInitialized(manager); |
| |
| manager.setShowTitle( |
| mIntentDataProvider.getTitleVisibilityState() == CustomTabsIntent.SHOW_PAGE_TITLE); |
| if (mConnection.shouldHideDomainForSession(mIntentDataProvider.getSession())) { |
| manager.setUrlBarHidden(true); |
| } |
| if (mIntentDataProvider.isMediaViewer()) { |
| manager.setToolbarShadowVisibility(View.GONE); |
| } |
| showCustomButtonsOnToolbar(); |
| CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar); |
| toolbar.setIncognitoIconHidden(mIntentDataProvider.shouldHideIncognitoIconOnToolbarInCct()); |
| } |
| |
| /** |
| * Configures the custom button on toolbar. Does nothing if invalid data is provided by clients. |
| */ |
| private void showCustomButtonsOnToolbar() { |
| for (CustomButtonParams params : mIntentDataProvider.getCustomButtonsOnToolbar()) { |
| View.OnClickListener onClickListener = v -> onCustomButtonClick(params); |
| mToolbarManager.addCustomActionButton( |
| params.getIcon(mActivity), params.getDescription(), onClickListener); |
| } |
| } |
| |
| private void onCustomButtonClick(CustomButtonParams params) { |
| Tab tab = mTabProvider.getTab(); |
| if (tab == null) return; |
| |
| sendButtonPendingIntentWithUrlAndTitle(params, tab.getUrlString(), tab.getTitle()); |
| |
| RecordUserAction.record("CustomTabsCustomActionButtonClick"); |
| Resources resources = mActivity.getResources(); |
| if (mIntentDataProvider.shouldEnableEmbeddedMediaExperience() |
| && TextUtils.equals(params.getDescription(), resources.getString(R.string.share))) { |
| RecordUserAction.record("CustomTabsCustomActionButtonClick.DownloadsUI.Share"); |
| } |
| } |
| |
| /** |
| * Sends the pending intent for the custom button on the toolbar with the given {@code params}, |
| * with the given {@code url} as data. |
| * @param params The parameters for the custom button. |
| * @param url The URL to attach as additional data to the {@link PendingIntent}. |
| * @param title The title to attach as additional data to the {@link PendingIntent}. |
| */ |
| private void sendButtonPendingIntentWithUrlAndTitle( |
| CustomButtonParams params, String url, String title) { |
| Intent addedIntent = new Intent(); |
| addedIntent.setData(Uri.parse(url)); |
| addedIntent.putExtra(Intent.EXTRA_SUBJECT, title); |
| try { |
| params.getPendingIntent().send( |
| mAppContext, 0, addedIntent, mCustomButtonClickOnFinished, null); |
| } catch (PendingIntent.CanceledException e) { |
| Log.e(TAG, "CanceledException while sending pending intent in custom tab"); |
| } |
| } |
| |
| private void onCompositorContentInitialized(LayoutManagerImpl layoutDriver) { |
| mToolbarManager.initializeWithNative( |
| layoutDriver, null, null, null, v -> onCloseButtonClick(), null); |
| mInitializedToolbarWithNative = true; |
| } |
| |
| private void onCloseButtonClick() { |
| RecordUserAction.record("CustomTabs.CloseButtonClicked"); |
| if (mIntentDataProvider.shouldEnableEmbeddedMediaExperience()) { |
| RecordUserAction.record("CustomTabs.CloseButtonClicked.DownloadsUI"); |
| } |
| |
| mNavigationController.navigateOnClose(); |
| } |
| |
| public void setBrowserControlsState(@BrowserControlsState int controlsState) { |
| mVisibilityDelegate.setControlsState(controlsState); |
| if (controlsState == BrowserControlsState.HIDDEN) { |
| mControlsHidingToken = |
| mBrowserControlsVisibilityManager.get().hideAndroidControlsAndClearOldToken( |
| mControlsHidingToken); |
| } else { |
| mBrowserControlsVisibilityManager.get().releaseAndroidControlsHidingToken( |
| mControlsHidingToken); |
| } |
| } |
| |
| /** |
| * Shows toolbar temporarily, for a few seconds. |
| */ |
| public void showToolbarTemporarily() { |
| mBrowserControlsVisibilityManager.get() |
| .getBrowserVisibilityDelegate() |
| .showControlsTransient(); |
| } |
| |
| /** |
| * Updates a custom button with a new icon and description. Provided {@link CustomButtonParams} |
| * must have the updated data. |
| * Returns whether has succeeded. |
| */ |
| public boolean updateCustomButton(CustomButtonParams params) { |
| if (!params.doesIconFitToolbar(mActivity)) { |
| return false; |
| } |
| int index = mIntentDataProvider.getCustomToolbarButtonIndexForId(params.getId()); |
| if (index == -1) { |
| assert false; |
| return false; |
| } |
| if (mToolbarManager == null) { |
| return false; |
| } |
| |
| mToolbarManager.updateCustomActionButton( |
| index, params.getIcon(mActivity), params.getDescription()); |
| return true; |
| } |
| |
| /** |
| * Returns whether the toolbar has been fully initialized |
| * ({@link ToolbarManager#initializeWithNative}). |
| */ |
| public boolean toolbarIsInitialized() { |
| return mInitializedToolbarWithNative; |
| } |
| |
| /** |
| * Set the callback object for the {@link PendingIntent} which is sent by the custom buttons. |
| */ |
| @VisibleForTesting |
| public void setCustomButtonPendingIntentOnFinishedForTesting( |
| PendingIntent.OnFinished onFinished) { |
| mCustomButtonClickOnFinished = onFinished; |
| } |
| } |