| // 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 android.app.PendingIntent; |
| import android.app.PendingIntent.CanceledException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.Nullable; |
| import android.support.customtabs.CustomTabsIntent; |
| import android.support.customtabs.CustomTabsSessionToken; |
| import android.support.customtabs.TrustedWebUtils; |
| import android.text.TextUtils; |
| import android.util.Pair; |
| import android.view.View; |
| import android.widget.RemoteViews; |
| |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.Log; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.ChromeApplication; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.ChromeSwitches; |
| import org.chromium.chrome.browser.ChromeVersionInfo; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.UrlConstants; |
| import org.chromium.chrome.browser.browserservices.BrowserSessionDataProvider; |
| import org.chromium.chrome.browser.customtabs.dynamicmodule.ModuleMetrics; |
| import org.chromium.chrome.browser.externalauth.ExternalAuthUtils; |
| import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; |
| import org.chromium.chrome.browser.util.ColorUtils; |
| import org.chromium.chrome.browser.util.IntentUtils; |
| import org.chromium.chrome.browser.widget.TintedDrawable; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| /** |
| * A model class that parses the incoming intent for Custom Tabs specific customization data. |
| */ |
| public class CustomTabIntentDataProvider extends BrowserSessionDataProvider { |
| private static final String TAG = "CustomTabIntentData"; |
| |
| // The type of UI for Custom Tab to use. |
| @IntDef({CustomTabsUiType.DEFAULT, CustomTabsUiType.MEDIA_VIEWER, |
| CustomTabsUiType.PAYMENT_REQUEST, CustomTabsUiType.INFO_PAGE, |
| CustomTabsUiType.READER_MODE, CustomTabsUiType.MINIMAL_UI_WEBAPP, |
| CustomTabsUiType.OFFLINE_PAGE}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface CustomTabsUiType { |
| int DEFAULT = 0; |
| int MEDIA_VIEWER = 1; |
| int PAYMENT_REQUEST = 2; |
| int INFO_PAGE = 3; |
| int READER_MODE = 4; |
| int MINIMAL_UI_WEBAPP = 5; |
| int OFFLINE_PAGE = 6; |
| } |
| |
| @IntDef({LaunchSourceType.OTHER, LaunchSourceType.WEBAPP, LaunchSourceType.WEBAPK, |
| LaunchSourceType.MEDIA_LAUNCHER_ACTIVITY}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface LaunchSourceType { |
| int OTHER = -1; |
| int WEBAPP = 0; |
| int WEBAPK = 1; |
| int MEDIA_LAUNCHER_ACTIVITY = 3; |
| } |
| |
| /** |
| * Extra that indicates whether or not the Custom Tab is being launched by an Intent fired by |
| * Chrome itself. |
| */ |
| public static final String EXTRA_IS_OPENED_BY_CHROME = |
| "org.chromium.chrome.browser.customtabs.IS_OPENED_BY_CHROME"; |
| |
| /** URL that should be loaded in place of the URL passed along in the data. */ |
| public static final String EXTRA_MEDIA_VIEWER_URL = |
| "org.chromium.chrome.browser.customtabs.MEDIA_VIEWER_URL"; |
| |
| /** Extra that enables embedded media experience. */ |
| public static final String EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE = |
| "org.chromium.chrome.browser.customtabs.EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE"; |
| |
| /** Indicates the type of UI Custom Tab should use. */ |
| public static final String EXTRA_UI_TYPE = |
| "org.chromium.chrome.browser.customtabs.EXTRA_UI_TYPE"; |
| |
| /** Extra that defines the initial background color (RGB color stored as an integer). */ |
| public static final String EXTRA_INITIAL_BACKGROUND_COLOR = |
| "org.chromium.chrome.browser.customtabs.EXTRA_INITIAL_BACKGROUND_COLOR"; |
| |
| /** Extra that enables the client to disable the star button in menu. */ |
| public static final String EXTRA_DISABLE_STAR_BUTTON = |
| "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON"; |
| |
| /** Extra that enables the client to disable the download button in menu. */ |
| public static final String EXTRA_DISABLE_DOWNLOAD_BUTTON = |
| "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON"; |
| |
| /** Extra that indicates whether the client is a WebAPK. */ |
| public static final String EXTRA_IS_OPENED_BY_WEBAPK = |
| "org.chromium.chrome.browser.customtabs.EXTRA_IS_OPENED_BY_WEBAPK"; |
| |
| /** |
| * Indicates the source where the Custom Tab is launched. This is only used for |
| * WebApp/WebAPK/TrustedWebActivity. The value is defined as |
| * {@link WebappActivity.ActivityType#WebappActivity}. |
| */ |
| public static final String EXTRA_BROWSER_LAUNCH_SOURCE = |
| "org.chromium.chrome.browser.customtabs.EXTRA_BROWSER_LAUNCH_SOURCE"; |
| |
| // TODO(yusufo): Move this to CustomTabsIntent. |
| /** Signals custom tabs to favor sending initial urls to external handler apps if possible. */ |
| public static final String EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER = |
| "android.support.customtabs.extra.SEND_TO_EXTERNAL_HANDLER"; |
| |
| /** Key for the intent extra used to define an array list of module managed hosts. */ |
| @VisibleForTesting |
| public static final String EXTRA_MODULE_MANAGED_HOST_LIST = |
| "org.chromium.chrome.browser.customtabs.EXTRA_MODULE_MANAGED_HOST_LIST"; |
| |
| /** Extra that defines the module managed URLs regex. */ |
| @VisibleForTesting |
| public static final String EXTRA_MODULE_MANAGED_URLS_REGEX = |
| "org.chromium.chrome.browser.customtabs.EXTRA_MODULE_MANAGED_URLS_REGEX"; |
| |
| /** The APK package to load the module from. */ |
| @VisibleForTesting |
| public static final String EXTRA_MODULE_PACKAGE_NAME = |
| "org.chromium.chrome.browser.customtabs.EXTRA_MODULE_PACKAGE_NAME"; |
| |
| /** The class name of the module entry point. */ |
| @VisibleForTesting |
| public static final String EXTRA_MODULE_CLASS_NAME = |
| "org.chromium.chrome.browser.customtabs.EXTRA_MODULE_CLASS_NAME"; |
| |
| /** Extra that indicates whether to hide the CCT header on module managed URLs. */ |
| @VisibleForTesting |
| public static final String EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS = |
| "org.chromium.chrome.browser.customtabs.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS"; |
| |
| private static final int MAX_CUSTOM_MENU_ITEMS = 5; |
| |
| private static final int MAX_CUSTOM_TOOLBAR_ITEMS = 2; |
| |
| private static final String FIRST_PARTY_PITFALL_MSG = |
| "The intent contains a non-default UI type, but it is not from a first-party app. " |
| + "To make locally-built Chrome a first-party app, sign with release-test " |
| + "signing keys and run on userdebug devices. See use_signing_keys GN arg."; |
| |
| private final Intent mIntent; |
| |
| private final int mUiType; |
| private final int mTitleVisibilityState; |
| private final String mMediaViewerUrl; |
| private final boolean mEnableEmbeddedMediaExperience; |
| private final boolean mIsFromMediaLauncherActivity; |
| private final int mInitialBackgroundColor; |
| private final boolean mDisableStar; |
| private final boolean mDisableDownload; |
| private final boolean mIsOpenedByWebApk; |
| private final boolean mIsTrustedWebActivity; |
| @Nullable |
| private final ComponentName mModuleComponentName; |
| @Nullable |
| private final List<String> mModuleManagedHosts; |
| @Nullable |
| private final Pattern mModuleManagedUrlsPattern; |
| private final boolean mHideCctHeaderOnModuleManagedUrls; |
| private final boolean mIsIncognito; |
| @Nullable |
| private String mUrlToLoad; |
| |
| private int mToolbarColor; |
| private int mBottomBarColor; |
| private boolean mEnableUrlBarHiding; |
| private List<CustomButtonParams> mCustomButtonParams; |
| private Drawable mCloseButtonIcon; |
| private List<Pair<String, PendingIntent>> mMenuEntries = new ArrayList<>(); |
| private boolean mShowShareItem; |
| private List<CustomButtonParams> mToolbarButtons = new ArrayList<>(1); |
| private List<CustomButtonParams> mBottombarButtons = new ArrayList<>(2); |
| private RemoteViews mRemoteViews; |
| private int[] mClickableViewIds; |
| private PendingIntent mRemoteViewsPendingIntent; |
| // OnFinished listener for PendingIntents. Used for testing only. |
| private PendingIntent.OnFinished mOnFinished; |
| |
| /** Whether this CustomTabActivity was explicitly started by another Chrome Activity. */ |
| private final boolean mIsOpenedByChrome; |
| |
| /** |
| * Add extras to customize menu items for opening payment request UI custom tab from Chrome. |
| */ |
| public static void addPaymentRequestUIExtras(Intent intent) { |
| intent.putExtra(EXTRA_UI_TYPE, CustomTabsUiType.PAYMENT_REQUEST); |
| intent.putExtra(EXTRA_IS_OPENED_BY_CHROME, true); |
| IntentHandler.addTrustedIntentExtras(intent); |
| } |
| |
| /** |
| * Add extras to customize menu items for opening Reader Mode UI custom tab from Chrome. |
| */ |
| public static void addReaderModeUIExtras(Intent intent) { |
| intent.putExtra(EXTRA_UI_TYPE, CustomTabsUiType.READER_MODE); |
| intent.putExtra(EXTRA_IS_OPENED_BY_CHROME, true); |
| IntentHandler.addTrustedIntentExtras(intent); |
| } |
| |
| /** |
| * Constructs a {@link CustomTabIntentDataProvider}. |
| */ |
| public CustomTabIntentDataProvider(Intent intent, Context context) { |
| super(intent); |
| if (intent == null) assert false; |
| |
| mIntent = intent; |
| mIsOpenedByChrome = IntentUtils.safeGetBooleanExtra( |
| intent, EXTRA_IS_OPENED_BY_CHROME, false); |
| |
| final int requestedUiType = |
| IntentUtils.safeGetIntExtra(intent, EXTRA_UI_TYPE, CustomTabsUiType.DEFAULT); |
| mUiType = verifiedUiType(requestedUiType); |
| |
| mIsIncognito = isIncognitoForPaymentsFlow(intent) || isValidExternalIncognitoIntent(intent); |
| |
| retrieveCustomButtons(intent, context); |
| retrieveToolbarColor(intent, context); |
| retrieveBottomBarColor(intent); |
| mInitialBackgroundColor = retrieveInitialBackgroundColor(intent); |
| |
| mEnableUrlBarHiding = IntentUtils.safeGetBooleanExtra( |
| intent, CustomTabsIntent.EXTRA_ENABLE_URLBAR_HIDING, true); |
| |
| Bitmap bitmap = IntentUtils.safeGetParcelableExtra( |
| intent, CustomTabsIntent.EXTRA_CLOSE_BUTTON_ICON); |
| if (bitmap != null && !checkCloseButtonSize(context, bitmap)) { |
| IntentUtils.safeRemoveExtra(intent, CustomTabsIntent.EXTRA_CLOSE_BUTTON_ICON); |
| bitmap.recycle(); |
| bitmap = null; |
| } |
| if (bitmap == null) { |
| mCloseButtonIcon = |
| TintedDrawable.constructTintedDrawable(context, R.drawable.btn_close); |
| } else { |
| mCloseButtonIcon = new BitmapDrawable(context.getResources(), bitmap); |
| } |
| |
| List<Bundle> menuItems = |
| IntentUtils.getParcelableArrayListExtra(intent, CustomTabsIntent.EXTRA_MENU_ITEMS); |
| if (menuItems != null) { |
| for (int i = 0; i < Math.min(MAX_CUSTOM_MENU_ITEMS, menuItems.size()); i++) { |
| Bundle bundle = menuItems.get(i); |
| String title = |
| IntentUtils.safeGetString(bundle, CustomTabsIntent.KEY_MENU_ITEM_TITLE); |
| PendingIntent pendingIntent = |
| IntentUtils.safeGetParcelable(bundle, CustomTabsIntent.KEY_PENDING_INTENT); |
| if (TextUtils.isEmpty(title) || pendingIntent == null) continue; |
| mMenuEntries.add(new Pair<String, PendingIntent>(title, pendingIntent)); |
| } |
| } |
| |
| mIsTrustedWebActivity = IntentUtils.safeGetBooleanExtra( |
| intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false); |
| mTitleVisibilityState = IntentUtils.safeGetIntExtra( |
| intent, CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.NO_TITLE); |
| mShowShareItem = IntentUtils.safeGetBooleanExtra(intent, |
| CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, |
| mIsOpenedByChrome && mUiType == CustomTabsUiType.DEFAULT); |
| mRemoteViews = |
| IntentUtils.safeGetParcelableExtra(intent, CustomTabsIntent.EXTRA_REMOTEVIEWS); |
| mClickableViewIds = IntentUtils.safeGetIntArrayExtra( |
| intent, CustomTabsIntent.EXTRA_REMOTEVIEWS_VIEW_IDS); |
| mRemoteViewsPendingIntent = IntentUtils.safeGetParcelableExtra( |
| intent, CustomTabsIntent.EXTRA_REMOTEVIEWS_PENDINGINTENT); |
| mMediaViewerUrl = isMediaViewer() |
| ? IntentUtils.safeGetStringExtra(intent, EXTRA_MEDIA_VIEWER_URL) |
| : null; |
| mEnableEmbeddedMediaExperience = isTrustedIntent() |
| && IntentUtils.safeGetBooleanExtra( |
| intent, EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE, false); |
| mIsFromMediaLauncherActivity = isTrustedIntent() |
| && (IntentUtils.safeGetIntExtra( |
| intent, EXTRA_BROWSER_LAUNCH_SOURCE, LaunchSourceType.OTHER) |
| == LaunchSourceType.MEDIA_LAUNCHER_ACTIVITY); |
| mDisableStar = IntentUtils.safeGetBooleanExtra(intent, EXTRA_DISABLE_STAR_BUTTON, false); |
| mDisableDownload = |
| IntentUtils.safeGetBooleanExtra(intent, EXTRA_DISABLE_DOWNLOAD_BUTTON, false); |
| mIsOpenedByWebApk = |
| IntentUtils.safeGetBooleanExtra(intent, EXTRA_IS_OPENED_BY_WEBAPK, false); |
| |
| String modulePackageName = |
| IntentUtils.safeGetStringExtra(intent, EXTRA_MODULE_PACKAGE_NAME); |
| String moduleClassName = IntentUtils.safeGetStringExtra(intent, EXTRA_MODULE_CLASS_NAME); |
| if (modulePackageName != null && moduleClassName != null) { |
| mModuleComponentName = new ComponentName(modulePackageName, moduleClassName); |
| mModuleManagedHosts = |
| IntentUtils.safeGetStringArrayListExtra(intent, EXTRA_MODULE_MANAGED_HOST_LIST); |
| String moduleManagedUrlsRegex = |
| IntentUtils.safeGetStringExtra(intent, EXTRA_MODULE_MANAGED_URLS_REGEX); |
| mModuleManagedUrlsPattern = (moduleManagedUrlsRegex != null) |
| ? Pattern.compile(moduleManagedUrlsRegex) |
| : null; |
| mHideCctHeaderOnModuleManagedUrls = IntentUtils.safeGetBooleanExtra( |
| intent, EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, false); |
| } else { |
| mModuleComponentName = null; |
| mModuleManagedHosts = null; |
| mModuleManagedUrlsPattern = null; |
| mHideCctHeaderOnModuleManagedUrls = false; |
| } |
| } |
| |
| private boolean isIncognitoForPaymentsFlow(Intent intent) { |
| return incognitoRequested(intent) && isTrustedIntent() && isOpenedByChrome() |
| && isForPaymentRequest(); |
| } |
| |
| public static boolean isValidExternalIncognitoIntent(Intent intent) { |
| if (!CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_INCOGNITO_CUSTOM_TABS)) { |
| return false; |
| } |
| |
| if (!incognitoRequested(intent)) { |
| return false; |
| } |
| |
| return isVerifiedFirstPartyIntent(intent) |
| || CommandLine.getInstance().hasSwitch( |
| ChromeSwitches.ALLOW_INCOGNITO_CUSTOM_TABS_FROM_THIRD_PARTY); |
| } |
| |
| private static boolean incognitoRequested(Intent intent) { |
| return IntentUtils.safeGetBooleanExtra( |
| intent, IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false); |
| } |
| |
| private static boolean isVerifiedFirstPartyIntent(Intent intent) { |
| CustomTabsSessionToken sessionToken = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| String packageNameFromSession = |
| CustomTabsConnection.getInstance().getClientPackageNameForSession(sessionToken); |
| return !TextUtils.isEmpty(packageNameFromSession) |
| && ExternalAuthUtils.getInstance().isGoogleSigned(packageNameFromSession); |
| } |
| |
| /** |
| * Get the verified UI type, according to the intent extras, and whether the intent is trusted. |
| * @param requestedUiType requested UI type in the intent, unqualified |
| * @return verified UI type |
| */ |
| private int verifiedUiType(int requestedUiType) { |
| if (!isTrustedIntent()) { |
| if (ChromeVersionInfo.isLocalBuild()) Log.w(TAG, FIRST_PARTY_PITFALL_MSG); |
| return CustomTabsUiType.DEFAULT; |
| } |
| |
| if (requestedUiType == CustomTabsUiType.PAYMENT_REQUEST) { |
| if (!mIsOpenedByChrome) { |
| return CustomTabsUiType.DEFAULT; |
| } |
| } |
| |
| return requestedUiType; |
| } |
| |
| /** |
| * Gets custom buttons from the intent and updates {@link #mCustomButtonParams}, |
| * {@link #mBottombarButtons} and {@link #mToolbarButtons}. |
| */ |
| private void retrieveCustomButtons(Intent intent, Context context) { |
| assert mCustomButtonParams == null; |
| mCustomButtonParams = CustomButtonParams.fromIntent(context, intent); |
| for (CustomButtonParams params : mCustomButtonParams) { |
| if (!params.showOnToolbar()) { |
| mBottombarButtons.add(params); |
| } else if (mToolbarButtons.size() < getMaxCustomToolbarItems()) { |
| mToolbarButtons.add(params); |
| } else { |
| Log.w(TAG, "Only %d items are allowed in the toolbar", getMaxCustomToolbarItems()); |
| } |
| } |
| } |
| |
| private int getMaxCustomToolbarItems() { |
| if (!isTrustedIntent()) return 1; |
| |
| return MAX_CUSTOM_TOOLBAR_ITEMS; |
| } |
| |
| /** |
| * Processes the color passed from the client app and updates {@link #mToolbarColor}. |
| */ |
| private void retrieveToolbarColor(Intent intent, Context context) { |
| int defaultColor = ColorUtils.getDefaultThemeColor(context.getResources(), isIncognito()); |
| if (isIncognito()) { |
| mToolbarColor = defaultColor; |
| return; // Don't allow toolbar color customization for incognito tabs. |
| } |
| int color = IntentUtils.safeGetIntExtra( |
| intent, CustomTabsIntent.EXTRA_TOOLBAR_COLOR, defaultColor); |
| mToolbarColor = removeTransparencyFromColor(color); |
| } |
| |
| /** |
| * Must be called after calling {@link #retrieveToolbarColor(Intent, Context)}. |
| */ |
| private void retrieveBottomBarColor(Intent intent) { |
| if (isIncognito()) { |
| mBottomBarColor = mToolbarColor; |
| return; |
| } |
| int defaultColor = mToolbarColor; |
| int color = IntentUtils.safeGetIntExtra( |
| intent, CustomTabsIntent.EXTRA_SECONDARY_TOOLBAR_COLOR, defaultColor); |
| mBottomBarColor = removeTransparencyFromColor(color); |
| } |
| |
| /** |
| * Returns the color to initialize the background of the Custom Tab with. |
| * If no valid color is set, Color.TRANSPARENT is returned. |
| */ |
| private int retrieveInitialBackgroundColor(Intent intent) { |
| int defaultColor = Color.TRANSPARENT; |
| int color = |
| IntentUtils.safeGetIntExtra(intent, EXTRA_INITIAL_BACKGROUND_COLOR, defaultColor); |
| return color == Color.TRANSPARENT ? color : removeTransparencyFromColor(color); |
| } |
| |
| /** |
| * Removes the alpha channel of the given color and returns the processed value. |
| */ |
| private int removeTransparencyFromColor(int color) { |
| return color | 0xFF000000; |
| } |
| |
| private String resolveUrlToLoad(Intent intent) { |
| String url = IntentHandler.getUrlFromIntent(intent); |
| |
| // Intents fired for media viewers have an additional file:// URI passed along so that the |
| // tab can display the actual filename to the user when it is loaded. |
| if (isMediaViewer()) { |
| String mediaViewerUrl = getMediaViewerUrl(); |
| if (!TextUtils.isEmpty(mediaViewerUrl)) { |
| Uri mediaViewerUri = Uri.parse(mediaViewerUrl); |
| if (UrlConstants.FILE_SCHEME.equals(mediaViewerUri.getScheme())) { |
| url = mediaViewerUrl; |
| } |
| } |
| } |
| |
| if (!TextUtils.isEmpty(url)) { |
| url = DataReductionProxySettings.getInstance().maybeRewriteWebliteUrl(url); |
| } |
| |
| return url; |
| } |
| |
| /** |
| * @return The URL that should be used from this intent. If it is a WebLite url, it may be |
| * overridden if the Data Reduction Proxy is using Lo-Fi previews. |
| * Must be called only after native has loaded. |
| */ |
| public String getUrlToLoad() { |
| if (mUrlToLoad == null) { |
| mUrlToLoad = resolveUrlToLoad(mIntent); |
| } |
| return mUrlToLoad; |
| } |
| |
| /** |
| * @return Whether url bar hiding should be enabled in the custom tab. Default is false. |
| * It should be impossible to hide the url bar when the tab is opened for Payment Request. |
| */ |
| public boolean shouldEnableUrlBarHiding() { |
| return mEnableUrlBarHiding && !isForPaymentRequest(); |
| } |
| |
| /** |
| * @return The toolbar color specified in the intent. Will return the default theme color, if |
| * not set in the intent. |
| */ |
| public int getToolbarColor() { |
| return mToolbarColor; |
| } |
| |
| /** |
| * @return The drawable of the icon of close button shown in the custom tab toolbar. If the |
| * client app provides an icon in valid size, use this icon; else return the default |
| * drawable. |
| */ |
| public Drawable getCloseButtonDrawable() { |
| return mCloseButtonIcon; |
| } |
| |
| /** |
| * @return The title visibility state for the toolbar. |
| * Default is {@link CustomTabsIntent#NO_TITLE}. |
| */ |
| public int getTitleVisibilityState() { |
| return mTitleVisibilityState; |
| } |
| |
| /** |
| * @return Whether the default share item should be shown in the menu. |
| */ |
| public boolean shouldShowShareMenuItem() { |
| return mShowShareItem; |
| } |
| |
| /** |
| * @return The params for the custom buttons that show on the toolbar. |
| */ |
| public List<CustomButtonParams> getCustomButtonsOnToolbar() { |
| return mToolbarButtons; |
| } |
| |
| /** |
| * @return The list of params representing the buttons on the bottombar. |
| */ |
| public List<CustomButtonParams> getCustomButtonsOnBottombar() { |
| return mBottombarButtons; |
| } |
| |
| /** |
| * @return Whether the bottom bar should be shown. |
| */ |
| public boolean shouldShowBottomBar() { |
| return !mBottombarButtons.isEmpty() || mRemoteViews != null; |
| } |
| |
| /** |
| * @return The color of the bottom bar, or {@link #getToolbarColor()} if not specified. |
| */ |
| public int getBottomBarColor() { |
| return mBottomBarColor; |
| } |
| |
| /** |
| * @return The {@link RemoteViews} to show on the bottom bar, or null if the extra is not |
| * specified. |
| */ |
| public RemoteViews getBottomBarRemoteViews() { |
| return mRemoteViews; |
| } |
| |
| /** |
| * @return A array of {@link View} ids, of which the onClick event is handled by the custom tab. |
| */ |
| public int[] getClickableViewIDs() { |
| if (mClickableViewIds == null) return null; |
| return mClickableViewIds.clone(); |
| } |
| |
| /** |
| * @return The {@link PendingIntent} that is sent when the user clicks on the remote view. |
| */ |
| public PendingIntent getRemoteViewsPendingIntent() { |
| return mRemoteViewsPendingIntent; |
| } |
| |
| /** |
| * Gets params for all custom buttons, which is the combination of |
| * {@link #getCustomButtonsOnBottombar()} and {@link #getCustomButtonsOnToolbar()}. |
| */ |
| public List<CustomButtonParams> getAllCustomButtons() { |
| return mCustomButtonParams; |
| } |
| |
| /** |
| * Searches for the toolbar button with the given {@code id} and returns its index. |
| * @param id The ID of a toolbar button to search for. |
| * @return The index of the toolbar button with the given {@code id}, or -1 if no such button |
| * can be found. |
| */ |
| public int getCustomToolbarButtonIndexForId(int id) { |
| for (int i = 0; i < mToolbarButtons.size(); i++) { |
| if (mToolbarButtons.get(i).getId() == id) return i; |
| } |
| return -1; |
| } |
| |
| /** |
| * @return The {@link CustomButtonParams} (either on the toolbar or bottom bar) with the given |
| * {@code id}, or null if no such button can be found. |
| */ |
| public CustomButtonParams getButtonParamsForId(int id) { |
| for (CustomButtonParams params : mCustomButtonParams) { |
| // A custom button params will always carry an ID. If the client calls updateVisuals() |
| // without an id, we will assign the toolbar action button id to it. |
| if (id == params.getId()) return params; |
| } |
| return null; |
| } |
| |
| /** |
| * @return Titles of menu items that were passed from client app via intent. |
| */ |
| public List<String> getMenuTitles() { |
| ArrayList<String> list = new ArrayList<>(); |
| for (Pair<String, PendingIntent> pair : mMenuEntries) { |
| list.add(pair.first); |
| } |
| return list; |
| } |
| |
| /** |
| * Triggers the client-defined action when the user clicks a custom menu item. |
| * @param activity The {@link ChromeActivity} to use for sending the {@link PendingIntent}. |
| * @param menuIndex The index that the menu item is shown in the result of |
| * {@link #getMenuTitles()}. |
| * @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}. |
| */ |
| public void clickMenuItemWithUrlAndTitle( |
| ChromeActivity activity, int menuIndex, String url, String title) { |
| Intent addedIntent = new Intent(); |
| addedIntent.setData(Uri.parse(url)); |
| addedIntent.putExtra(Intent.EXTRA_SUBJECT, title); |
| try { |
| // Media viewers pass in PendingIntents that contain CHOOSER Intents. Setting the data |
| // in these cases prevents the Intent from firing correctly. |
| String menuTitle = mMenuEntries.get(menuIndex).first; |
| PendingIntent pendingIntent = mMenuEntries.get(menuIndex).second; |
| pendingIntent.send( |
| activity, 0, isMediaViewer() ? null : addedIntent, mOnFinished, null); |
| if (shouldEnableEmbeddedMediaExperience() |
| && TextUtils.equals(menuTitle, |
| activity.getString(R.string.download_manager_open_with))) { |
| RecordUserAction.record("CustomTabsMenuCustomMenuItem.DownloadsUI.OpenWith"); |
| } |
| } catch (CanceledException e) { |
| Log.e(TAG, "Custom tab in Chrome failed to send pending intent."); |
| } |
| } |
| |
| /** |
| * Sends the pending intent for the custom button on the toolbar with the given {@code params}, |
| * with the given {@code url} as data. |
| * @param context The context to use for sending the {@link PendingIntent}. |
| * @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}. |
| */ |
| public void sendButtonPendingIntentWithUrlAndTitle( |
| Context context, 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(context, 0, addedIntent, mOnFinished, null); |
| } catch (CanceledException e) { |
| Log.e(TAG, "CanceledException while sending pending intent in custom tab"); |
| } |
| } |
| |
| private boolean checkCloseButtonSize(Context context, Bitmap bitmap) { |
| int size = context.getResources().getDimensionPixelSize(R.dimen.toolbar_icon_height); |
| if (bitmap.getHeight() == size && bitmap.getWidth() == size) return true; |
| return false; |
| } |
| |
| /** |
| * Set the callback object for {@link PendingIntent}s that are sent in this class. For testing |
| * purpose only. |
| */ |
| @VisibleForTesting |
| void setPendingIntentOnFinishedForTesting(PendingIntent.OnFinished onFinished) { |
| mOnFinished = onFinished; |
| } |
| |
| /** |
| * @return See {@link #EXTRA_IS_OPENED_BY_CHROME}. |
| */ |
| boolean isOpenedByChrome() { |
| return mIsOpenedByChrome; |
| } |
| |
| /** |
| * @return See {@link #EXTRA_UI_TYPE}. |
| */ |
| boolean isMediaViewer() { |
| return mUiType == CustomTabsUiType.MEDIA_VIEWER; |
| } |
| |
| @CustomTabsUiType |
| int getUiType() { |
| return mUiType; |
| } |
| |
| /** |
| * @return See {@link #EXTRA_MEDIA_VIEWER_URL}. |
| */ |
| String getMediaViewerUrl() { |
| return mMediaViewerUrl; |
| } |
| |
| /** |
| * @return See {@link #EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE} |
| */ |
| boolean shouldEnableEmbeddedMediaExperience() { |
| return mEnableEmbeddedMediaExperience; |
| } |
| |
| /** |
| * @return See {@link #EXTRA_IS_FROM_MEDIA_LAUNCHER_ACTIVITY} |
| */ |
| boolean isFromMediaLauncherActivity() { |
| return mIsFromMediaLauncherActivity; |
| } |
| |
| /** |
| * @return If the Custom Tab is an info page. |
| * See {@link #EXTRA_UI_TYPE}. |
| */ |
| boolean isInfoPage() { |
| return mUiType == CustomTabsUiType.INFO_PAGE; |
| } |
| |
| /** |
| * See {@link #EXTRA_INITIAL_BACKGROUND_COLOR}. |
| * @return The color if it was specified in the Intent, Color.TRANSPARENT otherwise. |
| */ |
| int getInitialBackgroundColor() { |
| return mInitialBackgroundColor; |
| } |
| |
| /** |
| * @return Whether there should be a star button in the menu. |
| */ |
| boolean shouldShowStarButton() { |
| return !mDisableStar; |
| } |
| |
| /** |
| * @return Whether there should be a download button in the menu. |
| */ |
| boolean shouldShowDownloadButton() { |
| return !mDisableDownload; |
| } |
| |
| /** |
| * @return Whether the Custom Tab was opened from a WebAPK. |
| */ |
| boolean isOpenedByWebApk() { |
| return mIsOpenedByWebApk; |
| } |
| |
| /** |
| * @return Whether the Custom Tab is opened for payment request. |
| */ |
| boolean isForPaymentRequest() { |
| return mUiType == CustomTabsUiType.PAYMENT_REQUEST; |
| } |
| |
| /** |
| * @return Whether the custom Tab should be opened in incognito mode. |
| */ |
| boolean isIncognito() { |
| return mIsIncognito; |
| } |
| |
| /** |
| * @return Whether the Custom Tab should attempt to display a Trusted Web Activity. |
| * Will return false if native is not initialized. |
| * |
| * Trusted Web Activities require CustomTabsClient#warmup to have been called, meaning that |
| * native will have been initialized when the client is trying to use a TWA. |
| */ |
| boolean isTrustedWebActivity() { |
| if (!ChromeFeatureList.isInitialized()) return false; |
| if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TRUSTED_WEB_ACTIVITY)) return false; |
| |
| return mIsTrustedWebActivity; |
| } |
| |
| /** |
| * @return Whether the Custom Tab should attempt to load a dynamic module, i.e. |
| * if the feature is enabled, the package is provided and package is Google-signed. |
| * |
| * Will return false if native is not initialized. |
| */ |
| public boolean isDynamicModuleEnabled() { |
| if (!ChromeFeatureList.isInitialized()) return false; |
| |
| ComponentName componentName = getModuleComponentName(); |
| // Return early if no component name was provided. It's important to do this before checking |
| // the feature experiment group, to avoid entering users into the experiment that do not |
| // even receive the extras for using the feature. |
| if (componentName == null) return false; |
| |
| if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_MODULE)) { |
| Log.w(TAG, "The %s feature is disabled.", ChromeFeatureList.CCT_MODULE); |
| ModuleMetrics.recordLoadResult(ModuleMetrics.LoadResult.FEATURE_DISABLED); |
| return false; |
| } |
| |
| ExternalAuthUtils authUtils = ChromeApplication.getComponent().resolveExternalAuthUtils(); |
| if (!authUtils.isGoogleSigned(componentName.getPackageName())) { |
| Log.w(TAG, "The %s package is not Google-signed.", componentName.getPackageName()); |
| ModuleMetrics.recordLoadResult(ModuleMetrics.LoadResult.NOT_GOOGLE_SIGNED); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @return The component name of the module entry point, or null if not specified. |
| */ |
| @Nullable |
| public ComponentName getModuleComponentName() { |
| return mModuleComponentName; |
| } |
| |
| /** |
| * See {@link #EXTRA_MODULE_MANAGED_URLS_REGEX}. |
| * @return The pattern compiled from the regex that defines the module managed URLs, |
| * or null if not specified. |
| */ |
| @Nullable |
| public Pattern getExtraModuleManagedUrlsPattern() { |
| return mModuleManagedUrlsPattern; |
| } |
| |
| /** |
| * See {@link #EXTRA_MODULE_MANAGED_HOST_LIST}. |
| * @return The list of module managed hosts, or null if not specified. |
| */ |
| @Nullable |
| public List<String> getExtraModuleManagedHosts() { |
| return mModuleManagedHosts; |
| } |
| |
| /** |
| * @return the Intent this instance was created with. |
| */ |
| public Intent getIntent() { |
| return mIntent; |
| } |
| |
| /** |
| * @return Whether to hide CCT header on module managed URLs. |
| */ |
| public boolean shouldHideCctHeaderOnModuleManagedUrls() { |
| return mHideCctHeaderOnModuleManagedUrls; |
| } |
| } |