| // 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; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ResolveInfo; |
| import android.net.Uri; |
| import android.provider.Browser; |
| import android.support.annotation.Nullable; |
| import android.support.customtabs.CustomTabsIntent; |
| |
| import org.chromium.base.ApplicationStatus; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.browserservices.BrowserServicesMetrics; |
| import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient; |
| import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tabmodel.TabLaunchType; |
| import org.chromium.chrome.browser.tabmodel.document.AsyncTabCreationParams; |
| import org.chromium.chrome.browser.tabmodel.document.TabDelegate; |
| import org.chromium.chrome.browser.webapps.ChromeWebApkHost; |
| import org.chromium.chrome.browser.webapps.WebappDataStorage; |
| import org.chromium.chrome.browser.webapps.WebappRegistry; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.common.Referrer; |
| import org.chromium.content_public.common.ResourceRequestBody; |
| import org.chromium.ui.base.PageTransition; |
| import org.chromium.ui.mojom.WindowOpenDisposition; |
| import org.chromium.webapk.lib.client.WebApkIdentityServiceClient; |
| import org.chromium.webapk.lib.client.WebApkNavigationClient; |
| import org.chromium.webapk.lib.client.WebApkValidator; |
| |
| import java.util.List; |
| |
| /** |
| * Tab Launcher to be used to launch new tabs from background Android Services, |
| * when it is not known whether an activity is available. It will send an intent to launch the |
| * activity. |
| * |
| * URLs within the scope of a recently launched standalone-capable web app on the Android home |
| * screen are launched in the standalone web app frame. |
| */ |
| public class ServiceTabLauncher { |
| // Name of the extra containing the Id of a tab launch request id. |
| public static final String LAUNCH_REQUEST_ID_EXTRA = |
| "org.chromium.chrome.browser.ServiceTabLauncher.LAUNCH_REQUEST_ID"; |
| |
| /** |
| * Launches the browser activity and launches a tab for |url|. |
| * |
| * @param requestId Id of the request for launching this tab. |
| * @param incognito Whether the tab should be launched in incognito mode. |
| * @param url The URL which should be launched in a tab. |
| * @param disposition The disposition requested by the navigation source. |
| * @param referrerUrl URL of the referrer which is opening the page. |
| * @param referrerPolicy The referrer policy to consider when applying the referrer. |
| * @param extraHeaders Extra headers to apply when requesting the tab's URL. |
| * @param postData Post-data to include in the tab URL's request body. |
| */ |
| @CalledByNative |
| public static void launchTab(final int requestId, boolean incognito, String url, |
| int disposition, String referrerUrl, int referrerPolicy, String extraHeaders, |
| ResourceRequestBody postData) { |
| // Open popup window in custom tab. |
| // Note that this is used by PaymentRequestEvent.openWindow(). |
| if (disposition == WindowOpenDisposition.NEW_POPUP) { |
| if (!createPopupCustomTab(requestId, url, incognito)) { |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, |
| () -> onWebContentsForRequestAvailable(requestId, null)); |
| } |
| return; |
| } |
| |
| dispatchLaunch( |
| requestId, incognito, url, referrerUrl, referrerPolicy, extraHeaders, postData); |
| } |
| |
| /** Dispatches the launch event. */ |
| private static void dispatchLaunch(final int requestId, final boolean incognito, |
| final String url, final String referrerUrl, final int referrerPolicy, |
| final String extraHeaders, final ResourceRequestBody postData) { |
| Context context = ContextUtils.getApplicationContext(); |
| |
| List<ResolveInfo> resolveInfos; |
| try (BrowserServicesMetrics.TimingMetric t = |
| BrowserServicesMetrics.getServiceTabResolveInfoTimingContext()) { |
| resolveInfos = WebApkValidator.resolveInfosForUrl(context, url); |
| } |
| String webApkPackageName = WebApkValidator.findFirstWebApkPackage(context, resolveInfos); |
| |
| if (webApkPackageName != null) { |
| final List<ResolveInfo> resolveInfosFinal = resolveInfos; |
| WebApkIdentityServiceClient.CheckBrowserBacksWebApkCallback callback = |
| doesBrowserBackWebApk -> { |
| if (doesBrowserBackWebApk) { |
| Intent intent = WebApkNavigationClient.createLaunchWebApkIntent( |
| webApkPackageName, url, true /* forceNavigation */); |
| intent.putExtra( |
| ShortcutHelper.EXTRA_SOURCE, ShortcutSource.NOTIFICATION); |
| ContextUtils.getApplicationContext().startActivity(intent); |
| return; |
| } |
| launchTabOrWebapp(requestId, incognito, url, referrerUrl, |
| referrerPolicy, extraHeaders, postData, resolveInfosFinal); |
| }; |
| ChromeWebApkHost.checkChromeBacksWebApkAsync(webApkPackageName, callback); |
| return; |
| } |
| |
| launchTabOrWebapp(requestId, incognito, url, referrerUrl, referrerPolicy, extraHeaders, |
| postData, resolveInfos); |
| } |
| |
| /** Launches WebappActivity or a tab for the |url|. */ |
| private static void launchTabOrWebapp(int requestId, boolean incognito, String url, |
| String referrerUrl, int referrerPolicy, String extraHeaders, |
| ResourceRequestBody postData, List<ResolveInfo> resolveInfosForUrl) { |
| // Launch WebappActivity if one matches the target URL and was opened recently. |
| // Otherwise, open the URL in a tab. |
| WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorageForUrl(url); |
| TabDelegate tabDelegate = new TabDelegate(incognito); |
| |
| // Launch into a TrustedWebActivity if one exists for the URL. |
| Context appContext = ContextUtils.getApplicationContext(); |
| if (!incognito) { |
| Intent twaIntent = TrustedWebActivityClient |
| .createLaunchIntentForTwa(appContext, url, resolveInfosForUrl); |
| |
| if (twaIntent != null) { |
| appContext.startActivity(twaIntent); |
| return; |
| } |
| } |
| |
| // Open a new tab if: |
| // - We did not find a WebappDataStorage corresponding to this URL. |
| // OR |
| // - The WebappDataStorage hasn't been opened recently enough. |
| if (storage == null || !storage.wasUsedRecently()) { |
| LoadUrlParams loadUrlParams = new LoadUrlParams(url, PageTransition.LINK); |
| loadUrlParams.setPostData(postData); |
| loadUrlParams.setVerbatimHeaders(extraHeaders); |
| loadUrlParams.setReferrer(new Referrer(referrerUrl, referrerPolicy)); |
| |
| AsyncTabCreationParams asyncParams = new AsyncTabCreationParams(loadUrlParams, |
| requestId); |
| tabDelegate.createNewTab(asyncParams, TabLaunchType.FROM_CHROME_UI, Tab.INVALID_TAB_ID); |
| } else { |
| // The URL is within the scope of a recently launched standalone-capable web app |
| // on the home screen, so open it a standalone web app frame. |
| // |
| // This currently assumes that the only source is notifications; any future use |
| // which adds a different source will need to change this. |
| Intent intent = storage.createWebappLaunchIntent(); |
| // Replace the web app URL with the URL from the notification. This is within the |
| // webapp's scope, so it is valid. |
| intent.putExtra(ShortcutHelper.EXTRA_URL, url); |
| intent.putExtra(ShortcutHelper.EXTRA_SOURCE, ShortcutSource.NOTIFICATION); |
| intent.putExtra(ShortcutHelper.EXTRA_FORCE_NAVIGATION, true); |
| tabDelegate.createNewStandaloneFrame(intent); |
| } |
| } |
| |
| /** |
| * Creates a popup custom tab to open the url. The popup tab is animated in from bottom to top |
| * and out from top to bottom. |
| * Note that this is used by PaymentRequestEvent.openWindow(). |
| * |
| * @param requestId The tab launch request ID from the {@link ServiceTabLauncher}. |
| * @param url The url to open in the new tab. |
| */ |
| private static boolean createPopupCustomTab(int requestId, String url, boolean incognito) { |
| // Do not open the popup custom tab if the chrome activity is not in the forground. |
| Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFocusedActivity(); |
| if (!(lastTrackedActivity instanceof ChromeActivity)) return false; |
| |
| CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); |
| builder.setShowTitle(true); |
| builder.setStartAnimations(lastTrackedActivity, R.anim.slide_in_up, 0); |
| builder.setExitAnimations(lastTrackedActivity, 0, R.anim.slide_out_down); |
| CustomTabsIntent customTabsIntent = builder.build(); |
| customTabsIntent.intent.setPackage(ContextUtils.getApplicationContext().getPackageName()); |
| customTabsIntent.intent.putExtra(ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA, requestId); |
| customTabsIntent.intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, incognito); |
| customTabsIntent.intent.putExtra(Browser.EXTRA_APPLICATION_ID, |
| ContextUtils.getApplicationContext().getPackageName()); |
| |
| // Customize items on menu as payment request UI to show 'Find in page', 'Forward arrow', |
| // 'Info' and 'Refresh' only. |
| CustomTabIntentDataProvider.addPaymentRequestUIExtras(customTabsIntent.intent); |
| |
| customTabsIntent.launchUrl(lastTrackedActivity, Uri.parse(url)); |
| |
| return true; |
| } |
| |
| /** |
| * To be called by the activity when the WebContents for |requestId| has been created, or has |
| * been recycled from previous use. The |webContents| must not yet have started provisional |
| * load for the main frame. |
| * The |webContents| could be null if the request is failed. |
| * |
| * @param requestId Id of the tab launching request which has been fulfilled. |
| * @param webContents The WebContents instance associated with this request. |
| */ |
| public static void onWebContentsForRequestAvailable( |
| int requestId, @Nullable WebContents webContents) { |
| nativeOnWebContentsForRequestAvailable(requestId, webContents); |
| } |
| |
| private static native void nativeOnWebContentsForRequestAvailable( |
| int requestId, WebContents webContents); |
| } |