// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.customtabs; import android.app.PendingIntent; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.StrictMode; import android.support.customtabs.CustomTabsCallback; import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsSessionToken; import android.support.v4.app.ActivityOptionsCompat; import android.text.TextUtils; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.widget.RemoteViews; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.IntentHandler.ExternalAppId; import org.chromium.chrome.browser.KeyboardShortcuts; import org.chromium.chrome.browser.WarmupManager; import org.chromium.chrome.browser.WebContentsFactory; import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerDocument; import org.chromium.chrome.browser.datausage.DataUseTabUIManager; import org.chromium.chrome.browser.document.ChromeLauncherActivity; import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl; import org.chromium.chrome.browser.metrics.PageLoadMetrics; import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; import org.chromium.chrome.browser.pageinfo.WebsiteSettingsPopup; import org.chromium.chrome.browser.rappor.RapporServiceBridge; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabDelegateFactory; import org.chromium.chrome.browser.tab.TabIdManager; import org.chromium.chrome.browser.tabmodel.ChromeTabCreator; import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; import org.chromium.chrome.browser.tabmodel.TabModelObserver; import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl; import org.chromium.chrome.browser.tabmodel.TabPersistencePolicy; import org.chromium.chrome.browser.toolbar.ToolbarControlContainer; import org.chromium.chrome.browser.util.ColorUtils; import org.chromium.chrome.browser.util.UrlUtilities; import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager; import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.common.Referrer; import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.WindowAndroid; /** * The activity for custom tabs. It will be launched on top of a client's task. */ public class CustomTabActivity extends ChromeActivity { private static final String TAG = "CustomTabActivity"; private static final String LAST_URL_PREF = "pref_last_custom_tab_url"; private static CustomTabContentHandler sActiveContentHandler; private FindToolbarManager mFindToolbarManager; private CustomTabIntentDataProvider mIntentDataProvider; private CustomTabsSessionToken mSession; private CustomTabContentHandler mCustomTabContentHandler; private Tab mMainTab; private CustomTabBottomBarDelegate mBottomBarDelegate; // This is to give the right package name while using the client's resources during an // overridePendingTransition call. // TODO(ianwen, yusufo): Figure out a solution to extract external resources without having to // change the package name. private boolean mShouldOverridePackage; private boolean mHasCreatedTabEarly; private boolean mIsInitialStart = true; // Whether there is any prerender associated with the session. private boolean mHasPrerender; private CustomTabObserver mTabObserver; private String mPrerenderedUrl; // Whether a prerender is being used. private boolean mHasPrerendered; private boolean mIsClosing; private static class PageLoadMetricsObserver implements PageLoadMetrics.Observer { private final CustomTabsConnection mConnection; private final CustomTabsSessionToken mSession; private final Tab mTab; public PageLoadMetricsObserver(CustomTabsConnection connection, CustomTabsSessionToken session, Tab tab) { mConnection = connection; mSession = session; mTab = tab; } @Override public void onFirstContentfulPaint(WebContents webContents, long firstContentfulPaintMs) { if (webContents != mTab.getWebContents()) return; mConnection.notifyPageLoadMetric( mSession, PageLoadMetrics.FIRST_CONTENTFUL_PAINT, firstContentfulPaintMs); } } private static class CustomTabCreator extends ChromeTabCreator { private final boolean mSupportsUrlBarHiding; private final boolean mIsOpenedByChrome; public CustomTabCreator( ChromeActivity activity, WindowAndroid nativeWindow, boolean incognito, boolean supportsUrlBarHiding, boolean isOpenedByChrome) { super(activity, nativeWindow, incognito); mSupportsUrlBarHiding = supportsUrlBarHiding; mIsOpenedByChrome = isOpenedByChrome; } @Override public TabDelegateFactory createDefaultTabDelegateFactory() { return new CustomTabDelegateFactory(mSupportsUrlBarHiding, mIsOpenedByChrome); } } private PageLoadMetricsObserver mMetricsObserver; // Only the normal tab model is observed because there is no incognito tabmodel in Custom Tabs. private TabModelObserver mTabModelObserver = new EmptyTabModelObserver() { @Override public void didAddTab(Tab tab, TabLaunchType type) { PageLoadMetrics.addObserver(mMetricsObserver); tab.addObserver(mTabObserver); } @Override public void didCloseTab(int tabId, boolean incognito) { PageLoadMetrics.removeObserver(mMetricsObserver); // Finish the activity after we intent out. if (getTabModelSelector().getCurrentModel().getCount() == 0) finish(); } }; /** * Sets the currently active {@link CustomTabContentHandler} in focus. * @param contentHandler {@link CustomTabContentHandler} to set. */ public static void setActiveContentHandler(CustomTabContentHandler contentHandler) { sActiveContentHandler = contentHandler; } /** * Used to check whether an incoming intent can be handled by the * current {@link CustomTabContentHandler}. * @return Whether the active {@link CustomTabContentHandler} has handled the intent. */ public static boolean handleInActiveContentIfNeeded(Intent intent) { if (sActiveContentHandler == null) return false; if (sActiveContentHandler.shouldIgnoreIntent(intent)) { Log.w(TAG, "Incoming intent to Custom Tab was ignored."); return false; } CustomTabsSessionToken session = CustomTabsSessionToken.getSessionTokenFromIntent(intent); if (session == null || !session.equals(sActiveContentHandler.getSession())) return false; String url = IntentHandler.getUrlFromIntent(intent); if (TextUtils.isEmpty(url)) return false; sActiveContentHandler.loadUrlAndTrackFromTimestamp(new LoadUrlParams(url), IntentHandler.getTimestampFromIntent(intent)); return true; } /** * Checks whether the active {@link CustomTabContentHandler} belongs to the given session, and * if true, update toolbar's custom button. * @param session The {@link IBinder} that the calling client represents. * @param bitmap The new icon for action button. * @param description The new content description for the action button. * @return Whether the update is successful. */ static boolean updateCustomButton( CustomTabsSessionToken session, int id, Bitmap bitmap, String description) { ThreadUtils.assertOnUiThread(); // Do nothing if there is no activity or the activity does not belong to this session. if (sActiveContentHandler == null || !sActiveContentHandler.getSession().equals(session)) { return false; } return sActiveContentHandler.updateCustomButton(id, bitmap, description); } /** * Checks whether the active {@link CustomTabContentHandler} belongs to the given session, and * if true, updates {@link RemoteViews} on the secondary toolbar. * @return Whether the update is successful. */ static boolean updateRemoteViews( CustomTabsSessionToken session, RemoteViews remoteViews, int[] clickableIDs, PendingIntent pendingIntent) { ThreadUtils.assertOnUiThread(); // Do nothing if there is no activity or the activity does not belong to this session. if (sActiveContentHandler == null || !sActiveContentHandler.getSession().equals(session)) { return false; } return sActiveContentHandler.updateRemoteViews(remoteViews, clickableIDs, pendingIntent); } @Override public boolean isCustomTab() { return true; } @Override public void onStart() { super.onStart(); mIsClosing = false; CustomTabsConnection.getInstance(getApplication()) .keepAliveForSession(mIntentDataProvider.getSession(), mIntentDataProvider.getKeepAliveServiceIntent()); } @Override public void onStop() { super.onStop(); CustomTabsConnection.getInstance(getApplication()) .dontKeepAliveForSession(mIntentDataProvider.getSession()); } @Override public void preInflationStartup() { super.preInflationStartup(); mIntentDataProvider = new CustomTabIntentDataProvider(getIntent(), this); mSession = mIntentDataProvider.getSession(); supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY); mHasPrerender = !TextUtils.isEmpty( CustomTabsConnection.getInstance(getApplication()).getPrerenderedUrl(mSession)); if (getSavedInstanceState() == null && CustomTabsConnection.hasWarmUpBeenFinished(getApplication())) { mMainTab = createMainTab(); loadUrlInTab(mMainTab, new LoadUrlParams(getUrlToLoad()), IntentHandler.getTimestampFromIntent(getIntent())); mHasCreatedTabEarly = true; } } @Override public boolean shouldAllocateChildConnection() { return !mHasCreatedTabEarly && !mHasPrerender && !WarmupManager.getInstance().hasSpareWebContents(); } @Override public void postInflationStartup() { super.postInflationStartup(); TabPersistencePolicy persistencePolicy = new CustomTabTabPersistencePolicy( getTaskId(), getSavedInstanceState() != null); setTabModelSelector(new TabModelSelectorImpl( this, persistencePolicy, getWindowAndroid(), false)); setTabCreators( new CustomTabCreator( this, getWindowAndroid(), false, mIntentDataProvider.shouldEnableUrlBarHiding(), mIntentDataProvider.isOpenedByChrome()), new CustomTabCreator( this, getWindowAndroid(), true, mIntentDataProvider.shouldEnableUrlBarHiding(), mIntentDataProvider.isOpenedByChrome())); getToolbarManager().setCloseButtonDrawable(mIntentDataProvider.getCloseButtonDrawable()); getToolbarManager().setShowTitle(mIntentDataProvider.getTitleVisibilityState() == CustomTabsIntent.SHOW_PAGE_TITLE); if (CustomTabsConnection.getInstance(getApplication()) .shouldHideDomainForSession(mSession)) { getToolbarManager().setUrlBarHidden(true); } int toolbarColor = mIntentDataProvider.getToolbarColor(); getToolbarManager().updatePrimaryColor(toolbarColor, false); if (!mIntentDataProvider.isOpenedByChrome()) { getToolbarManager().setShouldUpdateToolbarPrimaryColor(false); } if (toolbarColor != ApiCompatibilityUtils.getColor( getResources(), R.color.default_primary_color)) { ApiCompatibilityUtils.setStatusBarColor(getWindow(), ColorUtils.getDarkenedColorForStatusBar(toolbarColor)); } // Setting task title and icon to be null will preserve the client app's title and icon. ApiCompatibilityUtils.setTaskDescription(this, null, null, toolbarColor); showCustomButtonOnToolbar(); mBottomBarDelegate = new CustomTabBottomBarDelegate(this, mIntentDataProvider); mBottomBarDelegate.showBottomBarIfNecessary(); } @Override public void finishNativeInitialization() { final CustomTabsConnection connection = CustomTabsConnection.getInstance(getApplication()); // If extra headers have been passed, cancel any current prerender, as // prerendering doesn't support extra headers. if (IntentHandler.getExtraHeadersFromIntent(getIntent()) != null) { connection.cancelPrerender(mSession); } getTabModelSelector().getModel(false).addObserver(mTabModelObserver); boolean successfulStateRestore = false; // Attempt to restore the previous tab state if applicable. if (getSavedInstanceState() != null) { assert mMainTab == null; getTabModelSelector().loadState(true); getTabModelSelector().restoreTabs(true); mMainTab = getTabModelSelector().getCurrentTab(); successfulStateRestore = mMainTab != null; if (successfulStateRestore) initializeMainTab(mMainTab); } // If no tab was restored, create a new tab. if (!successfulStateRestore) { if (mHasCreatedTabEarly) { // When the tab is created early, we don't have the TabContentManager connected, // since compositor related controllers were not initialized at that point. mMainTab.attachTabContentManager(getTabContentManager()); } else { mMainTab = createMainTab(); } getTabModelSelector().getModel(false).addTab(mMainTab, 0, mMainTab.getLaunchType()); } ToolbarControlContainer controlContainer = (ToolbarControlContainer) findViewById( R.id.control_container); LayoutManagerDocument layoutDriver = new CustomTabLayoutManager(getCompositorViewHolder()); initializeCompositorContent(layoutDriver, findViewById(R.id.url_bar), (ViewGroup) findViewById(android.R.id.content), controlContainer); mFindToolbarManager = new FindToolbarManager(this, getToolbarManager().getActionModeController().getActionModeCallback()); if (getContextualSearchManager() != null) { getContextualSearchManager().setFindToolbarManager(mFindToolbarManager); } getToolbarManager().initializeWithNative(getTabModelSelector(), getFullscreenManager(), mFindToolbarManager, null, layoutDriver, null, null, null, new OnClickListener() { @Override public void onClick(View v) { RecordUserAction.record("CustomTabs.CloseButtonClicked"); finishAndClose(); } }); mMainTab.setFullscreenManager(getFullscreenManager()); mCustomTabContentHandler = new CustomTabContentHandler() { @Override public void loadUrlAndTrackFromTimestamp(LoadUrlParams params, long timestamp) { if (!TextUtils.isEmpty(params.getUrl())) { params.setUrl(DataReductionProxySettings.getInstance() .maybeRewriteWebliteUrl(params.getUrl())); } loadUrlInTab(getActivityTab(), params, timestamp); } @Override public CustomTabsSessionToken getSession() { return mSession; } @Override public boolean shouldIgnoreIntent(Intent intent) { return mIntentHandler.shouldIgnoreIntent(CustomTabActivity.this, intent); } @Override public boolean updateCustomButton(int id, Bitmap bitmap, String description) { CustomButtonParams params = mIntentDataProvider.getButtonParamsForId(id); if (params == null) return false; params.update(bitmap, description); if (params.showOnToolbar()) { if (!CustomButtonParams.doesIconFitToolbar(CustomTabActivity.this, bitmap)) { return false; } showCustomButtonOnToolbar(); } else { if (mBottomBarDelegate != null) { mBottomBarDelegate.updateBottomBarButtons(params); } } return true; } @Override public boolean updateRemoteViews(RemoteViews remoteViews, int[] clickableIDs, PendingIntent pendingIntent) { if (mBottomBarDelegate == null) return false; return mBottomBarDelegate.updateRemoteViews(remoteViews, clickableIDs, pendingIntent); } }; recordClientPackageName(); connection.showSignInToastIfNecessary(mSession, getIntent()); String url = getUrlToLoad(); String packageName = connection.getClientPackageNameForSession(mSession); if (TextUtils.isEmpty(packageName)) { packageName = connection.extractCreatorPackage(getIntent()); } DataUseTabUIManager.onCustomTabInitialNavigation(mMainTab, packageName, url); if (!mHasCreatedTabEarly && !successfulStateRestore) { loadUrlInTab(mMainTab, new LoadUrlParams(url), IntentHandler.getTimestampFromIntent(getIntent())); } // Put Sync in the correct state by calling tab state initialized. crbug.com/581811. getTabModelSelector().markTabStateInitialized(); SharedPreferences preferences = ContextUtils.getAppSharedPreferences(); String lastUrl = preferences.getString(LAST_URL_PREF, null); if (lastUrl != null && lastUrl.equals(getUrlToLoad())) { RecordUserAction.record("CustomTabsMenuOpenSameUrl"); } else { preferences.edit().putString(LAST_URL_PREF, getUrlToLoad()).apply(); } super.finishNativeInitialization(); } private Tab createMainTab() { CustomTabsConnection customTabsConnection = CustomTabsConnection.getInstance(getApplication()); String url = getUrlToLoad(); // Get any referrer that has been explicitly set by the app. String referrerUrl = IntentHandler.getReferrerUrlIncludingExtraHeaders(getIntent(), this); if (referrerUrl == null) { Referrer referrer = customTabsConnection.getReferrerForSession(mSession); if (referrer != null) referrerUrl = referrer.getUrl(); } Tab tab = new Tab(TabIdManager.getInstance().generateValidId(Tab.INVALID_TAB_ID), Tab.INVALID_TAB_ID, false, this, getWindowAndroid(), TabLaunchType.FROM_EXTERNAL_APP, null, null); tab.setAppAssociatedWith(customTabsConnection.getClientPackageNameForSession(mSession)); mPrerenderedUrl = customTabsConnection.getPrerenderedUrl(mSession); WebContents webContents = customTabsConnection.takePrerenderedUrl(mSession, url, referrerUrl); mHasPrerendered = webContents != null; if (!mHasPrerendered) { webContents = WarmupManager.getInstance().takeSpareWebContents(false, false); } if (webContents == null) webContents = WebContentsFactory.createWebContents(false, false); tab.initialize( webContents, getTabContentManager(), new CustomTabDelegateFactory( mIntentDataProvider.shouldEnableUrlBarHiding(), mIntentDataProvider.isOpenedByChrome()), false, false); initializeMainTab(tab); return tab; } private void initializeMainTab(Tab tab) { tab.getTabRedirectHandler().updateIntent(getIntent()); tab.getView().requestFocus(); mTabObserver = new CustomTabObserver( getApplication(), mSession, mIntentDataProvider.isOpenedByChrome()); mMetricsObserver = new PageLoadMetricsObserver( CustomTabsConnection.getInstance(getApplication()), mSession, tab); tab.addObserver(mTabObserver); } @Override public void initializeCompositor() { super.initializeCompositor(); getTabModelSelector().onNativeLibraryReady(getTabContentManager()); } private void recordClientPackageName() { String clientName = CustomTabsConnection.getInstance(getApplication()) .getClientPackageNameForSession(mSession); if (TextUtils.isEmpty(clientName)) clientName = mIntentDataProvider.getClientPackageName(); final String packageName = clientName; if (TextUtils.isEmpty(packageName) || packageName.contains(getPackageName())) return; ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { RapporServiceBridge.sampleString( "CustomTabs.ServiceClient.PackageName", packageName); } }); } @Override public void onStartWithNative() { super.onStartWithNative(); setActiveContentHandler(mCustomTabContentHandler); if (getSavedInstanceState() != null || !mIsInitialStart) { if (mIntentDataProvider.isOpenedByChrome()) { RecordUserAction.record("ChromeGeneratedCustomTab.StartedReopened"); } else { RecordUserAction.record("CustomTabs.StartedReopened"); } } else { if (mIntentDataProvider.isOpenedByChrome()) { RecordUserAction.record("ChromeGeneratedCustomTab.StartedInitially"); } else { ExternalAppId externalId = IntentHandler.determineExternalIntentSource(getPackageName(), getIntent()); RecordHistogram.recordEnumeratedHistogram("CustomTabs.ClientAppId", externalId.ordinal(), ExternalAppId.INDEX_BOUNDARY.ordinal()); RecordUserAction.record("CustomTabs.StartedInitially"); } } if (mHasCreatedTabEarly && !mMainTab.isLoading()) postDeferredStartupIfNeeded(); mIsInitialStart = false; } @Override public void onPauseWithNative() { super.onPauseWithNative(); CustomTabsConnection.getInstance(getApplication()).notifyNavigationEvent( mSession, CustomTabsCallback.TAB_HIDDEN); } @Override public void onStopWithNative() { super.onStopWithNative(); setActiveContentHandler(null); if (!mIsClosing) getTabModelSelector().saveState(); } /** * Loads the current tab with the given load params while taking client * referrer and extra headers into account. */ private void loadUrlInTab(final Tab tab, final LoadUrlParams params, long timeStamp) { Intent intent = getIntent(); String url = getUrlToLoad(); if (mHasPrerendered && UrlUtilities.urlsFragmentsDiffer(mPrerenderedUrl, url)) { mHasPrerendered = false; LoadUrlParams temporaryParams = new LoadUrlParams(mPrerenderedUrl); IntentHandler.addReferrerAndHeaders(temporaryParams, intent, this); tab.loadUrl(temporaryParams); params.setShouldReplaceCurrentEntry(true); } IntentHandler.addReferrerAndHeaders(params, intent, this); if (params.getReferrer() == null) { params.setReferrer(CustomTabsConnection.getInstance(getApplication()) .getReferrerForSession(mSession)); } // See ChromeTabCreator#getTransitionType(). This marks the navigation chain as starting // from an external intent (unless otherwise specified by an extra in the intent). params.setTransitionType(IntentHandler.getTransitionTypeFromIntent(this, intent, PageTransition.LINK | PageTransition.FROM_API)); mTabObserver.trackNextPageLoadFromTimestamp(timeStamp); tab.loadUrl(params); } @Override public void createContextualSearchTab(String searchUrl) { if (getActivityTab() == null) return; getActivityTab().loadUrl(new LoadUrlParams(searchUrl)); } @Override public TabModelSelectorImpl getTabModelSelector() { return (TabModelSelectorImpl) super.getTabModelSelector(); } @Override public Tab getActivityTab() { Tab tab = super.getActivityTab(); if (tab == null) tab = mMainTab; return tab; } @Override protected AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() { return new CustomTabAppMenuPropertiesDelegate(this, mIntentDataProvider.getMenuTitles(), mIntentDataProvider.shouldShowShareMenuItem(), mIntentDataProvider.isOpenedByChrome(), mIntentDataProvider.isMediaViewer()); } @Override protected int getAppMenuLayoutId() { return R.menu.custom_tabs_menu; } @Override protected int getControlContainerLayoutId() { return R.layout.custom_tabs_control_container; } @Override public int getControlContainerHeightResource() { return R.dimen.custom_tabs_control_container_height; } @Override public String getPackageName() { if (mShouldOverridePackage) return mIntentDataProvider.getClientPackageName(); return super.getPackageName(); } @Override public void finish() { // Prevent the menu window from leaking. if (getAppMenuHandler() != null) getAppMenuHandler().hideAppMenu(); super.finish(); if (mIntentDataProvider != null && mIntentDataProvider.shouldAnimateOnFinish()) { mShouldOverridePackage = true; overridePendingTransition(mIntentDataProvider.getAnimationEnterRes(), mIntentDataProvider.getAnimationExitRes()); mShouldOverridePackage = false; } else if (mIntentDataProvider != null && mIntentDataProvider.isOpenedByChrome()) { overridePendingTransition(R.anim.no_anim, R.anim.activity_close_exit); } } /** * Finishes the activity and removes the reference from the Android recents. */ public final void finishAndClose() { mIsClosing = true; handleFinishAndClose(); } /** * Internal implementation that finishes the activity and removes the references from Android * recents. */ protected void handleFinishAndClose() { // When on top of another app, finish is all that is required. finish(); } @Override protected boolean handleBackPressed() { RecordUserAction.record("CustomTabs.SystemBack"); if (getActivityTab() == null) return false; if (exitFullscreenIfShowing()) return true; if (!getToolbarManager().back()) { if (getCurrentTabModel().getCount() > 1) { getCurrentTabModel().closeTab(getActivityTab(), false, false, false); } else { finishAndClose(); } } return true; } /** * Configures the custom button on toolbar. Does nothing if invalid data is provided by clients. */ private void showCustomButtonOnToolbar() { final CustomButtonParams params = mIntentDataProvider.getCustomButtonOnToolbar(); if (params == null) return; getToolbarManager().setCustomActionButton( params.getIcon(getResources()), params.getDescription(), new OnClickListener() { @Override public void onClick(View v) { mIntentDataProvider.sendButtonPendingIntentWithUrl( getApplicationContext(), getActivityTab().getUrl()); RecordUserAction.record("CustomTabsCustomActionButtonClick"); } }); } @Override public boolean shouldShowAppMenu() { return getActivityTab() != null && getToolbarManager().isInitialized(); } @Override protected void showAppMenuForKeyboardEvent() { if (!shouldShowAppMenu()) return; super.showAppMenuForKeyboardEvent(); } @Override public boolean onOptionsItemSelected(MenuItem item) { int menuIndex = getAppMenuPropertiesDelegate().getIndexOfMenuItem(item); if (menuIndex >= 0) { mIntentDataProvider.clickMenuItemWithUrl(this, menuIndex, getTabModelSelector().getCurrentTab().getUrl()); RecordUserAction.record("CustomTabsMenuCustomMenuItem"); return true; } return super.onOptionsItemSelected(item); } @Override public boolean dispatchKeyEvent(KeyEvent event) { Boolean result = KeyboardShortcuts.dispatchKeyEvent(event, this, getToolbarManager().isInitialized()); return result != null ? result : super.dispatchKeyEvent(event); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (!getToolbarManager().isInitialized()) { return super.onKeyDown(keyCode, event); } return KeyboardShortcuts.onKeyDown(event, this, true, false) || super.onKeyDown(keyCode, event); } @Override public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) { // Disable creating new tabs, bookmark, history, print, help, focus_url, etc. if (id == R.id.focus_url_bar || id == R.id.all_bookmarks_menu_id || id == R.id.help_id || id == R.id.recent_tabs_menu_id || id == R.id.new_incognito_tab_menu_id || id == R.id.new_tab_menu_id || id == R.id.open_history_menu_id) { return true; } else if (id == R.id.open_in_browser_id) { openCurrentUrlInBrowser(false); RecordUserAction.record("CustomTabsMenuOpenInChrome"); return true; } else if (id == R.id.info_menu_id) { if (getTabModelSelector().getCurrentTab() == null) return false; WebsiteSettingsPopup.show( this, getTabModelSelector().getCurrentTab(), getToolbarManager().getContentPublisher(), WebsiteSettingsPopup.OPENED_FROM_MENU); return true; } return super.onMenuOrKeyboardAction(id, fromMenu); } @Override protected void setStatusBarColor(Tab tab, int color) { // Intentionally do nothing as CustomTabActivity explicitly sets status bar color. Except // for Custom Tabs opened by Chrome. if (mIntentDataProvider.isOpenedByChrome()) super.setStatusBarColor(tab, color); } /** * @return The {@link AppMenuPropertiesDelegate} associated with this activity. For test * purposes only. */ @VisibleForTesting @Override public CustomTabAppMenuPropertiesDelegate getAppMenuPropertiesDelegate() { return (CustomTabAppMenuPropertiesDelegate) super.getAppMenuPropertiesDelegate(); } @Override public void onCheckForUpdate(boolean updateAvailable) { } /** * @return The {@link CustomTabIntentDataProvider} for this {@link CustomTabActivity}. For test * purposes only. */ @VisibleForTesting CustomTabIntentDataProvider getIntentDataProvider() { return mIntentDataProvider; } /** * Opens the URL currently being displayed in the Custom Tab in the regular browser. * @param forceReparenting Whether tab reparenting should be forced for testing. * * @return Whether or not the tab was sent over successfully. */ boolean openCurrentUrlInBrowser(boolean forceReparenting) { Tab tab = getActivityTab(); if (tab == null) return false; String url = tab.getUrl(); if (DomDistillerUrlUtils.isDistilledPage(url)) { url = DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(url); } if (TextUtils.isEmpty(url)) url = getUrlToLoad(); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(ChromeLauncherActivity.EXTRA_IS_ALLOWED_TO_RETURN_TO_PARENT, false); boolean willChromeHandleIntent = getIntentDataProvider().isOpenedByChrome(); StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); StrictMode.allowThreadDiskWrites(); try { willChromeHandleIntent |= ExternalNavigationDelegateImpl .willChromeHandleIntent(this, intent, true); } finally { StrictMode.setThreadPolicy(oldPolicy); } Bundle startActivityOptions = ActivityOptionsCompat.makeCustomAnimation( this, R.anim.abc_fade_in, R.anim.abc_fade_out).toBundle(); if (willChromeHandleIntent || forceReparenting) { Runnable finalizeCallback = new Runnable() { @Override public void run() { finishAndClose(); } }; mMainTab = null; tab.detachAndStartReparenting(intent, startActivityOptions, finalizeCallback); } else { // Temporarily allowing disk access while fixing. TODO: http://crbug.com/581860 StrictMode.allowThreadDiskReads(); StrictMode.allowThreadDiskWrites(); try { startActivity(intent, startActivityOptions); } finally { StrictMode.setThreadPolicy(oldPolicy); } } return true; } /** * @return The URL that should be used from this intent. If it is a WebLite url, it may be * overridden if the Data Reduction Proxy is using Lo-Fi previews. */ private String getUrlToLoad() { String url = IntentHandler.getUrlFromIntent(getIntent()); if (!TextUtils.isEmpty(url)) { url = DataReductionProxySettings.getInstance().maybeRewriteWebliteUrl(url); } return url; } }