chromium/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/ChromeAsyncTabLauncher.java

// Copyright 2014 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.tabmodel.document;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Browser;

import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.chrome.browser.ActivityUtils;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.ServiceTabLauncher;
import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabIdManager;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.AsyncTabCreationParams;
import org.chromium.chrome.browser.tabmodel.AsyncTabLauncher;
import org.chromium.content_public.browser.LoadUrlParams;

/** Asynchronously creates Tabs by creating/starting up Activities. */
public class ChromeAsyncTabLauncher implements AsyncTabLauncher {
    private final boolean mIsIncognito;

    /**
     * Creates a TabDelegate.
     * @param incognito Whether or not the TabDelegate handles the creation of incognito tabs.
     */
    public ChromeAsyncTabLauncher(boolean incognito) {
        mIsIncognito = incognito;
    }

    /**
     * @return Running Activity that owns the given Tab, null if the Activity couldn't be found.
     */
    private static Activity getActivityForTabId(int id) {
        if (id == Tab.INVALID_TAB_ID) return null;

        Tab tab = TabWindowManagerSingleton.getInstance().getTabById(id);
        if (tab == null) return null;

        Context tabContext = tab.getContext();
        return (tabContext instanceof Activity) ? (Activity) tabContext : null;
    }

    /**
     * Creates a tab in the "other" window in multi-window mode. This will only work if
     * MultiWindowUtils#isOpenInOtherWindowSupported() is true for the given activity.
     *
     * @param loadUrlParams Parameters specifying the URL to load and other navigation details.
     * @param activity The current {@link Activity}
     * @param parentId The ID of the parent tab, or {@link Tab#INVALID_TAB_ID}.
     * @param otherActivity The activity to create a new tab in. This is non-null when we have a
     *     visible activity running adjacently.
     */
    public void launchTabInOtherWindow(
            LoadUrlParams loadUrlParams, Activity activity, int parentId, Activity otherActivity) {
        Intent intent =
                createNewTabIntent(new AsyncTabCreationParams(loadUrlParams), parentId, false);

        Class<? extends Activity> targetActivity =
                MultiWindowUtils.getInstance().getOpenInOtherWindowActivity(activity);
        if (targetActivity == null) return;

        MultiWindowUtils.setOpenInOtherWindowIntentExtras(intent, activity, targetActivity);
        IntentUtils.addTrustedIntentExtras(intent);

        MultiInstanceManager.onMultiInstanceModeStarted();
        if (MultiWindowUtils.isMultiInstanceApi31Enabled()) {
            // If there is a Chrome window running adjacently, open the link in it.
            // Otherwise create a new window.
            if (otherActivity != null) {
                assert otherActivity instanceof ChromeTabbedActivity;
                ((ChromeTabbedActivity) otherActivity).onNewIntent(intent);
                return;
            }
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        }
        activity.startActivity(
                intent, MultiWindowUtils.getOpenInOtherWindowActivityOptions(activity));
    }

    /**
     * Creates a new tab and loads the specified URL in it. This is a convenience method for {@link
     * #launchNewTab} with the default {@link LoadUrlParams} and no parent tab.
     *
     * @param url the URL to open.
     * @param type the type of action that triggered that launch. Determines how the tab is opened
     *     (for example, in the foreground or background).
     */
    public void launchUrl(String url, @TabLaunchType int type) {
        launchNewTab(new LoadUrlParams(url), type, null);
    }

    @Override
    public void launchNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent) {
        AsyncTabCreationParams asyncParams = new AsyncTabCreationParams(loadUrlParams);
        launchNewTab(asyncParams, type, parent == null ? Tab.INVALID_TAB_ID : parent.getId());
    }

    /**
     * Launches a Tab to host the given WebContents asynchronously.
     *
     * @param asyncParams Parameters to create the Tab with, including the URL.
     * @param type Information about how the tab was launched.
     * @param parentId ID of the parent tab, if it exists.
     */
    public void launchNewTab(
            AsyncTabCreationParams asyncParams, @TabLaunchType int type, int parentId) {
        assert asyncParams != null;

        // Tabs shouldn't be launched in affiliated mode when a webcontents exists.
        assert !(type == TabLaunchType.FROM_LONGPRESS_BACKGROUND
                && asyncParams.getWebContents() != null);

        Intent intent =
                createNewTabIntent(asyncParams, parentId, type == TabLaunchType.FROM_CHROME_UI);
        IntentHandler.startActivityForTrustedIntent(intent);
    }

    private Intent createNewTabIntent(
            AsyncTabCreationParams asyncParams, int parentId, boolean isChromeUI) {
        int assignedTabId = TabIdManager.getInstance().generateValidId(Tab.INVALID_TAB_ID);
        AsyncTabParamsManagerSingleton.getInstance().add(assignedTabId, asyncParams);

        Intent intent =
                new Intent(Intent.ACTION_VIEW, Uri.parse(asyncParams.getLoadUrlParams().getUrl()));

        addAsyncTabExtras(asyncParams, parentId, isChromeUI, assignedTabId, intent);

        return intent;
    }

    private void addAsyncTabExtras(
            AsyncTabCreationParams asyncParams,
            int parentId,
            boolean isChromeUI,
            int assignedTabId,
            Intent intent) {
        ComponentName componentName = asyncParams.getComponentName();
        if (componentName == null) {
            intent.setClass(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class);
        } else {
            ActivityUtils.setNonAliasedComponentForMainBrowsingActivity(intent, componentName);
        }
        IntentHandler.setIntentExtraHeaders(
                asyncParams.getLoadUrlParams().getExtraHeaders(), intent);

        IntentHandler.setTabId(intent, assignedTabId);
        intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, mIsIncognito);
        intent.putExtra(IntentHandler.EXTRA_PARENT_TAB_ID, parentId);

        if (mIsIncognito || isChromeUI) {
            intent.putExtra(
                    Browser.EXTRA_APPLICATION_ID,
                    ContextUtils.getApplicationContext().getPackageName());
        }

        if (isChromeUI) intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);

        Activity parentActivity = getActivityForTabId(parentId);
        if (parentActivity != null && parentActivity.getIntent() != null) {
            intent.putExtra(IntentHandler.EXTRA_PARENT_INTENT, parentActivity.getIntent());
        }

        if (asyncParams.getRequestId() != null) {
            intent.putExtra(
                    ServiceTabLauncher.LAUNCH_REQUEST_ID_EXTRA,
                    asyncParams.getRequestId().intValue());
        }

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }

    /**
     * Passes the supplied web app launch intent to the IntentHandler.
     *
     * @param intent Web app launch intent.
     */
    public void launchNewStandaloneFrame(Intent intent) {
        assert intent != null;
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        IntentHandler.startActivityForTrustedIntent(intent);
    }
}