chromium/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsIntentTestUtils.java

// Copyright 2022 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 android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;

import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.test.core.app.ApplicationProvider;

import org.chromium.base.Callback;
import org.chromium.base.IntentUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;

import java.util.ArrayList;
import java.util.concurrent.TimeoutException;

/**
 * Utility class that contains convenience calls related with intent creation for custom tabs
 * testing.
 */
public class CustomTabsIntentTestUtils {
    /**
     * A utility class to ensure that a pending intent assigned to a menu item in CCT was invoked.
     */
    public static class OnFinishedForTest implements PendingIntent.OnFinished {
        private final PendingIntent mPendingIntent;
        private final CallbackHelper mCallbackHelper = new CallbackHelper();
        private Intent mCallbackIntent;

        /**
         * Create an instance of {@link OnFinishedForTest}, testing the given {@link PendingIntent}.
         */
        public OnFinishedForTest(PendingIntent pendingIntent) {
            mPendingIntent = pendingIntent;
        }

        public Intent getCallbackIntent() {
            return mCallbackIntent;
        }

        public void waitForCallback(String failureReason) throws TimeoutException {
            mCallbackHelper.waitForCallback(failureReason, 0);
        }

        @Override
        public void onSendFinished(
                PendingIntent pendingIntent,
                Intent intent,
                int resultCode,
                String resultData,
                Bundle resultExtras) {
            if (pendingIntent.equals(mPendingIntent)) {
                mCallbackIntent = intent;
                mCallbackHelper.notifyCalled();
            }
        }
    }

    /**
     * Creates the simplest intent that is sufficient to let {@link ChromeLauncherActivity} launch
     * the {@link CustomTabActivity}.
     */
    public static Intent createMinimalCustomTabIntent(Context context, String url) {
        return createCustomTabIntent(context, url, /* launchAsNewTask= */ true, builder -> {});
    }

    /** Creates an Intent that launches a CustomTabActivity, allows some customization. */
    public static Intent createCustomTabIntent(
            Context context,
            String url,
            boolean launchAsNewTask,
            Callback<CustomTabsIntent.Builder> customizer) {
        CustomTabsIntent.Builder builder =
                new CustomTabsIntent.Builder(
                        CustomTabsSession.createMockSessionForTesting(
                                new ComponentName(context, ChromeLauncherActivity.class)));
        customizer.onResult(builder);
        CustomTabsIntent customTabsIntent = builder.build();
        Intent intent = customTabsIntent.intent;
        intent.setAction(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(url));
        if (launchAsNewTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        return intent;
    }

    /**
     * Creates the simplest intent that that is sufficient to let {@link ChromeLauncherActivity}
     * launch the incognito {@link CustomTabActivity}.
     *
     * @param context The instrumentation context to use.
     * @param url The URL to load in the incognito CCT.
     * @return Returns the intent to launch the incognito CCT.
     */
    public static Intent createMinimalIncognitoCustomTabIntent(Context context, String url) {
        Intent intent = createMinimalCustomTabIntent(context, url);
        intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true);
        return intent;
    }

    /**
     * Creates the simplest intent that is sufficient to let {@link ChromeLauncherActivity} launch
     * the {@link CustomTabActivity}. Allows specification of a theme.
     *
     * @param context The instrumentation context to use.
     * @param url The URL to load in the incognito CCT.
     * @param inNightMode Whether the CCT should be launched in night mode.
     * @return Returns the intent to launch the incognito CCT.
     */
    public static Intent createMinimalCustomTabIntentWithTheme(
            Context context, String url, boolean inNightMode) {
        return createCustomTabIntent(
                context,
                url,
                /* launchAsNewTask= */ true,
                builder -> {
                    builder.setColorScheme(
                            inNightMode
                                    ? CustomTabsIntent.COLOR_SCHEME_DARK
                                    : CustomTabsIntent.COLOR_SCHEME_LIGHT);
                });
    }

    /**
     * Add a bundle specifying a a number of custom menu entries.
     *
     * @param customTabIntent The intent to modify.
     * @param numEntries The number of menu entries to add.
     * @param menuTitle The title of the menu.
     * @return The pending intent associated with the menu entries.
     */
    public static PendingIntent addMenuEntriesToIntent(
            Intent customTabIntent, int numEntries, String menuTitle) {
        return addMenuEntriesToIntent(customTabIntent, numEntries, new Intent(), menuTitle);
    }

    /**
     * Add a bundle specifying a custom menu entry.
     *
     * @param customTabIntent The intent to modify.
     * @param numEntries The number of menu entries to add.
     * @param callbackIntent The intent to use as the base for the pending intent.
     * @param menuTitle The title of the menu.
     * @return The pending intent associated with the menu entry.
     */
    public static PendingIntent addMenuEntriesToIntent(
            Intent customTabIntent, int numEntries, Intent callbackIntent, String menuTitle) {
        PendingIntent pi =
                PendingIntent.getBroadcast(
                        ApplicationProvider.getApplicationContext(),
                        0,
                        callbackIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT
                                | IntentUtils.getPendingIntentMutabilityFlag(false));
        ArrayList<Bundle> menuItems = new ArrayList<>();
        for (int i = 0; i < numEntries; i++) {
            Bundle bundle = new Bundle();
            bundle.putString(CustomTabsIntent.KEY_MENU_ITEM_TITLE, menuTitle);
            bundle.putParcelable(CustomTabsIntent.KEY_PENDING_INTENT, pi);
            menuItems.add(bundle);
        }
        customTabIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, menuItems);
        return pi;
    }

    /**
     * Creates a CCT Toolbar menu item bundle.
     *
     * @param icon The Bitmap icon to be add in the toolbar.
     * @param description The description about the icon which will be added.
     * @param pi The pending intent that would be triggered when the icon is clicked on.
     * @param id A unique id for this new icon.
     * @return Returns the bundle encapsulating the toolbar item.
     */
    public static Bundle makeToolbarItemBundle(
            Bitmap icon, String description, PendingIntent pi, int id) {
        Bundle bundle = new Bundle();
        bundle.putInt(CustomTabsIntent.KEY_ID, id);
        bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon);
        bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description);
        bundle.putParcelable(CustomTabsIntent.KEY_PENDING_INTENT, pi);
        bundle.putBoolean(CustomButtonParamsImpl.SHOW_ON_TOOLBAR, true);
        return bundle;
    }

    /**
     * Adds an action button to the custom tab toolbar.
     *
     * @param intent The intent where the action button would be added.
     * @param icon The icon representing the action button.
     * @param description The description associated with the action button.
     * @param id The unique id that would be used for this new Action button.
     * @return The {@link PendingIntent} that will be triggered when the action button is clicked.
     */
    public static PendingIntent addActionButtonToIntent(
            Intent intent, Bitmap icon, String description, int id) {
        PendingIntent pi =
                PendingIntent.getBroadcast(
                        ApplicationProvider.getApplicationContext(),
                        0,
                        new Intent(),
                        IntentUtils.getPendingIntentMutabilityFlag(true));
        intent.putExtra(
                CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE,
                makeToolbarItemBundle(icon, description, pi, id));
        return pi;
    }

    /**
     * Sets the {@link CustomTabsIntent.ShareState} of the custom tab.
     *
     * @param intent The intent to modify.
     * @param shareState The {@link CustomTabsIntent.ShareState} being set.
     */
    public static void setShareState(Intent intent, int shareState) {
        intent.putExtra(CustomTabsIntent.EXTRA_SHARE_STATE, shareState);
    }
}