blob: 19b986e09ffa131837868cfb85af4069977b3509 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.customtabs;
import android.app.Activity;
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.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
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.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.TrustedWebUtils;
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.IntentUtils;
import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.CachedFeatureFlags;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.version.ChromeVersionInfo;
import org.chromium.components.browser_ui.widget.TintedDrawable;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.device.mojom.ScreenOrientationLockType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* 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;
}
// These values are persisted to logs. Entries should not be renumbered and numeric values
// should never be reused.
@IntDef({ShareOptionLocation.TOOLBAR, ShareOptionLocation.MENU,
ShareOptionLocation.TOOLBAR_FULL_MENU_FALLBACK, ShareOptionLocation.NO_SPACE,
ShareOptionLocation.SHARE_DISABLED, ShareOptionLocation.NUM_ENTRIES})
private @interface ShareOptionLocation {
int TOOLBAR = 0;
int MENU = 1;
int TOOLBAR_FULL_MENU_FALLBACK = 2;
int NO_SPACE = 3;
int SHARE_DISABLED = 4;
// Must be the last one.
int NUM_ENTRIES = 5;
}
/**
* 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 =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? "android:activity." : "android:";
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";
/**
* See {@link BrowserServicesIntentDataProvider#shouldAddCctClientDataHeader} for details on
* this.
*/
public static final String EXTRA_ADD_CCT_CLIENT_DATA_HEADER =
"org.chromium.chrome.browser.customtabs.ADD_CCT_CLIENT_DATA_HEADER";
/**
* 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";
/**
* 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";
// 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";
// TODO(amalova): Move this to CustomTabsIntent.
/**
* Extra that, if set, specifies Translate UI should be triggered with
* specified target language.
*/
private static final String EXTRA_TRANSLATE_LANGUAGE =
"androidx.browser.customtabs.extra.TRANSLATE_LANGUAGE";
/**
* Extra that, if set, results in blocking new notification requests while in CCT.
*/
public static final String EXTRA_BLOCK_NEW_NOTIFICATION_REQUESTS_IN_CCT =
"androidx.browser.customtabs.extra.BLOCK_NEW_NOTIFICATION_REQUESTS_IN_CCT";
/**
* Extra that, if set, results in hiding omnibox suggestions for visits from cct. The value is
* a boolean, and is only considered if the feature kSuggestVisitsWithPageTransitionFromApi2 is
* enabled.
*/
public static final String EXTRA_HIDE_OMNIBOX_SUGGESTIONS_FROM_CCT =
"androidx.browser.customtabs.extra.HIDE_OMNIBOX_SUGGESTIONS_FROM_CCT";
/**
* Extra that determines whether the 'open in chrome' menu item should be shown in the context
* menu. The value is a boolean. Default value is false, meaning the item is shown.
*/
public static final String EXTRA_HIDE_OPEN_IN_CHROME_MENU_ITEM_IN_CONTEXT_MENU =
"androidx.browser.customtabs.extra.HIDE_OPEN_IN_CHROME_MENU_ITEM_IN_CONTEXT_MENU";
/**
* Extra that determines whether the 'open in chrome' menu item should be shown in the menu. The
* value is a boolean. Default value is false, meaning the item is shown.
*/
public static final String EXTRA_HIDE_OPEN_IN_CHROME_MENU_ITEM =
"androidx.browser.customtabs.extra.HIDE_OPEN_IN_CHROME_MENU_ITEM";
/**
* Extra that, if set, results in marking visits from cct as hidden. The value is
* a boolean, and is only considered if the feature kCCTHideVisits is enabled.
*/
public static final String EXTRA_HIDE_VISITS_FROM_CCT =
"androidx.browser.customtabs.extra.HIDE_VISITS_FROM_CCT";
/**
* Extra used to provide a PendingIntent that we can launch to focus the client.
* TODO(peconn): Move to AndroidX.
*/
private static final String EXTRA_FOCUS_INTENT =
"androidx.browser.customtabs.extra.FOCUS_INTENT";
private static final String EXTRA_TWA_DISCLOSURE_UI =
"androidx.browser.trusted.extra.DISCLOSURE_VERSION";
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 static final String EXPERIMENT_IDS =
"org.chromium.chrome.browser.customtabs.AGA_EXPERIMENT_IDS";
private final Intent mIntent;
private final CustomTabsSessionToken mSession;
private final boolean mIsTrustedIntent;
private final Intent mKeepAliveServiceIntent;
private 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<String> mTrustedWebActivityAdditionalOrigins;
@Nullable
private final TrustedWebActivityDisplayMode mTrustedWebActivityDisplayMode;
@Nullable
private String mUrlToLoad;
private boolean mEnableUrlBarHiding;
private List<CustomButtonParams> mCustomButtonParams;
private Drawable mCloseButtonIcon;
private List<Pair<String, PendingIntent>> mMenuEntries = new ArrayList<>();
private boolean mShowShareItemInMenu;
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;
private PendingIntent mFocusIntent;
/** Whether this CustomTabActivity was explicitly started by another Chrome Activity. */
private final boolean mIsOpenedByChrome;
/** ISO 639 language code */
@Nullable
private final String mTranslateLanguage;
private final int mDefaultOrientation;
@Nullable
private final int[] mGsaExperimentIds;
@NonNull
private final CustomTabColorProvider mColorProvider;
/**
* 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);
}
/**
* 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, CustomTabsSessionToken session) {
return IntentHandler.wasIntentSenderChrome(intent)
|| CustomTabsConnection.getInstance().isSessionFirstParty(session);
}
/**
* Constructs a {@link CustomTabIntentDataProvider}.
*
* 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;
mSession = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
mIsTrustedIntent = isTrustedCustomTab(intent, mSession);
mAnimationBundle = IntentUtils.safeGetBundleExtra(
intent, CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE);
mKeepAliveServiceIntent = IntentUtils.safeGetParcelableExtra(intent, EXTRA_KEEP_ALIVE);
mIsOpenedByChrome = IntentUtils.safeGetBooleanExtra(
intent, EXTRA_IS_OPENED_BY_CHROME, false);
final int requestedUiType =
IntentUtils.safeGetIntExtra(intent, EXTRA_UI_TYPE, CustomTabsUiType.DEFAULT);
mUiType = verifiedUiType(requestedUiType);
mColorProvider = new CustomTabColorProviderImpl(intent, context, colorScheme);
retrieveCustomButtons(intent, context);
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);
updateExtraMenuItems(menuItems);
addShareOption(intent, context);
mActivityType = IntentUtils.safeGetBooleanExtra(
intent, TrustedWebUtils.EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, false)
? ActivityType.TRUSTED_WEB_ACTIVITY
: ActivityType.CUSTOM_TAB;
mTrustedWebActivityAdditionalOrigins = IntentUtils.safeGetStringArrayListExtra(intent,
TrustedWebActivityIntentBuilder.EXTRA_ADDITIONAL_TRUSTED_ORIGINS);
mTrustedWebActivityDisplayMode = resolveTwaDisplayMode();
mTitleVisibilityState = IntentUtils.safeGetIntExtra(
intent, CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.NO_TITLE);
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);
mTranslateLanguage = IntentUtils.safeGetStringExtra(intent, EXTRA_TRANSLATE_LANGUAGE);
mFocusIntent = IntentUtils.safeGetParcelableExtra(intent, EXTRA_FOCUS_INTENT);
// Import the {@link ScreenOrientation}.
mDefaultOrientation = convertOrientationType(IntentUtils.safeGetIntExtra(intent,
TrustedWebActivityIntentBuilder.EXTRA_SCREEN_ORIENTATION,
ScreenOrientation.DEFAULT));
mGsaExperimentIds = IntentUtils.safeGetIntArrayExtra(intent, EXPERIMENT_IDS);
}
private void updateExtraMenuItems(List<Bundle> 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<String, PendingIntent>(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;
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.");
}
}
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 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;
}
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() {
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.
*
* <p>Shows share options according to the following rules:
*
* <ul>
* <li>If {@link CustomTabsIntent#SHARE_STATE_ON} or
* {@link CustomTabsIntent#SHARE_STATE_DEFAULT}, add to the top toolbar if empty, otherwise
* add to the overflow menu if it is not customized.
* <li>If {@link CustomTabsIntent#SHARE_STATE_OFF}, add to the overflow menu depending on
* {@link CustomTabsIntent#EXTRA_DEFAULT_SHARE_MENU_ITEM}.
* </ul>
*/
private void addShareOption(Intent intent, Context context) {
int shareState = IntentUtils.safeGetIntExtra(
intent, CustomTabsIntent.EXTRA_SHARE_STATE, CustomTabsIntent.SHARE_STATE_DEFAULT);
if (shareState == CustomTabsIntent.SHARE_STATE_ON
|| (shareState == CustomTabsIntent.SHARE_STATE_DEFAULT
&& CachedFeatureFlags.isEnabled(
ChromeFeatureList.SHARE_BY_DEFAULT_IN_CCT))) {
if (mToolbarButtons.isEmpty()) {
mToolbarButtons.add(
CustomButtonParams.createShareButton(context, getToolbarColor()));
logShareOptionLocation(ShareOptionLocation.TOOLBAR);
} else if (mMenuEntries.isEmpty()) {
mShowShareItemInMenu = true;
logShareOptionLocation(ShareOptionLocation.TOOLBAR_FULL_MENU_FALLBACK);
} else {
logShareOptionLocation(ShareOptionLocation.NO_SPACE);
}
} else {
mShowShareItemInMenu = IntentUtils.safeGetBooleanExtra(intent,
CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM,
mIsOpenedByChrome && mUiType == CustomTabsUiType.DEFAULT);
if (mShowShareItemInMenu) {
logShareOptionLocation(ShareOptionLocation.MENU);
} else {
logShareOptionLocation(ShareOptionLocation.SHARE_DISABLED);
}
}
}
private static void logShareOptionLocation(@ShareOptionLocation int shareOptionLocation) {
RecordHistogram.recordEnumeratedHistogram("CustomTabs.ShareOptionLocation",
shareOptionLocation, ShareOptionLocation.NUM_ENTRIES);
}
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;
}
@Nullable
private 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;
}
}
@Override
public int getDefaultOrientation() {
return mDefaultOrientation;
}
@Override
public @ActivityType int getActivityType() {
return mActivityType;
}
@Override
public Intent getIntent() {
return mIntent;
}
@Override
@Nullable
public CustomTabsSessionToken getSession() {
return mSession;
}
@Override
@Nullable
public Intent getKeepAliveServiceIntent() {
return mKeepAliveServiceIntent;
}
@Override
public boolean shouldAnimateOnFinish() {
return mAnimationBundle != null && getClientPackageName() != null;
}
@Override
public String getClientPackageName() {
if (mAnimationBundle == null) return null;
return mAnimationBundle.getString(BUNDLE_PACKAGE_NAME);
}
@Override
public int getAnimationEnterRes() {
return shouldAnimateOnFinish() ? mAnimationBundle.getInt(BUNDLE_ENTER_ANIMATION_RESOURCE)
: 0;
}
@Override
public int getAnimationExitRes() {
return shouldAnimateOnFinish() ? mAnimationBundle.getInt(BUNDLE_EXIT_ANIMATION_RESOURCE)
: 0;
}
@Deprecated
@Override
public boolean isTrustedIntent() {
return mIsTrustedIntent;
}
@Override
@Nullable
public String getUrlToLoad() {
if (mUrlToLoad == null) {
mUrlToLoad = resolveUrlToLoad(getIntent());
}
return mUrlToLoad;
}
@Override
public boolean shouldEnableUrlBarHiding() {
return mEnableUrlBarHiding;
}
@Override
public int getToolbarColor() {
return mColorProvider.getToolbarColor();
}
@Override
public boolean hasCustomToolbarColor() {
return mColorProvider.hasCustomToolbarColor();
}
@Override
@Nullable
public Integer getNavigationBarColor() {
return mColorProvider.getNavigationBarColor();
}
@Override
@Nullable
public Integer getNavigationBarDividerColor() {
return mColorProvider.getNavigationBarDividerColor();
}
@Override
@Nullable
public Drawable getCloseButtonDrawable() {
return mCloseButtonIcon;
}
@Override
public int getTitleVisibilityState() {
return mTitleVisibilityState;
}
@Override
public boolean shouldShowShareMenuItem() {
return mShowShareItemInMenu;
}
@Override
public List<CustomButtonParams> getCustomButtonsOnToolbar() {
return mToolbarButtons;
}
@Override
public List<CustomButtonParams> getCustomButtonsOnBottombar() {
return mBottombarButtons;
}
@Override
public int getBottomBarColor() {
return mColorProvider.getBottomBarColor();
}
@Override
@Nullable
public RemoteViews getBottomBarRemoteViews() {
return mRemoteViews;
}
@Override
@Nullable
public int[] getClickableViewIDs() {
if (mClickableViewIds == null) return null;
return mClickableViewIds.clone();
}
@Override
@Nullable
public PendingIntent getRemoteViewsPendingIntent() {
return mRemoteViewsPendingIntent;
}
@Override
public List<CustomButtonParams> getAllCustomButtons() {
return mCustomButtonParams;
}
@Override
public List<String> getMenuTitles() {
ArrayList<String> list = new ArrayList<>();
for (Pair<String, PendingIntent> 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.
*/
@VisibleForTesting
void setPendingIntentOnFinishedForTesting(PendingIntent.OnFinished onFinished) {
mOnFinished = onFinished;
}
@Override
public boolean isOpenedByChrome() {
return mIsOpenedByChrome;
}
@Override
@BrowserServicesIntentDataProvider.CustomTabsUiType
public int getUiType() {
return mUiType;
}
/**
* @return See {@link #EXTRA_MEDIA_VIEWER_URL}.
*/
@Override
@Nullable
public String getMediaViewerUrl() {
return mMediaViewerUrl;
}
@Override
public boolean shouldEnableEmbeddedMediaExperience() {
return mEnableEmbeddedMediaExperience;
}
@Override
public boolean isFromMediaLauncherActivity() {
return mIsFromMediaLauncherActivity;
}
@Override
public int getInitialBackgroundColor() {
return mColorProvider.getInitialBackgroundColor();
}
@Override
public boolean shouldShowStarButton() {
return !mDisableStar;
}
@Override
public boolean shouldShowDownloadButton() {
return !mDisableDownload;
}
@Override
public boolean isIncognito() {
return false;
}
@Nullable
@Override
public TrustedWebActivityDisplayMode getTwaDisplayMode() {
return mTrustedWebActivityDisplayMode;
}
@Override
@Nullable
public List<String> getTrustedWebActivityAdditionalOrigins() {
return mTrustedWebActivityAdditionalOrigins;
}
@Override
@Nullable
public String getTranslateLanguage() {
return mTranslateLanguage;
}
@Override
@Nullable
public 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
@Nullable
public 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;
}
}
@Override
@Nullable
public PendingIntent getFocusIntent() {
return mFocusIntent;
}
@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;
}
@Override
@Nullable
public int[] getGsaExperimentIds() {
return mGsaExperimentIds;
}
public static boolean shouldHideOmniboxSuggestionsForCctVisits(Intent intent) {
return intent.getBooleanExtra(EXTRA_HIDE_OMNIBOX_SUGGESTIONS_FROM_CCT, false)
&& ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_HIDE_VISITS_FROM_CCT);
}
@Override
public boolean shouldHideOmniboxSuggestionsForCctVisits() {
return shouldHideOmniboxSuggestionsForCctVisits(mIntent);
}
@Override
public boolean shouldHideCctVisits() {
if (!ChromeFeatureList.isEnabled(
ChromeFeatureList.HIDE_FROM_API_3_TRANSITIONS_FROM_HISTORY)) {
return false;
}
// Only 1p apps are allowed to hide visits.
String clientPackageName =
CustomTabsConnection.getInstance().getClientPackageNameForSession(getSession());
if (!GSAState.isGsaPackageName(clientPackageName)) return false;
return IntentUtils.safeGetBooleanExtra(mIntent, EXTRA_HIDE_VISITS_FROM_CCT, false);
}
@Override
public boolean shouldBlockNewNotificationRequests() {
// Only 1p apps are allowed to hide visits.
String clientPackageName =
CustomTabsConnection.getInstance().getClientPackageNameForSession(getSession());
if (!GSAState.isGsaPackageName(clientPackageName)) return false;
return IntentUtils.safeGetBooleanExtra(
mIntent, EXTRA_BLOCK_NEW_NOTIFICATION_REQUESTS_IN_CCT, false);
}
@Override
public boolean shouldShowOpenInChromeMenuItemInContextMenu() {
// Only 1p apps are allowed to hide visits.
String clientPackageName =
CustomTabsConnection.getInstance().getClientPackageNameForSession(getSession());
if (!GSAState.isGsaPackageName(clientPackageName)) return true;
return !IntentUtils.safeGetBooleanExtra(
mIntent, EXTRA_HIDE_OPEN_IN_CHROME_MENU_ITEM_IN_CONTEXT_MENU, false);
}
@Override
public boolean shouldShowOpenInChromeMenuItem() {
// Only 1p apps are allowed to hide visits.
String clientPackageName =
CustomTabsConnection.getInstance().getClientPackageNameForSession(getSession());
if (!GSAState.isGsaPackageName(clientPackageName)) return true;
return !IntentUtils.safeGetBooleanExtra(
mIntent, EXTRA_HIDE_OPEN_IN_CHROME_MENU_ITEM, false);
}
@Override
public boolean shouldAddCctClientDataHeader() {
return IntentUtils.safeGetBooleanExtra(mIntent, EXTRA_ADD_CCT_CLIENT_DATA_HEADER, false);
}
}