diff --git a/tools/under-control/src/RELEASE b/tools/under-control/src/RELEASE index 72041c5c..d6000583 100644 --- a/tools/under-control/src/RELEASE +++ b/tools/under-control/src/RELEASE @@ -1 +1 @@ -140.0.7339.81 +140.0.7339.128 diff --git a/tools/under-control/src/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/tools/under-control/src/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java new file mode 100755 index 00000000..74417f90 --- /dev/null +++ b/tools/under-control/src/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java @@ -0,0 +1,1794 @@ +// 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.customtabs; + +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_HEIGHT_FIXED; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_POSITION_DEFAULT; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_POSITION_END; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_DEFAULT; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_NONE; +import static androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_TOP; +import static androidx.browser.customtabs.CustomTabsIntent.CLOSE_BUTTON_POSITION_DEFAULT; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_SIDE_SHEET_POSITION; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_CLOSE_BUTTON_ENABLED; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_CLOSE_BUTTON_POSITION; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_WIDTH_PX; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_NETWORK; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE; +import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_CORNER_RADIUS_DP; +import static androidx.browser.trusted.LaunchHandlerClientMode.FOCUS_EXISTING; +import static androidx.browser.trusted.LaunchHandlerClientMode.NAVIGATE_EXISTING; +import static androidx.browser.trusted.LaunchHandlerClientMode.NAVIGATE_NEW; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Network; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Pair; +import android.widget.RemoteViews; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Px; +import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomContentAction; +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.browser.customtabs.CustomTabsIntent.ActivityHeightResizeBehavior; +import androidx.browser.customtabs.CustomTabsIntent.ActivitySideSheetDecorationType; +import androidx.browser.customtabs.CustomTabsIntent.ActivitySideSheetRoundedCornersPosition; +import androidx.browser.customtabs.CustomTabsIntent.CloseButtonPosition; +import androidx.browser.customtabs.CustomTabsIntent.OpenInBrowserState; +import androidx.browser.customtabs.CustomTabsSessionToken; +import androidx.browser.customtabs.ExperimentalCustomContentAction; +import androidx.browser.customtabs.ExperimentalOpenInBrowser; +import androidx.browser.customtabs.TrustedWebUtils; +import androidx.browser.trusted.FileHandlingData; +import androidx.browser.trusted.LaunchHandlerClientMode; +import androidx.browser.trusted.ScreenOrientation; +import androidx.browser.trusted.TrustedWebActivityDisplayMode; +import androidx.browser.trusted.TrustedWebActivityIntentBuilder; +import androidx.browser.trusted.sharing.ShareData; +import androidx.browser.trusted.sharing.ShareTarget; + +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.base.BuildInfo; +import org.chromium.base.IntentUtils; +import org.chromium.base.LocaleUtils; +import org.chromium.base.Log; +import org.chromium.base.ResettersForTesting; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.metrics.RecordUserAction; +import org.chromium.base.version_info.VersionInfo; +import org.chromium.blink.mojom.DisplayMode; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.IntentHandler; +import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider; +import org.chromium.chrome.browser.browserservices.intents.ColorProvider; +import org.chromium.chrome.browser.browserservices.intents.CustomButtonParams; +import org.chromium.chrome.browser.browserservices.intents.SessionHolder; +import org.chromium.chrome.browser.customtabs.CustomTabsFeatureUsage.CustomTabsFeature; +import org.chromium.chrome.browser.flags.ActivityType; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.share.ShareUtils; +import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant; +import org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarCoordinator; +import org.chromium.chrome.browser.ui.google_bottom_bar.proto.IntentParams.GoogleBottomBarIntentParams; +import org.chromium.chrome.browser.ui.web_app_header.WebAppHeaderUtils; +import org.chromium.components.browser_ui.widget.TintedDrawable; +import org.chromium.components.embedder_support.util.Origin; +import org.chromium.components.embedder_support.util.UrlConstants; +import org.chromium.device.mojom.ScreenOrientationLockType; +import org.chromium.net.NetId; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * A model class that parses the incoming intent for Custom Tabs specific customization data. + * + *

Lifecycle: is activity-scoped, i.e. one instance per CustomTabActivity instance. Must be + * re-created when color scheme changes, which happens automatically since color scheme change leads + * to activity re-creation. + */ +public class CustomTabIntentDataProvider extends BrowserServicesIntentDataProvider { + private static final String TAG = "CustomTabIntentData"; + + @IntDef({LaunchSourceType.OTHER, LaunchSourceType.MEDIA_LAUNCHER_ACTIVITY}) + @Retention(RetentionPolicy.SOURCE) + public @interface LaunchSourceType { + int OTHER = -1; + int MEDIA_LAUNCHER_ACTIVITY = 3; + } + + @IntDef({ + BackgroundInteractBehavior.DEFAULT, + BackgroundInteractBehavior.ON, + BackgroundInteractBehavior.OFF + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BackgroundInteractBehavior { + int DEFAULT = 0; + int ON = 1; + int OFF = 2; + + // Must be the last one. + int NUM_ENTRIES = 3; + } + + /** Extra used to keep the caller alive. Its value is an Intent. */ + public static final String EXTRA_KEEP_ALIVE = "android.support.customtabs.extra.KEEP_ALIVE"; + + public static final String ANIMATION_BUNDLE_PREFIX = "android:activity."; + public static final String BUNDLE_PACKAGE_NAME = ANIMATION_BUNDLE_PREFIX + "packageName"; + public static final String BUNDLE_ENTER_ANIMATION_RESOURCE = + ANIMATION_BUNDLE_PREFIX + "animEnterRes"; + public static final String BUNDLE_EXIT_ANIMATION_RESOURCE = + ANIMATION_BUNDLE_PREFIX + "animExitRes"; + + /** 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"; + + /** + * Indicates the source where the Custom Tab is launched. The value is defined as + * {@link LaunchSourceType}. + */ + public static final String EXTRA_BROWSER_LAUNCH_SOURCE = + "org.chromium.chrome.browser.customtabs.EXTRA_BROWSER_LAUNCH_SOURCE"; + + // Deprecated. Use CustomTabsIntent#EXTRA_TRANSLATE_LANGUAGE_TAG + /** + * Extra that, if set, specifies Translate UI should be triggered with + * specified target language. + */ + @Deprecated @VisibleForTesting + static final String EXTRA_TRANSLATE_LANGUAGE = + "androidx.browser.customtabs.extra.TRANSLATE_LANGUAGE"; + + /** + * Extra that, if set, specifies that the loaded page should be automatically translated once it + * loads with the specified target language. This overrides EXTRA_TRANSLATE_LANGUAGE. + */ + @VisibleForTesting + static final String EXTRA_AUTO_TRANSLATE_LANGUAGE = + "androidx.browser.customtabs.extra.AUTO_TRANSLATE_LANGUAGE"; + + public static final String EXTRA_OPEN_IN_BROWSER_STATE = + "androidx.browser.customtabs.extra.OPEN_IN_BROWSER_STATE"; + + @VisibleForTesting + static final String EXTRA_OPEN_IN_BROWSER_BUTTON_ALLOWED = + "androidx.browser.customtabs.extra.OPEN_IN_BROWSER_BUTTON_ALLOWED"; + + static final String EXTRA_CUSTOM_CONTENT_ACTIONS = + "androidx.browser.customtabs.extra.CUSTOM_CONTENT_ACTIONS"; + + @IntDef({ + CustomTabsButtonState.BUTTON_STATE_OFF, + CustomTabsButtonState.BUTTON_STATE_ON, + CustomTabsButtonState.BUTTON_STATE_DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CustomTabsButtonState { + int BUTTON_STATE_OFF = CustomTabsIntent.SHARE_STATE_OFF; + int BUTTON_STATE_ON = CustomTabsIntent.SHARE_STATE_ON; + int BUTTON_STATE_DEFAULT = CustomTabsIntent.SHARE_STATE_DEFAULT; + } + + private static final String EXTRA_TWA_DISCLOSURE_UI = + "androidx.browser.trusted.extra.DISCLOSURE_VERSION"; + + private static final int DEFAULT_BREAKPOINT_DP = 840; + + private static final int MAX_CUSTOM_MENU_ITEMS = 7; + + 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."; + + // Extra whose value is an array of ints that is supplied to + // SyntheticTrialRegistry::RegisterExternalExperiments(). + public static final String EXPERIMENT_IDS = + "org.chromium.chrome.browser.customtabs.AGA_EXPERIMENT_IDS"; + + // These Extra Intent parameters allow an Intent to enable or disable a set of Features. + // The set of Features that may be enabled or disabled is restricted by the code, + // and initially only two Features may be enabled together, or disabled together. + public static final String EXPERIMENTS_ENABLE = + "org.chromium.chrome.browser.customtabs.EXPERIMENTS_ENABLE"; + public static final String EXPERIMENTS_DISABLE = + "org.chromium.chrome.browser.customtabs.EXPERIMENTS_DISABLE"; + + /** + * Extra that, if set, makes the Custom Tab Activity's height to be x pixels, the Custom Tab + * will behave as a bottom sheet. x will be clamped between 50% and 100% of screen height. + * TODO(jinsukkim): Deprecate this. + */ + public static final String EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL_LEGACY = + "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_IN_PIXEL"; + + /** + * Extra that, if set, corresponds to the timestamp (in SystemClock.uptimeMillis()) when the TWA + * launcher Activity was created. Used to measure the full startup duration. + */ + public static final String EXTRA_TWA_STARTUP_UPTIME_MS = + "org.chromium.chrome.browser.customtabs.trusted.STARTUP_UPTIME_MILLIS"; + + /** Extra that, if set, corresponds to the integer version of android_browser_helper. */ + public static final String EXTRA_ANDROID_BROWSER_HELPER_VERSION = + "org.chromium.chrome.browser.ANDROID_BROWSER_HELPER_VERSION"; + + /** + * Extra that, if set, allows you to interact with the background app when a PCCT is launched. + * Note: Deprecated. Use {@link CustomTabsIntent#isBackgroundInteractionEnabled(Intent)}. + */ + @Deprecated + public static final String EXTRA_ENABLE_BACKGROUND_INTERACTION = + "androidx.browser.customtabs.extra.ENABLE_BACKGROUND_INTERACTION"; + + /** + * Extra that, if set, makes the toolbar's top corner radii to be x pixels. This will only have + * effect if the custom tab is behaving as a bottom sheet. Currently, this is capped at 16dp. + * TODO(jinsukkim): Deprecate this. + */ + public static final String EXTRA_TOOLBAR_CORNER_RADIUS_IN_PIXEL_LEGACY = + "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_IN_PIXEL"; + + private static final String DEFAULT_POLICY_USE_DENYLIST = "use-denylist"; + private static final String DEFAULT_POLICY_USE_ALLOWLIST = "use-allowlist"; + + /** + * Extra that specifies the {@link PendingIntent} to be sent when the user swipes up from the + * secondary (bottom) toolbar. + * + *

Use {@link CustomTabsIntent.Builder#setSecondaryToolbarSwipeUpGesture(PendingIntent)} or + * {@link CustomTabsIntent#EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_GESTURE} as this is deprecated. + */ + @Deprecated + public static final String EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION = + "androidx.browser.customtabs.extra.SECONDARY_TOOLBAR_SWIPE_UP_ACTION"; + + /** + * Allow user gestures on content area to be used not only for scrolling contents but also for + * resizing CCT. Used for Partial Custom Tab Bottom Sheet only. + */ + public static final String EXTRA_ACTIVITY_SCROLL_CONTENT_RESIZE = + "androidx.browser.customtabs.extra.ACTIVITY_SCROLL_CONTENT_RESIZE"; + + @IntDef({ + OpenInBrowserButtonState.OPEN_IN_BROWSER_STATE_OFF, + OpenInBrowserButtonState.OPEN_IN_BROWSER_STATE_ON, + OpenInBrowserButtonState.OPEN_IN_BROWSER_STATE_DEFAULT + }) + @Retention(RetentionPolicy.SOURCE) + @ExperimentalOpenInBrowser + public @interface OpenInBrowserButtonState { + int OPEN_IN_BROWSER_STATE_OFF = CustomTabsIntent.OPEN_IN_BROWSER_STATE_OFF; + int OPEN_IN_BROWSER_STATE_ON = CustomTabsIntent.OPEN_IN_BROWSER_STATE_ON; + int OPEN_IN_BROWSER_STATE_DEFAULT = CustomTabsIntent.OPEN_IN_BROWSER_STATE_DEFAULT; + } + + private final Intent mIntent; + private final SessionHolder mSession; + private final boolean mIsTrustedIntent; + private final Intent mKeepAliveServiceIntent; + private final Bundle mAnimationBundle; + + private final int mUiType; + private final int mTitleVisibilityState; + private final String mMediaViewerUrl; + private final boolean mEnableEmbeddedMediaExperience; + private final boolean mIsFromMediaLauncherActivity; + private final boolean mDisableStar; + private final boolean mDisableDownload; + private final @ActivityType int mActivityType; + @Nullable private final List mTrustedWebActivityAdditionalOrigins; + @Nullable private Set mAllTrustedWebActivityOrigins; + @Nullable private final TrustedWebActivityDisplayMode mTrustedWebActivityDisplayMode; + @Nullable private String mUrlToLoad; + + private final boolean mEnableUrlBarHiding; + private boolean mInteractWithBackground; + private List mCustomButtonParams; + private Drawable mCloseButtonIcon; + private final boolean mIsCloseButtonEnabled; + private final List> mMenuEntries = new ArrayList<>(); + private boolean mShowShareItemInMenu; + private final List mToolbarButtons = new ArrayList<>(1); + private final List mBottombarButtons = new ArrayList<>(2); + private final List mGoogleBottomBarButtons = new ArrayList<>(); + private final RemoteViews mRemoteViews; + @ActivitySideSheetDecorationType private final int mSideSheetDecorationType; + @ActivitySideSheetRoundedCornersPosition private final int mSideSheetRoundedCornersPosition; + private final int[] mClickableViewIds; + private final PendingIntent mRemoteViewsPendingIntent; + private final PendingIntent mSecondaryToolbarSwipeUpPendingIntent; + private PendingIntent.OnFinished mOnFinishedForTesting; + private @DisplayMode.EnumType int mResolvedDisplayMode = DisplayMode.UNDEFINED; + private final @OpenInBrowserState int mOpenInBrowserState; + private final int mShareState; + + /** Whether this CustomTabActivity was explicitly started by another Chrome Activity. */ + private final boolean mIsOpenedByChrome; + + /** ISO 639 language code */ + @Nullable private final String mTranslateLanguage; + + /** ISO 639 language code, overrides {@link mTranslateLanguage} if non-null. */ + @Nullable private final String mAutoTranslateLanguage; + + private final int mDefaultOrientation; + + @Nullable private final int[] mGsaExperimentIds; + + @NonNull private final ColorProvider mColorProvider; + + private final int mBreakPointDp; + private final @Px int mInitialActivityHeight; + private final @Px int mInitialActivityWidth; + private final @Px int mPartialTabToolbarCornerRadius; + + private final boolean mIsPartialCustomTabFixedHeight; + private final boolean mContentScrollMayResizeTab; + + /** + * {@link Network} to be bound when launching a custom tab or tabs that have been pre-created. + */ + @Nullable private final Network mNetwork; + + /** 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); + IntentUtils.addTrustedIntentExtras(intent); + } + + /** + * Evaluates whether the passed Intent and/or CustomTabsSessionToken are from a trusted source. + * Trusted in this case means from the app itself or via a first-party application. + * + * @param intent The Intent used to start the custom tabs activity, or null. + * @param session The connected session for the custom tabs activity, or null. + * @return True if the intent or session are trusted. + */ + public static boolean isTrustedCustomTab(Intent intent, SessionHolder session) { + if (IntentHandler.wasIntentSenderChrome(intent)) return true; + String packageName = getClientPackageNameFromSessionOrCallingActivity(intent, session); + return CustomTabsConnection.getInstance().isFirstParty(packageName); + } + + static @Nullable String getClientPackageNameFromSessionOrCallingActivity( + Intent intent, SessionHolder session) { + String packageNameFromSession = + CustomTabsConnection.getInstance().getClientPackageNameForSession(session); + if (!TextUtils.isEmpty(packageNameFromSession)) return packageNameFromSession; + + String packageNameFromIntent = + IntentUtils.safeGetStringExtra( + intent, IntentHandler.EXTRA_CALLING_ACTIVITY_PACKAGE); + if (!TextUtils.isEmpty(packageNameFromIntent)) return packageNameFromIntent; + + return null; + } + + public static void configureIntentForResizableCustomTab(Context context, Intent intent) { + SessionHolder session = SessionHolder.getSessionHolderFromIntent(intent); + boolean isTrustedCustomTab = isTrustedCustomTab(intent, session); + String packageName = getClientPackageNameFromSessionOrCallingActivity(intent, session); + @Px + int initialActivityHeight = + getInitialActivityHeight( + isTrustedCustomTab, + getInitialActivityHeightFromIntent(intent), + packageName); + @Px + int initialActivityWidth = + getInitialActivityWidth( + isTrustedCustomTab, getInitialActivityWidthFromIntent(intent), packageName); + if (initialActivityHeight <= 0 && initialActivityWidth <= 0) { + // fallback to normal Custom Tab. + return; + } + intent.setClassName(context, TranslucentCustomTabActivity.class.getName()); + // When scrolling up the web content, we don't want to hide the URL bar. + intent.putExtra(CustomTabsIntent.EXTRA_ENABLE_URLBAR_HIDING, false); + } + + private static @Px int getInitialActivityHeight( + boolean isTrustedIntent, @Px int initialActivityHeight, String packageName) { + boolean enabledDueToThirdParty = + ChromeFeatureList.sCctResizableForThirdParties.isEnabled() + && isAllowedThirdParty(packageName); + return (isTrustedIntent || enabledDueToThirdParty) ? initialActivityHeight : 0; + } + + private static @Px int getInitialActivityWidth( + boolean isTrustedIntent, @Px int initialActivityWidth, String packageName) { + boolean enabledDueToThirdParty = isAllowedThirdParty(packageName); + return (isTrustedIntent || enabledDueToThirdParty) ? initialActivityWidth : 0; + } + + /** Returns the initial activity height in px. */ + private static int getInitialActivityHeightFromIntent(Intent intent) { + int heightPx1 = + IntentUtils.safeGetIntExtra( + intent, + CustomTabIntentDataProvider.EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL_LEGACY, + 0); + if (heightPx1 > 0) return heightPx1; + int heightPx2 = IntentUtils.safeGetIntExtra(intent, EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 0); + return heightPx2 > 0 ? heightPx2 : 0; + } + + private static int getInitialActivityWidthFromIntent(Intent intent) { + int widthPx = IntentUtils.safeGetIntExtra(intent, EXTRA_INITIAL_ACTIVITY_WIDTH_PX, 0); + return widthPx > 0 ? widthPx : 0; + } + + private static int getActivityBreakPointFromIntent(Intent intent) { + int breakPointDp = + IntentUtils.safeGetIntExtra( + intent, EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP, DEFAULT_BREAKPOINT_DP); + return breakPointDp < 0 ? DEFAULT_BREAKPOINT_DP : breakPointDp; + } + + private static int getActivitySideSheetDecorationTypeFromIntent(Intent intent) { + int decorationType = CustomTabsIntent.getActivitySideSheetDecorationType(intent); + return decorationType == ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT + || decorationType < 0 + || decorationType > ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER + ? ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW + : decorationType; + } + + private static int getActivitySideSheetRoundedCornersPositionFromIntent(Intent intent) { + int roundedCornersPosition = + CustomTabsIntent.getActivitySideSheetRoundedCornersPosition(intent); + return roundedCornersPosition == ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_DEFAULT + || roundedCornersPosition < 0 + || roundedCornersPosition > ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_TOP + ? ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION_NONE + : roundedCornersPosition; + } + + private static boolean getIsCloseButtonEnabled(Intent intent, int uiType) { + return IntentUtils.safeGetBooleanExtra(intent, EXTRA_CLOSE_BUTTON_ENABLED, true) + && uiType != CustomTabsUiType.POPUP; + } + + /** + * Extracts the name that identifies the embedding app from the referrer. + * + * @return Host name as an id if the referrer is of a well-formed URI with app intent scheme. If + * not, just the whole referrer string. TODO(crbug.com/40234088): Move this to + * IntentHandler. + */ + static String getAppIdFromReferrer(Activity activity) { + String referrer = + CustomTabActivityLifecycleUmaTracker.getReferrerUriString(activity) + .toLowerCase(Locale.US); + if (TextUtils.isEmpty(referrer)) return ""; + + Uri uri = Uri.parse(referrer); + boolean isUrl = TextUtils.equals(UrlConstants.APP_INTENT_SCHEME, uri.getScheme()); + if (isUrl) { + String host = uri.getHost(); + if (!TextUtils.isEmpty(host)) return host; + } + return referrer; + } + + /** + * Constructs a {@link CustomTabIntentDataProvider}. + * + * @param intent The intent to launch the CCT. + * @param colorScheme The colorScheme parameter specifies which color scheme the Custom Tab + * should use. + * It can currently be either {@link CustomTabsIntent#COLOR_SCHEME_LIGHT} or + * {@link CustomTabsIntent#COLOR_SCHEME_DARK}. + * If Custom Tab was launched with {@link CustomTabsIntent#COLOR_SCHEME_SYSTEM}, colorScheme + * must reflect the current system setting. When the system setting changes, a new + * CustomTabIntentDataProvider object must be created. + */ + public CustomTabIntentDataProvider(Intent intent, Context context, int colorScheme) { + if (intent == null) assert false; + mIntent = intent; + + CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); + mSession = token != null ? new SessionHolder<>(token) : null; + mIsTrustedIntent = isTrustedCustomTab(intent, mSession); + + mAnimationBundle = + IntentUtils.safeGetBundleExtra( + intent, CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE); + + mKeepAliveServiceIntent = IntentUtils.safeGetParcelableExtra(intent, EXTRA_KEEP_ALIVE); + + mNetwork = IntentUtils.safeGetParcelableExtra(intent, EXTRA_NETWORK); + + mIsOpenedByChrome = IntentHandler.wasIntentSenderChrome(intent); + + final int requestedUiType = + IntentUtils.safeGetIntExtra(intent, EXTRA_UI_TYPE, CustomTabsUiType.DEFAULT); + mUiType = getCustomTabsUiType(requestedUiType); + + mColorProvider = new CustomTabColorProviderImpl(intent, context, colorScheme); + + retrieveCustomButtons(intent, context); + + mEnableUrlBarHiding = + IntentUtils.safeGetBooleanExtra( + intent, CustomTabsIntent.EXTRA_ENABLE_URLBAR_HIDING, true); + mContentScrollMayResizeTab = + IntentUtils.safeGetBooleanExtra( + intent, EXTRA_ACTIVITY_SCROLL_CONTENT_RESIZE, false); + + mIsCloseButtonEnabled = getIsCloseButtonEnabled(intent, mUiType); + if (mIsCloseButtonEnabled) { + // TODO(crbug.com/393437143): Potentially reuse the close button code from Auth Tab. + 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 TintedDrawable(context, bitmap); + } + } + + mOpenInBrowserState = + IntentUtils.safeGetIntExtra( + intent, + EXTRA_OPEN_IN_BROWSER_STATE, + OpenInBrowserButtonState.OPEN_IN_BROWSER_STATE_DEFAULT); + if (mUiType == CustomTabsUiType.POPUP) { + mShareState = CustomTabsIntent.SHARE_STATE_OFF; + } else { + boolean usingInteractiveOmnibox = + CustomTabsConnection.getInstance().shouldEnableOmniboxForIntent(this); + int defState = + usingInteractiveOmnibox + ? CustomTabsIntent.SHARE_STATE_OFF + : CustomTabsIntent.SHARE_STATE_DEFAULT; + mShareState = + IntentUtils.safeGetIntExtra( + intent, CustomTabsIntent.EXTRA_SHARE_STATE, defState); + } + + List menuItems = + IntentUtils.getParcelableArrayListExtra(intent, CustomTabsIntent.EXTRA_MENU_ITEMS); + + addOpenInBrowserOption(intent, context); + updateExtraMenuItems(menuItems); + // Disable CCT share options for automotive. See b/300292495. + if (ShareUtils.enableShareForAutomotive(true)) { + addShareOption(intent, context); + } + + boolean isTwa = + mSession != null + && IntentUtils.safeGetBooleanExtra( + intent, + TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, + false); + + mActivityType = + isTwa + ? ActivityType.TRUSTED_WEB_ACTIVITY + : isAuthTab() ? ActivityType.AUTH_TAB : ActivityType.CUSTOM_TAB; + mTrustedWebActivityAdditionalOrigins = + IntentUtils.safeGetStringArrayListExtra( + intent, TrustedWebActivityIntentBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS); + + // Do not fill in `mAllTrustedWebActivityOrigins` yet, because we cannot `getUrlToLoad()` + // until native is loaded. + + mTrustedWebActivityDisplayMode = resolveTwaDisplayMode(); + + int intentVisibilityState = + IntentUtils.safeGetIntExtra( + intent, + CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, + CustomTabsIntent.NO_TITLE); + mTitleVisibilityState = + BrowserServicesIntentDataProvider.customTabIntentTitleBarVisibility( + intentVisibilityState, isTwa); + + mRemoteViews = + IntentUtils.safeGetParcelableExtra(intent, CustomTabsIntent.EXTRA_REMOTEVIEWS); + mClickableViewIds = + IntentUtils.safeGetIntArrayExtra( + intent, CustomTabsIntent.EXTRA_REMOTEVIEWS_VIEW_IDS); + mRemoteViewsPendingIntent = + IntentUtils.safeGetParcelableExtra( + intent, CustomTabsIntent.EXTRA_REMOTEVIEWS_PENDINGINTENT); + mSecondaryToolbarSwipeUpPendingIntent = getSecondaryToolbarSwipeUpGesture(intent); + 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 = !CustomTabsIntent.isBookmarksButtonEnabled(intent); + mDisableDownload = !CustomTabsIntent.isDownloadButtonEnabled(intent); + + mTranslateLanguage = getTranslateLanguage(intent); + + mAutoTranslateLanguage = + IntentUtils.safeGetStringExtra(intent, EXTRA_AUTO_TRANSLATE_LANGUAGE); + + // Import the {@link ScreenOrientation}. + mDefaultOrientation = + convertOrientationType( + IntentUtils.safeGetIntExtra( + intent, + TrustedWebActivityIntentBuilder.EXTRA_SCREEN_ORIENTATION, + ScreenOrientation.DEFAULT)); + + mGsaExperimentIds = IntentUtils.safeGetIntArrayExtra(intent, EXPERIMENT_IDS); + + mBreakPointDp = getActivityBreakPointFromIntent(intent); + mInitialActivityHeight = getInitialActivityHeightFromIntent(intent); + mInitialActivityWidth = getInitialActivityWidthFromIntent(intent); + mPartialTabToolbarCornerRadius = getToolbarCornerRadiusFromIntent(context, intent); + // The default behavior is that the PCCT's height is resizable. + @ActivityHeightResizeBehavior + int activityHeightResizeBehavior = + IntentUtils.safeGetIntExtra( + intent, EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR, ACTIVITY_HEIGHT_DEFAULT); + mIsPartialCustomTabFixedHeight = activityHeightResizeBehavior == ACTIVITY_HEIGHT_FIXED; + + mInteractWithBackground = CustomTabsIntent.isBackgroundInteractionEnabled(intent); + if (IntentUtils.safeHasExtra(intent, EXTRA_ENABLE_BACKGROUND_INTERACTION)) { + @BackgroundInteractBehavior + int backgroundInteractBehavior = + IntentUtils.safeGetIntExtra( + intent, + EXTRA_ENABLE_BACKGROUND_INTERACTION, + BackgroundInteractBehavior.DEFAULT); + mInteractWithBackground = backgroundInteractBehavior != BackgroundInteractBehavior.OFF; + } + mSideSheetDecorationType = getActivitySideSheetDecorationTypeFromIntent(intent); + mSideSheetRoundedCornersPosition = + getActivitySideSheetRoundedCornersPositionFromIntent(intent); + + logCustomTabFeatures(intent, colorScheme); + String packageName = getClientPackageNameFromSessionOrCallingActivity(mIntent, mSession); + RecordHistogram.recordBooleanHistogram( + "CustomTabs.HasNonSpoofablePackageName", !TextUtils.isEmpty(packageName)); + } + + /** Returns the toolbar corner radius in px. */ + private static int getToolbarCornerRadiusFromIntent(Context context, Intent intent) { + int defaultRadius = + context.getResources() + .getDimensionPixelSize(R.dimen.custom_tabs_default_corner_radius); + int radiusPx = + IntentUtils.safeGetIntExtra( + intent, EXTRA_TOOLBAR_CORNER_RADIUS_IN_PIXEL_LEGACY, -1); + if (radiusPx >= 0) return radiusPx; + + int radiusDp = IntentUtils.safeGetIntExtra(intent, EXTRA_TOOLBAR_CORNER_RADIUS_DP, -1); + if (radiusDp >= 0) { + return Math.round(radiusDp * context.getResources().getDisplayMetrics().density); + } + + return defaultRadius; + } + + private static PendingIntent getSecondaryToolbarSwipeUpGesture(Intent intent) { + PendingIntent pendingIntent = CustomTabsIntent.getSecondaryToolbarSwipeUpGesture(intent); + if (pendingIntent == null) { + pendingIntent = + IntentUtils.safeGetParcelableExtra( + intent, + CustomTabIntentDataProvider.EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION); + } + return pendingIntent; + } + + private static String getTranslateLanguage(Intent intent) { + String translateLanguage = null; + Locale locale = CustomTabsIntent.getTranslateLocale(intent); + if (locale != null) translateLanguage = LocaleUtils.toLanguageTag(locale); + if (TextUtils.isEmpty(translateLanguage)) { + translateLanguage = IntentUtils.safeGetStringExtra(intent, EXTRA_TRANSLATE_LANGUAGE); + } + return translateLanguage; + } + + private void updateExtraMenuItems(List menuItems) { + if (menuItems == null) return; + 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<>(title, pendingIntent)); + } + } + + /** + * Triggers the client-defined action when the user clicks a custom menu item. + * + * @param activity The {@link Activity} 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( + Activity 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; + ActivityOptions options = ActivityOptions.makeBasic(); + ApiCompatibilityUtils.setActivityOptionsBackgroundActivityStartAllowAlways(options); + pendingIntent.send( + activity, + 0, + isMediaViewer() ? null : addedIntent, + mOnFinishedForTesting, + null, + null, + options.toBundle()); + 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."); + } + } + + 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; + } + + /** + * Get the verified custom tabs UI type, according to the intent extras, and whether + * the intent is trusted. + * + * If the intent extras include a valid EXTRA_NETWORK, consider that the custom tab is + * used for captive portal scenarios especially and the UI hides the "Open in Chrome browser" + * menu item accordingly. + * + * @param requestedUiType requested UI type in the intent, unqualified + * @return verified UI type + */ + @BrowserServicesIntentDataProvider.CustomTabsUiType + private int getCustomTabsUiType(int requestedUiType) { + if (mNetwork != null) return CustomTabsUiType.NETWORK_BOUND_TAB; + if (!isTrustedIntent()) { + if (VersionInfo.isLocalBuild()) Log.w(TAG, FIRST_PARTY_PITFALL_MSG); + 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 = CustomButtonParamsImpl.fromIntent(context, intent); + Set googleBottomBarSupportedCustomButtonParamIds = + getGoogleBottomBarSupportedCustomButtonParamIds(); + for (CustomButtonParams params : mCustomButtonParams) { + if (googleBottomBarSupportedCustomButtonParamIds.contains(params.getId())) { + mGoogleBottomBarButtons.add(params); + params.updateShowOnToolbar(false); + } else if (!params.showOnToolbar()) { + mBottombarButtons.add(params); + } else if (canAddMoreToolbarItems()) { + mToolbarButtons.add(params); + } else { + Log.w(TAG, "Only %d items are allowed in the toolbar", getMaxCustomToolbarItems()); + } + } + + for (CustomButtonParams params : + getAdditionalSupportedGoogleBottomBarCustomButtonParams(context)) { + params.updateShowOnToolbar(false); + mCustomButtonParams.add(params); + mGoogleBottomBarButtons.add(params); + } + } + + private boolean canAddMoreToolbarItems() { + return mToolbarButtons.size() < getMaxCustomToolbarItems(); + } + + /** + * Determines which buttons should be displayed in the Google Bottom Bar. + * + * @return A set of integers representing the customButtonParamIds of the buttons that should be + * displayed in the Google Bottom Bar. If the Google Bottom Bar is not enabled, an empty set + * is returned. + */ + private Set getGoogleBottomBarSupportedCustomButtonParamIds() { + if (!isGoogleBottomBarEnabled(this)) { + return Set.of(); + } + GoogleBottomBarIntentParams intentParams = + CustomTabsConnection.getInstance().getGoogleBottomBarIntentParams(this); + return GoogleBottomBarCoordinator.getSupportedCustomButtonParamIds(intentParams); + } + + /** + * Retrieves a list of additional CustomButtonParams that are only supported for the Google + * Bottom Bar and should never be shown on the CCT toolbar. + * + * @param context Current context. + * @return A list of {@link CustomButtonParams}. If the Google Bottom Bar is not enabled, or no + * supported buttons are found, an empty list is returned. + */ + private List getAdditionalSupportedGoogleBottomBarCustomButtonParams( + Context context) { + List supportedGoogleBottomBarCustomButtonParams = new ArrayList<>(); + if (!isGoogleBottomBarEnabled(this)) { + return supportedGoogleBottomBarCustomButtonParams; + } + List googleBottomBarButtonBundles = + CustomTabsConnection.getInstance().getGoogleBottomBarButtons(this); + List googleBottomBarButtons = + CustomButtonParamsImpl.fromBundleList( + context, googleBottomBarButtonBundles, /* tinted= */ false); + for (CustomButtonParams params : googleBottomBarButtons) { + if (GoogleBottomBarCoordinator.isSupported(params.getId())) { + supportedGoogleBottomBarCustomButtonParams.add(params); + } else { + Log.w(TAG, "Unused GoogleBottomBarButton id: %s", params.getId()); + } + } + return supportedGoogleBottomBarCustomButtonParams; + } + + private static boolean isGoogleBottomBarEnabled(BrowserServicesIntentDataProvider provider) { + return GoogleBottomBarCoordinator.isFeatureEnabled() + && CustomTabsConnection.getInstance() + .shouldEnableGoogleBottomBarForIntent(provider); + } + + private int getMaxCustomToolbarItems() { + return MAX_CUSTOM_TOOLBAR_ITEMS; + } + + /** + * Adds a share option to the custom tab according to the {@link + * CustomTabsIntent#EXTRA_SHARE_STATE} stored in the intent. + * + *

Shows share options according to the following rules: + * + *

+ */ + private void addShareOption(Intent intent, Context context) { + if (mShareState == CustomTabsIntent.SHARE_STATE_DEFAULT) { + if (mToolbarButtons.isEmpty() + || (isCpaOnlyOpenInBrowserDefault() && canAddMoreToolbarItems())) { + mToolbarButtons.add( + CustomButtonParamsImpl.createShareButton( + context, getColorProvider().getToolbarColor())); + } else if (mMenuEntries.isEmpty()) { + mShowShareItemInMenu = true; + } + } else if (mShareState == CustomTabsIntent.SHARE_STATE_ON) { + if (mToolbarButtons.isEmpty() + || (isCpaOnlyOpenInBrowserDefault() && canAddMoreToolbarItems())) { + mToolbarButtons.add( + CustomButtonParamsImpl.createShareButton( + context, getColorProvider().getToolbarColor())); + } else { + mShowShareItemInMenu = true; + } + } else { + mShowShareItemInMenu = + IntentUtils.safeGetBooleanExtra( + intent, + CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, + mIsOpenedByChrome && mUiType == CustomTabsUiType.DEFAULT); + } + } + + private void addOpenInBrowserOption(Intent intent, Context context) { + boolean usingInteractiveOmnibox = + CustomTabsConnection.getInstance().shouldEnableOmniboxForIntent(this); + + int openInBrowserState = + IntentUtils.safeGetIntExtra( + intent, + EXTRA_OPEN_IN_BROWSER_STATE, + CustomTabsButtonState.BUTTON_STATE_DEFAULT); + + if (openInBrowserState == CustomTabsButtonState.BUTTON_STATE_ON + && !ChromeFeatureList.sCctOpenInBrowserButtonIfEnabledByEmbedder.isEnabled()) { + openInBrowserState = CustomTabsButtonState.BUTTON_STATE_DEFAULT; + } + + if (openInBrowserState == CustomTabsButtonState.BUTTON_STATE_DEFAULT) { + if (usingInteractiveOmnibox) { + openInBrowserState = CustomTabsButtonState.BUTTON_STATE_ON; + } else if (ChromeFeatureList.sCctOpenInBrowserButtonIfAllowedByEmbedder.isEnabled() + && IntentUtils.safeGetBooleanExtra( + intent, EXTRA_OPEN_IN_BROWSER_BUTTON_ALLOWED, false)) { + openInBrowserState = CustomTabsButtonState.BUTTON_STATE_ON; + } else { + openInBrowserState = CustomTabsButtonState.BUTTON_STATE_OFF; + } + } + + if (openInBrowserState == CustomTabsButtonState.BUTTON_STATE_ON) { + if (mToolbarButtons.isEmpty() + || (isCpaOnlyOpenInBrowserDefault() && canAddMoreToolbarItems())) { + mToolbarButtons.add( + CustomButtonParamsImpl.createOpenInBrowserButton( + context, getColorProvider().getToolbarColor())); + } + } + } + + 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; + } + } + } + return url; + } + + private @Nullable TrustedWebActivityDisplayMode resolveTwaDisplayMode() { + Bundle bundle = + IntentUtils.safeGetBundleExtra( + mIntent, TrustedWebActivityIntentBuilder.EXTRA_DISPLAY_MODE); + if (bundle == null) { + return null; + } + try { + return TrustedWebActivityDisplayMode.fromBundle(bundle); + } catch (Throwable e) { + return null; + } + } + + /** + * Returns the {@link ScreenOrientationLockType} which matches {@link ScreenOrientation}. + * @param orientation {@link ScreenOrientation} + * @return The matching ScreenOrientationLockType. {@link ScreenOrientationLockType#DEFAULT} if + * there is no match. + */ + private static int convertOrientationType(@ScreenOrientation.LockType int orientation) { + switch (orientation) { + case ScreenOrientation.DEFAULT: + return ScreenOrientationLockType.DEFAULT; + case ScreenOrientation.PORTRAIT_PRIMARY: + return ScreenOrientationLockType.PORTRAIT_PRIMARY; + case ScreenOrientation.PORTRAIT_SECONDARY: + return ScreenOrientationLockType.PORTRAIT_SECONDARY; + case ScreenOrientation.LANDSCAPE_PRIMARY: + return ScreenOrientationLockType.LANDSCAPE_PRIMARY; + case ScreenOrientation.LANDSCAPE_SECONDARY: + return ScreenOrientationLockType.LANDSCAPE_SECONDARY; + case ScreenOrientation.ANY: + return ScreenOrientationLockType.ANY; + case ScreenOrientation.LANDSCAPE: + return ScreenOrientationLockType.LANDSCAPE; + case ScreenOrientation.PORTRAIT: + return ScreenOrientationLockType.PORTRAIT; + case ScreenOrientation.NATURAL: + return ScreenOrientationLockType.NATURAL; + default: + Log.w( + TAG, + "The provided orientaton is not supported, orientation = %d", + orientation); + return ScreenOrientationLockType.DEFAULT; + } + } + + /** + * Logs the usage of intents of all CCT features to a large enum histogram in order to track + * usage by apps. + * + * @param intent The intent used to launch the CCT. + * @param colorScheme The requested color scheme to use with the CCT. + */ + private void logCustomTabFeatures(Intent intent, int colorScheme) { + CustomTabsFeatureUsage featureUsage = new CustomTabsFeatureUsage(); + + // Ordering: Log all the features ordered by CustomTabsFeature enum, when they apply. + if (mAnimationBundle != null) { + featureUsage.log(CustomTabsFeature.EXTRA_EXIT_ANIMATION_BUNDLE); + } + if (IntentUtils.safeHasExtra(intent, CustomTabsIntent.EXTRA_TINT_ACTION_BUTTON)) { + featureUsage.log(CustomTabsFeature.EXTRA_TINT_ACTION_BUTTON); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_INITIAL_BACKGROUND_COLOR)) { + featureUsage.log(CustomTabsFeature.EXTRA_INITIAL_BACKGROUND_COLOR); + } + if (mInteractWithBackground) { + featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_BACKGROUND_INTERACTION); + } + if (IntentUtils.safeHasExtra(intent, CustomTabsIntent.EXTRA_CLOSE_BUTTON_ICON)) { + featureUsage.log(CustomTabsFeature.EXTRA_CLOSE_BUTTON_ICON); + } + if (getCloseButtonPosition() != CLOSE_BUTTON_POSITION_DEFAULT) { + featureUsage.log(CustomTabsFeature.EXTRA_CLOSE_BUTTON_POSITION); + } + if (colorScheme == CustomTabsIntent.COLOR_SCHEME_DARK) { + featureUsage.log(CustomTabsFeature.CTF_DARK); + } + if (colorScheme == CustomTabsIntent.COLOR_SCHEME_LIGHT) { + featureUsage.log(CustomTabsFeature.CTF_LIGHT); + } + if (IntentUtils.safeHasExtra(intent, CustomTabsIntent.EXTRA_COLOR_SCHEME)) { + featureUsage.log(CustomTabsFeature.EXTRA_COLOR_SCHEME); + } + if (colorScheme == CustomTabsIntent.COLOR_SCHEME_SYSTEM) { + featureUsage.log(CustomTabsFeature.CTF_SYSTEM); + } + if (mDisableDownload) featureUsage.log(CustomTabsFeature.EXTRA_DISABLE_DOWNLOAD_BUTTON); + if (mDisableStar) featureUsage.log(CustomTabsFeature.EXTRA_DISABLE_STAR_BUTTON); + if (mGsaExperimentIds != null) featureUsage.log(CustomTabsFeature.EXPERIMENT_IDS); + if (IntentUtils.safeHasExtra( + intent, + CustomTabIntentDataProvider.EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL_LEGACY) + || IntentUtils.safeHasExtra(intent, EXTRA_INITIAL_ACTIVITY_HEIGHT_PX)) { + featureUsage.log(CustomTabsFeature.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_INITIAL_ACTIVITY_WIDTH_PX)) { + featureUsage.log(CustomTabsFeature.EXTRA_INITIAL_ACTIVITY_WIDTH_PX); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP)) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE)) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION)) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_POSITION); + } + if (mEnableEmbeddedMediaExperience) { + featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE); + } + if (mIsFromMediaLauncherActivity) { + featureUsage.log(CustomTabsFeature.EXTRA_BROWSER_LAUNCH_SOURCE); + } + if (mMediaViewerUrl != null) featureUsage.log(CustomTabsFeature.EXTRA_MEDIA_VIEWER_URL); + if (mMenuEntries != null) featureUsage.log(CustomTabsFeature.EXTRA_MENU_ITEMS); + if (IntentUtils.safeHasExtra(intent, IntentHandler.EXTRA_CALLING_ACTIVITY_PACKAGE)) { + featureUsage.log(CustomTabsFeature.EXTRA_CALLING_ACTIVITY_PACKAGE); + } + if (getClientPackageName() != null) featureUsage.log(CustomTabsFeature.CTF_PACKAGE_NAME); + if (IntentUtils.safeHasExtra(intent, EXTRA_TOOLBAR_CORNER_RADIUS_IN_PIXEL_LEGACY) + || IntentUtils.safeHasExtra(intent, EXTRA_TOOLBAR_CORNER_RADIUS_DP)) { + featureUsage.log(CustomTabsFeature.EXTRA_TOOLBAR_CORNER_RADIUS_DP); + } + if (isPartialHeightCustomTab()) { + featureUsage.log(CustomTabsFeature.CTF_PARTIAL); + } + if (isPartialWidthCustomTab()) { + featureUsage.log(CustomTabsFeature.CTF_PARTIAL_SIDE_SHEET); + } + if (mRemoteViewsPendingIntent != null) { + featureUsage.log(CustomTabsFeature.EXTRA_REMOTEVIEWS_PENDINGINTENT); + } + if (mClickableViewIds != null) { + featureUsage.log(CustomTabsFeature.EXTRA_REMOTEVIEWS_VIEW_IDS); + } + if (mRemoteViews != null) featureUsage.log(CustomTabsFeature.EXTRA_REMOTEVIEWS); + if (!mIsPartialCustomTabFixedHeight) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR); + } + if (mDefaultOrientation != ScreenOrientation.DEFAULT) { + featureUsage.log(CustomTabsFeature.EXTRA_SCREEN_ORIENTATION); + } + if (mIsOpenedByChrome) featureUsage.log(CustomTabsFeature.CTF_SENT_BY_CHROME); + if (mKeepAliveServiceIntent != null) featureUsage.log(CustomTabsFeature.EXTRA_KEEP_ALIVE); + if (mShowShareItemInMenu) featureUsage.log(CustomTabsFeature.EXTRA_DEFAULT_SHARE_MENU_ITEM); + if (IntentUtils.safeHasExtra(intent, CustomTabsIntent.EXTRA_SHARE_STATE)) { + featureUsage.log(CustomTabsFeature.EXTRA_SHARE_STATE); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_TITLE_VISIBILITY_STATE)) { + featureUsage.log(CustomTabsFeature.EXTRA_TITLE_VISIBILITY_STATE); + } + if (IntentUtils.safeHasExtra(intent, CustomTabsIntent.EXTRA_TOOLBAR_ITEMS)) { + featureUsage.log(CustomTabsFeature.EXTRA_TOOLBAR_ITEMS); + } + if (mTranslateLanguage != null) { + featureUsage.log(CustomTabsFeature.EXTRA_TRANSLATE_LANGUAGE); + } + if (mAutoTranslateLanguage != null) { + featureUsage.log(CustomTabsFeature.EXTRA_AUTO_TRANSLATE_LANGUAGE); + } + if (IntentUtils.safeHasExtra(intent, TrustedWebActivityIntentBuilder.EXTRA_DISPLAY_MODE)) { + featureUsage.log(CustomTabsFeature.EXTRA_DISPLAY_MODE); + } + if (mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY) { + featureUsage.log(CustomTabsFeature.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY); + } + if (mTrustedWebActivityAdditionalOrigins != null) { + featureUsage.log(CustomTabsFeature.EXTRA_ADDITIONAL_TRUSTED_ORIGINS); + } + if (mEnableUrlBarHiding) featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_URLBAR_HIDING); + if (showSideSheetMaximizeButton()) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION); + } + if (mSecondaryToolbarSwipeUpPendingIntent != null) { + featureUsage.log(CustomTabsFeature.EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_ACTIVITY_SIDE_SHEET_POSITION)) { + featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_POSITION); + } + if (CustomTabsConnection.getInstance().shouldEnableGoogleBottomBarForIntent(this)) { + featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_GOOGLE_BOTTOM_BAR); + } + if (CustomTabsConnection.getInstance().hasExtraGoogleBottomBarButtons(this)) { + featureUsage.log(CustomTabsFeature.EXTRA_GOOGLE_BOTTOM_BAR_BUTTONS); + } + if (mNetwork != null) featureUsage.log(CustomTabsFeature.EXTRA_NETWORK); + if (IntentUtils.safeHasExtra(intent, EXTRA_OPEN_IN_BROWSER_STATE)) { + featureUsage.log(CustomTabsFeature.EXTRA_OPEN_IN_BROWSER_STATE); + } + if (IntentUtils.safeHasExtra( + intent, TrustedWebActivityIntentBuilder.EXTRA_LAUNCH_HANDLER_CLIENT_MODE)) { + featureUsage.log(CustomTabsFeature.EXTRA_LAUNCH_HANDLER); + } + if (IntentUtils.safeHasExtra( + intent, TrustedWebActivityIntentBuilder.EXTRA_FILE_HANDLING_DATA)) { + featureUsage.log(CustomTabsFeature.EXTRA_FILE_HANDLERS); + } + if (IntentUtils.safeHasExtra(intent, EXTRA_CUSTOM_CONTENT_ACTIONS)) { + featureUsage.log(CustomTabsFeature.EXTRA_CUSTOM_CONTENT_ACTIONS); + } + } + + @Override + public int getDefaultOrientation() { + return mDefaultOrientation; + } + + @Override + public @ActivityType int getActivityType() { + return mActivityType; + } + + @Override + public Intent getIntent() { + return mIntent; + } + + @Override + public @Nullable SessionHolder getSession() { + return mSession; + } + + @Override + public @Nullable Intent getKeepAliveServiceIntent() { + return mKeepAliveServiceIntent; + } + + @Override + public boolean isPartialHeightCustomTab() { + if (BuildInfo.getInstance().isAutomotive) return false; + return getInitialActivityHeight() > 0; + } + + @Override + public boolean isPartialWidthCustomTab() { + if (BuildInfo.getInstance().isAutomotive) return false; + return getInitialActivityWidth() > 0; + } + + @Override + public boolean isPartialCustomTab() { + return isPartialHeightCustomTab() || isPartialWidthCustomTab(); + } + + @Override + public boolean shouldAnimateOnFinish() { + return getInsecureClientPackageNameForOnFinishAnimation() != null; + } + + /** Returns client package name for finishing animation. */ + public String getInsecureClientPackageNameForOnFinishAnimation() { + // The package name may come from the insecure info contained in the animation + // bundle which won't do any harm in the operation. + if (mAnimationBundle == null) return null; + return mAnimationBundle.getString(BUNDLE_PACKAGE_NAME); + } + + @Override + public @Nullable String getClientPackageName() { + return getClientPackageNameFromSessionOrCallingActivity(mIntent, mSession); + } + + @Override + public @Nullable String getClientPackageNameIdentitySharing() { + return IntentUtils.safeGetStringExtra(mIntent, IntentHandler.EXTRA_LAUNCHED_FROM_PACKAGE); + } + + @Override + public int getAnimationEnterRes() { + return shouldAnimateOnFinish() + ? mAnimationBundle.getInt(BUNDLE_ENTER_ANIMATION_RESOURCE) + : 0; + } + + @Override + public int getAnimationExitRes() { + return shouldAnimateOnFinish() && !isPartialCustomTab() + ? mAnimationBundle.getInt(BUNDLE_EXIT_ANIMATION_RESOURCE) + : 0; + } + + @Override + public boolean isTrustedIntent() { + return mIsTrustedIntent; + } + + @Override + public String getUrlToLoad() { + if (mUrlToLoad == null) { + mUrlToLoad = resolveUrlToLoad(getIntent()); + } + return mUrlToLoad; + } + + @Override + public boolean shouldEnableUrlBarHiding() { + return mEnableUrlBarHiding; + } + + @Override + public boolean contentScrollMayResizeTab() { + return mContentScrollMayResizeTab; + } + + @Override + public ColorProvider getColorProvider() { + return mColorProvider; + } + + @Override + public @Nullable Drawable getCloseButtonDrawable() { + return mCloseButtonIcon; + } + + @Override + public @TitleVisibility int getTitleVisibilityState() { + return mTitleVisibilityState; + } + + @Override + public boolean shouldShowShareMenuItem() { + return mShowShareItemInMenu; + } + + @Override + public List getCustomButtonsOnToolbar() { + return mToolbarButtons; + } + + @Override + public List getCustomButtonsOnBottombar() { + return mBottombarButtons; + } + + @Override + public List getCustomButtonsOnGoogleBottomBar() { + return mGoogleBottomBarButtons; + } + + @Override + public @Nullable RemoteViews getBottomBarRemoteViews() { + return mRemoteViews; + } + + @Override + @Nullable + public int[] getClickableViewIDs() { + if (mClickableViewIds == null) return null; + return mClickableViewIds.clone(); + } + + @Override + public @Nullable PendingIntent getRemoteViewsPendingIntent() { + return mRemoteViewsPendingIntent; + } + + @Nullable + @Override + public PendingIntent getSecondaryToolbarSwipeUpPendingIntent() { + return mSecondaryToolbarSwipeUpPendingIntent; + } + + @Override + public List getAllCustomButtons() { + return mCustomButtonParams; + } + + @Override + public List getMenuTitles() { + ArrayList list = new ArrayList<>(); + for (Pair pair : mMenuEntries) { + list.add(pair.first); + } + return list; + } + + /** + * Set the callback object for {@link PendingIntent}s that are sent in this class. For testing + * purpose only. + */ + void setPendingIntentOnFinishedForTesting(PendingIntent.OnFinished onFinished) { + mOnFinishedForTesting = onFinished; + ResettersForTesting.register(() -> mOnFinishedForTesting = null); + } + + @Override + public boolean isOpenedByChrome() { + return mIsOpenedByChrome; + } + + @Override + @BrowserServicesIntentDataProvider.CustomTabsUiType + public int getUiType() { + return mUiType; + } + + /** + * @return See {@link #EXTRA_MEDIA_VIEWER_URL}. + */ + @Override + public @Nullable String getMediaViewerUrl() { + return mMediaViewerUrl; + } + + @Override + public boolean shouldEnableEmbeddedMediaExperience() { + return mEnableEmbeddedMediaExperience; + } + + @Override + public boolean isFromMediaLauncherActivity() { + return mIsFromMediaLauncherActivity; + } + + @Override + public boolean shouldShowStarButton() { + return !mDisableStar; + } + + @Override + public boolean shouldShowDownloadButton() { + return !mDisableDownload; + } + + @Nullable + @Override + public TrustedWebActivityDisplayMode getProvidedTwaDisplayMode() { + return mTrustedWebActivityDisplayMode; + } + + @Override + @Nullable + public List getTrustedWebActivityAdditionalOrigins() { + return mTrustedWebActivityAdditionalOrigins; + } + + @Override + @Nullable + public Set getAllTrustedWebActivityOrigins() { + // Lazily compute this, since `getUrlToLoad()` requires native to be loaded. + if (mAllTrustedWebActivityOrigins != null) { + return mAllTrustedWebActivityOrigins; + } + + mAllTrustedWebActivityOrigins = new HashSet<>(); + Origin initialOrigin = Origin.create(getUrlToLoad()); + if (initialOrigin != null) mAllTrustedWebActivityOrigins.add(initialOrigin); + if (mTrustedWebActivityAdditionalOrigins != null) { + for (String originAsString : mTrustedWebActivityAdditionalOrigins) { + Origin origin = Origin.create(originAsString); + if (origin == null) continue; + + mAllTrustedWebActivityOrigins.add(origin); + } + } + + return mAllTrustedWebActivityOrigins; + } + + @Override + public @Nullable String getTranslateLanguage() { + return shouldAutoTranslate() ? mAutoTranslateLanguage : mTranslateLanguage; + } + + @Override + public boolean shouldAutoTranslate() { + return mAutoTranslateLanguage != null && isAllowedToAutoTranslate(); + } + + private static boolean isPackageNameInList(String packageName, String pipeDelimitedList) { + if (packageName == null || TextUtils.isEmpty(pipeDelimitedList)) return false; + for (String p : pipeDelimitedList.split("\\|")) { + if (packageName.equals(p)) return true; + } + return false; + } + + private boolean isAllowedToAutoTranslate() { + if (!ChromeFeatureList.sCctAutoTranslate.isEnabled()) { + return false; + } + if (mIsTrustedIntent + && ChromeFeatureList.sCctAutoTranslateAllowAllFirstParties.getValue()) { + return true; + } + return isPackageNameInList( + getClientPackageName(), + ChromeFeatureList.sCctAutoTranslatePackageNamesAllowlist.getValue()); + } + + @Override + public @Nullable ShareTarget getShareTarget() { + Bundle bundle = + IntentUtils.safeGetBundleExtra( + getIntent(), TrustedWebActivityIntentBuilder.EXTRA_SHARE_TARGET); + if (bundle == null) return null; + try { + return ShareTarget.fromBundle(bundle); + } catch (Throwable e) { + // Catch unparcelling errors. + return null; + } + } + + @Override + public @Nullable ShareData getShareData() { + Bundle bundle = + IntentUtils.safeGetParcelableExtra( + getIntent(), TrustedWebActivityIntentBuilder.EXTRA_SHARE_DATA); + if (bundle == null) return null; + try { + return ShareData.fromBundle(bundle); + } catch (Throwable e) { + // Catch unparcelling errors. + return null; + } + } + + @TwaDisclosureUi + @Override + public int getTwaDisclosureUi() { + int version = mIntent.getIntExtra(EXTRA_TWA_DISCLOSURE_UI, TwaDisclosureUi.DEFAULT); + + if (version != TwaDisclosureUi.V1_INFOBAR + && version != TwaDisclosureUi.V2_NOTIFICATION_OR_SNACKBAR) { + return TwaDisclosureUi.DEFAULT; + } + + return version; + } + + @ActivitySideSheetDecorationType + @Override + public int getActivitySideSheetDecorationType() { + return mSideSheetDecorationType; + } + + @ActivitySideSheetRoundedCornersPosition + @Override + public int getActivitySideSheetRoundedCornersPosition() { + return mSideSheetRoundedCornersPosition; + } + + @Override + @Nullable + public int[] getGsaExperimentIds() { + return mGsaExperimentIds; + } + + @Override + public @Px int getInitialActivityHeight() { + return getInitialActivityHeight( + mIsTrustedIntent, mInitialActivityHeight, getClientPackageName()); + } + + @Override + public @Px int getInitialActivityWidth() { + return getInitialActivityWidth( + mIsTrustedIntent, mInitialActivityWidth, getClientPackageName()); + } + + @Override + public int getActivityBreakPoint() { + return mBreakPointDp; + } + + static boolean isAllowedThirdParty(String packageName) { + if (packageName == null) return false; + String defaultPolicy = + ChromeFeatureList.sCctResizableForThirdPartiesDefaultPolicy.getValue(); + if (defaultPolicy.equals(DEFAULT_POLICY_USE_ALLOWLIST)) { + return isPackageNameInList( + packageName, + ChromeFeatureList.sCctResizableForThirdPartiesAllowlistEntries.getValue()); + } else if (defaultPolicy.equals(DEFAULT_POLICY_USE_DENYLIST)) { + return !isPackageNameInList( + packageName, + ChromeFeatureList.sCctResizableForThirdPartiesDenylistEntries.getValue()); + } + assert false : "We can't get here since the default policy is use denylist."; + return false; + } + + @Override + public @CloseButtonPosition int getCloseButtonPosition() { + if (!mIsCloseButtonEnabled) return CLOSE_BUTTON_POSITION_DEFAULT; + return IntentUtils.safeGetIntExtra( + mIntent, EXTRA_CLOSE_BUTTON_POSITION, CLOSE_BUTTON_POSITION_DEFAULT); + } + + @Override + public boolean shouldSuppressAppMenu() { + // The media viewer has no default menu items, so if there are also no custom items, we + // should disable the menu altogether. + return isMediaViewer() && getMenuTitles().isEmpty(); + } + + @Override + public int getPartialTabToolbarCornerRadius() { + return mPartialTabToolbarCornerRadius; + } + + @Override + public boolean isPartialCustomTabFixedHeight() { + return mIsPartialCustomTabFixedHeight; + } + + @Override + public boolean canInteractWithBackground() { + return mInteractWithBackground; + } + + @Override + public boolean showSideSheetMaximizeButton() { + return IntentUtils.safeGetBooleanExtra( + mIntent, EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION, false); + } + + @Override + public int getSideSheetSlideInBehavior() { + return ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE; + } + + @Override + public int getSideSheetPosition() { + int position = CustomTabsIntent.getActivitySideSheetPosition(mIntent); + return position == ACTIVITY_SIDE_SHEET_POSITION_DEFAULT + ? ACTIVITY_SIDE_SHEET_POSITION_END + : position; + } + + @Override + public boolean isInteractiveOmniboxAllowed() { + if (isOffTheRecord()) return false; + if (isPartialCustomTab()) return false; + if (BuildInfo.getInstance().isAutomotive) return false; + + return true; + } + + @Override + public boolean isInteractiveOmniboxEnabled() { + return ChromeFeatureList.sSearchInCCT.isEnabled() + && isPackageNameInList( + getClientPackageName(), + ChromeFeatureList.sSearchinCctOmniboxAllowedPackageNames.getValue()); + } + + @Override + public long getTargetNetwork() { + return mNetwork != null ? mNetwork.getNetworkHandle() : NetId.INVALID; + } + + @Override + public boolean isCloseButtonEnabled() { + return mIsCloseButtonEnabled; + } + + @Override + public @LaunchHandlerClientMode.ClientMode int getLaunchHandlerClientMode() { + @LaunchHandlerClientMode.ClientMode + int clientMode = + IntentUtils.safeGetIntExtra( + mIntent, + TrustedWebActivityIntentBuilder.EXTRA_LAUNCH_HANDLER_CLIENT_MODE, + LaunchHandlerClientMode.AUTO); + + if (Arrays.asList(NAVIGATE_EXISTING, FOCUS_EXISTING, NAVIGATE_NEW).contains(clientMode)) { + return clientMode; + } else { + return LaunchHandlerClientMode.AUTO; + } + } + + @Override + public @Nullable FileHandlingData getFileHandlingData() { + Bundle bundle = + IntentUtils.safeGetBundleExtra( + getIntent(), TrustedWebActivityIntentBuilder.EXTRA_FILE_HANDLING_DATA); + if (bundle == null) return null; + try { + return FileHandlingData.fromBundle(bundle); + } catch (Throwable e) { + // Catch unparcelling errors potentially thrown by AndroidX bundle parsing code + return null; + } + } + + @Override + public int getResolvedDisplayMode() { + if (!isTrustedWebActivity()) { + return DisplayMode.UNDEFINED; + } + + if (mResolvedDisplayMode != DisplayMode.UNDEFINED) { + return mResolvedDisplayMode; + } + + mResolvedDisplayMode = resolveDisplayMode(); + return mResolvedDisplayMode; + } + + @ExperimentalOpenInBrowser + @Override + public @OpenInBrowserState int getOpenInBrowserButtonState() { + return mOpenInBrowserState; + } + + @Override + public int getShareButtonState() { + return mShareState; + } + + @ExperimentalCustomContentAction + @Override + public List getCustomContentActions() { + if (ChromeFeatureList.sCctContextualMenuItems.isEnabled()) { + return CustomTabsIntent.getCustomContentActions(mIntent); + } + return List.of(); + } + + @Override + public boolean isOptionalButtonSupported() { + return ChromeFeatureList.sCctAdaptiveButton.isEnabled() + && !isTrustedWebActivity() + && mUiType == CustomTabsUiType.DEFAULT; + } + + private @DisplayMode.EnumType int resolveDisplayMode() { + TrustedWebActivityDisplayMode displayMode = getProvidedTwaDisplayMode(); + if (displayMode == null) { + return DisplayMode.STANDALONE; + } + + if (displayMode instanceof TrustedWebActivityDisplayMode.ImmersiveMode) { + return DisplayMode.FULLSCREEN; + } + + // `browser` display mode web apps are not installable by default, because by the spec they + // should be opened in a new tab or browser window, but in Chrome they can be forcefully + // installed via app menu. In this case display mode should resolve to the first supported + // display mode in the "fullscreen -> standalone -> minimal-ui -> browser" fallback chain. + boolean shouldUseMinimalUi = + displayMode instanceof TrustedWebActivityDisplayMode.MinimalUiMode + || displayMode instanceof TrustedWebActivityDisplayMode.BrowserMode; + if (WebAppHeaderUtils.isMinimalUiFlagEnabled() && shouldUseMinimalUi) { + return DisplayMode.MINIMAL_UI; + } + + return DisplayMode.STANDALONE; + } + + @Override + public long getTwaStartupUptimeMillis() { + if (!isTrustedWebActivity()) return 0; + return IntentUtils.safeGetLongExtra(getIntent(), EXTRA_TWA_STARTUP_UPTIME_MS, 0); + } + + @Override + public int getAndroidBrowserHelperVersion() { + return IntentUtils.safeGetIntExtra(getIntent(), EXTRA_ANDROID_BROWSER_HELPER_VERSION, 0); + } + + private boolean isCpaOnlyOpenInBrowserDefault() { + return ChromeFeatureList.sCctAdaptiveButtonContextualOnly.getValue() + && ChromeFeatureList.sCctAdaptiveButtonEnableOpenInBrowser.getValue() + && ChromeFeatureList.sCctAdaptiveButtonDefaultVariant.getValue() + == AdaptiveToolbarButtonVariant.OPEN_IN_BROWSER; + } +} diff --git a/tools/under-control/src/chrome/browser/download/background_download_service_factory.cc b/tools/under-control/src/chrome/browser/download/background_download_service_factory.cc new file mode 100755 index 00000000..bde438ed --- /dev/null +++ b/tools/under-control/src/chrome/browser/download/background_download_service_factory.cc @@ -0,0 +1,242 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/download/background_download_service_factory.h" + +#include +#include + +#include "base/feature_list.h" +#include "base/files/file_path.h" +#include "base/memory/raw_ptr.h" +#include "base/no_destructor.h" +#include "base/task/sequenced_task_runner.h" +#include "base/task/single_thread_task_runner.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "build/build_config.h" +#include "chrome/browser/download/deferred_client_wrapper.h" +#include "chrome/browser/download/download_manager_utils.h" +#include "chrome/browser/download/simple_download_manager_coordinator_factory.h" +#include "chrome/browser/optimization_guide/prediction/prediction_model_download_client.h" +#include "chrome/browser/profiles/incognito_helpers.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_key.h" +#include "chrome/browser/transition_manager/full_browser_transition_manager.h" +#include "chrome/common/chrome_constants.h" +#include "components/background_fetch/download_client.h" +#include "components/download/content/factory/download_service_factory_helper.h" +#include "components/download/content/factory/navigation_monitor_factory.h" +#include "components/download/public/background_service/background_download_service.h" +#include "components/download/public/background_service/basic_task_scheduler.h" +#include "components/download/public/background_service/blob_context_getter_factory.h" +#include "components/download/public/background_service/clients.h" +#include "components/download/public/background_service/features.h" +#include "components/download/public/background_service/url_loader_factory_getter.h" +#include "components/download/public/common/simple_download_manager_coordinator.h" +#include "components/keyed_service/core/simple_dependency_manager.h" +#include "components/leveldb_proto/public/proto_database_provider.h" +#include "components/optimization_guide/core/optimization_guide_features.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/download_manager.h" +#include "content/public/browser/network_service_instance.h" +#include "content/public/browser/storage_partition.h" + +#if BUILDFLAG(IS_ANDROID) +#include "chrome/browser/download/android/service/download_task_scheduler.h" +#endif + +#if BUILDFLAG(IS_CHROMEOS) +#include "chrome/browser/ash/plugin_vm/plugin_vm_image_download_client.h" +#endif + +namespace { + +std::unique_ptr CreateBackgroundFetchDownloadClient( + Profile* profile) { + return std::make_unique(profile); +} + +#if BUILDFLAG(IS_CHROMEOS) +std::unique_ptr CreatePluginVmImageDownloadClient( + Profile* profile) { + return std::make_unique(profile); +} +#endif // BUILDFLAG(IS_CHROMEOS) + +std::unique_ptr +CreateOptimizationGuidePredictionModelDownloadClient(Profile* profile) { + return std::make_unique( + profile); +} + +// Called on profile created to retrieve the BlobStorageContextGetter. +void DownloadOnProfileCreated(download::BlobContextGetterCallback callback, + Profile* profile) { + auto blob_context_getter = profile->GetBlobStorageContext(); + DCHECK(callback); + std::move(callback).Run(blob_context_getter); +} + +// Provides BlobContextGetter from Chrome asynchronously. +class DownloadBlobContextGetterFactory + : public download::BlobContextGetterFactory { + public: + explicit DownloadBlobContextGetterFactory(SimpleFactoryKey* key) : key_(key) { + DCHECK(key_); + } + + DownloadBlobContextGetterFactory(const DownloadBlobContextGetterFactory&) = + delete; + DownloadBlobContextGetterFactory& operator=( + const DownloadBlobContextGetterFactory&) = delete; + + ~DownloadBlobContextGetterFactory() override = default; + + private: + // download::BlobContextGetterFactory implementation. + void RetrieveBlobContextGetter( + download::BlobContextGetterCallback callback) override { + FullBrowserTransitionManager::Get()->RegisterCallbackOnProfileCreation( + key_, base::BindOnce(&DownloadOnProfileCreated, std::move(callback))); + } + + raw_ptr key_; +}; + +void OnProfileCreated(download::URLLoaderFactoryGetterCallback callback, + Profile* profile) { + DCHECK(callback); + std::move(callback).Run(profile->GetURLLoaderFactory()); +} + +class URLLoaderFactoryGetter : public download::URLLoaderFactoryGetter { + public: + explicit URLLoaderFactoryGetter(SimpleFactoryKey* key) : key_(key) { + DCHECK(key_); + } + + URLLoaderFactoryGetter(const URLLoaderFactoryGetter&) = delete; + URLLoaderFactoryGetter& operator=(const URLLoaderFactoryGetter&) = delete; + + ~URLLoaderFactoryGetter() override = default; + + private: + void RetrieveURLLoaderFactory( + download::URLLoaderFactoryGetterCallback callback) override { + FullBrowserTransitionManager::Get()->RegisterCallbackOnProfileCreation( + key_, base::BindOnce(&OnProfileCreated, std::move(callback))); + } + + raw_ptr key_; +}; + +} // namespace + +// static +BackgroundDownloadServiceFactory* +BackgroundDownloadServiceFactory::GetInstance() { + static base::NoDestructor instance; + return instance.get(); +} + +// static +download::BackgroundDownloadService* +BackgroundDownloadServiceFactory::GetForKey(SimpleFactoryKey* key) { + return static_cast( + GetInstance()->GetServiceForKey(key, true)); +} + +BackgroundDownloadServiceFactory::BackgroundDownloadServiceFactory() + : SimpleKeyedServiceFactory("download::BackgroundDownloadService", + SimpleDependencyManager::GetInstance()) { + DependsOn(SimpleDownloadManagerCoordinatorFactory::GetInstance()); + DependsOn(download::NavigationMonitorFactory::GetInstance()); +} + +BackgroundDownloadServiceFactory::~BackgroundDownloadServiceFactory() = default; + +std::unique_ptr +BackgroundDownloadServiceFactory::BuildServiceInstanceFor( + SimpleFactoryKey* key) const { + auto clients = std::make_unique(); + ProfileKey* profile_key = ProfileKey::FromSimpleFactoryKey(key); + + clients->insert(std::make_pair( + download::DownloadClient::BACKGROUND_FETCH, + std::make_unique( + base::BindOnce(&CreateBackgroundFetchDownloadClient), key))); + +#if BUILDFLAG(IS_CHROMEOS) + if (!key->IsOffTheRecord()) { + clients->insert(std::make_pair( + download::DownloadClient::PLUGIN_VM_IMAGE, + std::make_unique( + base::BindOnce(&CreatePluginVmImageDownloadClient), key))); + } +#endif // BUILDFLAG(IS_CHROMEOS) + + if (!key->IsOffTheRecord()) { + clients->insert(std::make_pair( + download::DownloadClient::OPTIMIZATION_GUIDE_PREDICTION_MODELS, + std::make_unique( + base::BindOnce( + &CreateOptimizationGuidePredictionModelDownloadClient), + key))); + } + + // Build in memory download service for incognito profile. + if (key->IsOffTheRecord()) { + auto blob_context_getter_factory = + std::make_unique(key); + scoped_refptr io_task_runner = + content::GetIOThreadTaskRunner({}); + + return download::BuildInMemoryDownloadService( + key, std::move(clients), content::GetNetworkConnectionTracker(), + base::FilePath(), std::move(blob_context_getter_factory), + io_task_runner, std::make_unique<::URLLoaderFactoryGetter>(key)); + } else { + // Build download service for normal profile. + base::FilePath storage_dir; + if (!key->IsOffTheRecord() && !key->GetPath().empty()) { + storage_dir = + key->GetPath().Append(chrome::kDownloadServiceStorageDirname); + } + scoped_refptr background_task_runner = + base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT}); + + std::unique_ptr task_scheduler; +#if BUILDFLAG(IS_ANDROID) + task_scheduler = + std::make_unique(); +#else + task_scheduler = + std::make_unique(base::BindRepeating( + [](SimpleFactoryKey* key) { + return BackgroundDownloadServiceFactory::GetForKey(key); + }, + key)); +#endif + // Some tests doesn't initialize DownloadManager when profile is created, + // and cause the download service to fail. Call + // InitializeSimpleDownloadManager() to initialize the DownloadManager + // whenever profile becomes available. + DownloadManagerUtils::InitializeSimpleDownloadManager(profile_key); + leveldb_proto::ProtoDatabaseProvider* proto_db_provider = + profile_key->GetProtoDatabaseProvider(); + return download::BuildDownloadService( + key, std::move(clients), content::GetNetworkConnectionTracker(), + storage_dir, SimpleDownloadManagerCoordinatorFactory::GetForKey(key), + proto_db_provider, background_task_runner, std::move(task_scheduler)); + } +} + +SimpleFactoryKey* BackgroundDownloadServiceFactory::GetKeyToUse( + SimpleFactoryKey* key) const { + return key; +} diff --git a/tools/under-control/src/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/tools/under-control/src/third_party/blink/renderer/platform/runtime_enabled_features.json5 index f4784c62..485025e4 100755 --- a/tools/under-control/src/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/tools/under-control/src/third_party/blink/renderer/platform/runtime_enabled_features.json5 @@ -957,8 +957,8 @@ status: "stable", }, { + // TODO(crbug.com/440374239): Disabled due to https://crbug.com/440374239. name: "ClipElementVisibleBoundsInLocalRoot", - status: "stable", }, { name: "ClipPathNestedRasterOptimization",