public abstract class

CustomTabsService

extends Service

 java.lang.Object

↳Service

↳androidx.browser.customtabs.CustomTabsService

Gradle dependencies

compile group: 'androidx.browser', name: 'browser', version: '1.8.0'

  • groupId: androidx.browser
  • artifactId: browser
  • version: 1.8.0

Artifact androidx.browser:browser:1.8.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.browser:browser com.android.support:customtabs

Androidx class mapping:

androidx.browser.customtabs.CustomTabsService android.support.customtabs.CustomTabsService

Overview

Abstract service class for implementing Custom Tabs related functionality. The service should be responding to the action ACTION_CUSTOM_TABS_CONNECTION. This class should be used by implementers that want to provide Custom Tabs functionality, not by clients that want to launch Custom Tabs.

Summary

Fields
public static final java.lang.StringACTION_CUSTOM_TABS_CONNECTION

The Intent action that a CustomTabsService must respond to.

public static final java.lang.StringCATEGORY_COLOR_SCHEME_CUSTOMIZATION

An Intent filter category to signify that the Custom Tabs provider supports selecting and customizing color schemes via CustomTabsIntent.Builder.setColorScheme(int) and CustomTabsIntent.Builder.setColorSchemeParams(int, CustomTabColorSchemeParams).

public static final java.lang.StringCATEGORY_NAVBAR_COLOR_CUSTOMIZATION

An Intent filter category to signify that the Custom Tabs provider supports customizing the color of the navigation bar (CustomTabsIntent.Builder.setNavigationBarColor(int)).

public static final java.lang.StringCATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE

An Intent filter category to signify that the Trusted Web Activity provider supports immersive mode.

public static final java.lang.StringCATEGORY_WEB_SHARE_TARGET_V2

An Intent filter category to signify that the Trusted Web Activity provider supports sending shared data according to the Web Share Target v2 protocol defined in https://wicg.github.io/web-share-target/level-2/.

public static final intFILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE

A constant to be used with CustomTabsSession.receiveFile(Uri, int, Bundle) indicating that the file is a splash image to be shown on top of a Trusted Web Activity while the web contents are loading.

public static final java.lang.StringKEY_SUCCESS

The key to use to store a boolean in the returns bundle of CustomTabsService.extraCommand(String, Bundle) method, to indicate the command is executed successfully.

public static final java.lang.StringKEY_URL

For CustomTabsService.mayLaunchUrl(CustomTabsSessionToken, Uri, Bundle, List) calls that wants to specify more than one url, this key can be used with to insert a new url to each bundle inside list of bundles.

public static final intRELATION_HANDLE_ALL_URLS

Used for CustomTabsSession.validateRelationship(int, Uri, Bundle).

public static final intRELATION_USE_AS_ORIGIN

Used for CustomTabsSession.validateRelationship(int, Uri, Bundle).

public static final intRESULT_FAILURE_DISALLOWED

Indicates that the postMessage request was not allowed due to a bad argument or requesting at a disallowed time like when in background.

public static final intRESULT_FAILURE_MESSAGING_ERROR

Indicates that the postMessage request has failed due to an internal error on the browser message channel.

public static final intRESULT_FAILURE_REMOTE_ERROR

Indicates that the postMessage request has failed due to a .

public static final intRESULT_SUCCESS

Indicates that the postMessage request was accepted.

public static final java.lang.StringTRUSTED_WEB_ACTIVITY_CATEGORY

An Intent filter category to signify that the Custom Tabs provider supports Trusted Web Activities (see TrustedWebUtils for more details).

Constructors
publicCustomTabsService()

Methods
protected booleancleanUpSession(CustomTabsSessionToken sessionToken)

Called when the client side IBinder for this CustomTabsSessionToken is dead.

protected abstract BundleextraCommand(java.lang.String commandName, Bundle args)

Unsupported commands that may be provided by the implementation.

protected booleanisEngagementSignalsApiAvailable(CustomTabsSessionToken sessionToken, Bundle extras)

Returns whether the Engagement Signals API is available.

protected abstract booleanmayLaunchUrl(CustomTabsSessionToken sessionToken, Uri url, Bundle extras, java.util.List<Bundle> otherLikelyBundles)

Tells the browser of a likely future navigation to a URL.

protected abstract booleannewSession(CustomTabsSessionToken sessionToken)

Creates a new session through an ICustomTabsService with the optional callback.

public IBinderonBind(Intent intent)

protected abstract intpostMessage(CustomTabsSessionToken sessionToken, java.lang.String message, Bundle extras)

Sends a postMessage request using the origin communicated via CustomTabsService.requestPostMessageChannel(CustomTabsSessionToken, Uri).

protected abstract booleanreceiveFile(CustomTabsSessionToken sessionToken, Uri uri, int purpose, Bundle extras)

Receive a file from client by given Uri, e.g.

protected abstract booleanrequestPostMessageChannel(CustomTabsSessionToken sessionToken, Uri postMessageOrigin)

Sends a request to create a two way postMessage channel between the client and the browser linked with the given CustomTabsSession.

protected booleanrequestPostMessageChannel(CustomTabsSessionToken sessionToken, Uri postMessageOrigin, Uri postMessageTargetOrigin, Bundle extras)

Same as above method with specifying the target origin to establish communication with.

protected booleansetEngagementSignalsCallback(CustomTabsSessionToken sessionToken, EngagementSignalsCallback callback, Bundle extras)

Sets an EngagementSignalsCallback to execute callbacks for events related to the user's engagement with the webpage within the tab.

protected abstract booleanupdateVisuals(CustomTabsSessionToken sessionToken, Bundle bundle)

Updates the visuals of custom tabs for the given session.

protected abstract booleanvalidateRelationship(CustomTabsSessionToken sessionToken, int relation, Uri origin, Bundle extras)

Request to validate a relationship between the application and an origin.

protected abstract booleanwarmup(long flags)

Warms up the browser process asynchronously.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final java.lang.String ACTION_CUSTOM_TABS_CONNECTION

The Intent action that a CustomTabsService must respond to.

public static final java.lang.String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION

An Intent filter category to signify that the Custom Tabs provider supports customizing the color of the navigation bar (CustomTabsIntent.Builder.setNavigationBarColor(int)).

public static final java.lang.String CATEGORY_COLOR_SCHEME_CUSTOMIZATION

An Intent filter category to signify that the Custom Tabs provider supports selecting and customizing color schemes via CustomTabsIntent.Builder.setColorScheme(int) and CustomTabsIntent.Builder.setColorSchemeParams(int, CustomTabColorSchemeParams).

public static final java.lang.String TRUSTED_WEB_ACTIVITY_CATEGORY

An Intent filter category to signify that the Custom Tabs provider supports Trusted Web Activities (see TrustedWebUtils for more details).

public static final java.lang.String CATEGORY_WEB_SHARE_TARGET_V2

An Intent filter category to signify that the Trusted Web Activity provider supports sending shared data according to the Web Share Target v2 protocol defined in https://wicg.github.io/web-share-target/level-2/.

public static final java.lang.String CATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE

An Intent filter category to signify that the Trusted Web Activity provider supports immersive mode.

public static final java.lang.String KEY_URL

For CustomTabsService.mayLaunchUrl(CustomTabsSessionToken, Uri, Bundle, List) calls that wants to specify more than one url, this key can be used with to insert a new url to each bundle inside list of bundles.

public static final java.lang.String KEY_SUCCESS

The key to use to store a boolean in the returns bundle of CustomTabsService.extraCommand(String, Bundle) method, to indicate the command is executed successfully.

public static final int RESULT_SUCCESS

Indicates that the postMessage request was accepted.

public static final int RESULT_FAILURE_DISALLOWED

Indicates that the postMessage request was not allowed due to a bad argument or requesting at a disallowed time like when in background.

public static final int RESULT_FAILURE_REMOTE_ERROR

Indicates that the postMessage request has failed due to a .

public static final int RESULT_FAILURE_MESSAGING_ERROR

Indicates that the postMessage request has failed due to an internal error on the browser message channel.

public static final int RELATION_USE_AS_ORIGIN

Used for CustomTabsSession.validateRelationship(int, Uri, Bundle). For App -> Web transitions, requests the app to use the declared origin to be used as origin for the client app in the web APIs context.

public static final int RELATION_HANDLE_ALL_URLS

Used for CustomTabsSession.validateRelationship(int, Uri, Bundle). Requests the ability to handle all URLs from a given origin.

public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE

A constant to be used with CustomTabsSession.receiveFile(Uri, int, Bundle) indicating that the file is a splash image to be shown on top of a Trusted Web Activity while the web contents are loading.

Constructors

public CustomTabsService()

Methods

public IBinder onBind(Intent intent)

protected boolean cleanUpSession(CustomTabsSessionToken sessionToken)

Called when the client side IBinder for this CustomTabsSessionToken is dead. Can also be used to clean up instances allocated for the given token.

Parameters:

sessionToken: The session token for which the call has been received.

Returns:

Whether the clean up was successful. Multiple calls with two tokens holdings the same binder will return false.

protected abstract boolean warmup(long flags)

Warms up the browser process asynchronously.

Parameters:

flags: Reserved for future use.

Returns:

Whether warmup was/had been completed successfully. Multiple successful calls will return true.

protected abstract boolean newSession(CustomTabsSessionToken sessionToken)

Creates a new session through an ICustomTabsService with the optional callback. This session can be used to associate any related communication through the service with an intent and then later with a Custom Tab. The client can then send later service calls or intents to through same session-intent-Custom Tab association.

Parameters:

sessionToken: Session token to be used as a unique identifier. This also has access to the CustomTabsCallback passed from the client side through CustomTabsSessionToken.getCallback().

Returns:

Whether a new session was successfully created.

protected abstract boolean mayLaunchUrl(CustomTabsSessionToken sessionToken, Uri url, Bundle extras, java.util.List<Bundle> otherLikelyBundles)

Tells the browser of a likely future navigation to a URL.

The method CustomTabsService.warmup(long) has to be called beforehand. The most likely URL has to be specified explicitly. Optionally, a list of other likely URLs can be provided. They are treated as less likely than the first one, and have to be sorted in decreasing priority order. These additional URLs may be ignored. All previous calls to this method will be deprioritized.

Parameters:

sessionToken: The unique identifier for the session. Can not be null.
url: Most likely URL.
extras: Reserved for future use.
otherLikelyBundles: Other likely destinations, sorted in decreasing likelihood order. Each Bundle has to provide a url.

Returns:

Whether the call was successful.

protected abstract Bundle extraCommand(java.lang.String commandName, Bundle args)

Unsupported commands that may be provided by the implementation.

Note:Clients should never rely on this method to have a defined behavior, as it is entirely implementation-defined and not supported.

This call can be used by implementations to add extra commands, for testing or experimental purposes. A return value of null will be used to signify that the client does not know how to handle the request. As optional best practices, CustomTabsService.KEY_SUCCESS could be use to identify that command was *successfully* handled. For example, when returning a message with result:


     Bundle result = new Bundle();
     result.putString("message", message);
     if (success)
         result.putBoolean(KEY_SUCCESS, true);
     return result;
 
The caller side:

     Bundle result = service.extraCommand(commandName, args);
     if (result.getBoolean(service.KEY_SUCCESS)) {
         // Command was successfully handled
     }
 

Parameters:

commandName: Name of the extra command to execute.
args: Arguments for the command

Returns:

The result , or null.

protected abstract boolean updateVisuals(CustomTabsSessionToken sessionToken, Bundle bundle)

Updates the visuals of custom tabs for the given session. Will only succeed if the given session matches the currently active one.

Parameters:

sessionToken: The currently active session that the custom tab belongs to.
bundle: The action button configuration bundle. This bundle should be constructed with the same structure in CustomTabsIntent.Builder.

Returns:

Whether the operation was successful.

protected abstract boolean requestPostMessageChannel(CustomTabsSessionToken sessionToken, Uri postMessageOrigin)

Sends a request to create a two way postMessage channel between the client and the browser linked with the given CustomTabsSession.

Parameters:

sessionToken: The unique identifier for the session. Can not be null.
postMessageOrigin: A origin that the client is requesting to be identified as during the postMessage communication.

Returns:

Whether the implementation accepted the request. Note that returning true here doesn't mean an origin has already been assigned as the validation is asynchronous.

protected boolean requestPostMessageChannel(CustomTabsSessionToken sessionToken, Uri postMessageOrigin, Uri postMessageTargetOrigin, Bundle extras)

Same as above method with specifying the target origin to establish communication with.

Parameters:

sessionToken: The unique identifier for the session. Can not be null.
postMessageOrigin: A origin that the client is requesting to be identified as during the postMessage communication.
postMessageTargetOrigin: The target Origin to establish PostMessageChannel with and send messages to.
extras: Reserved for future use.

Returns:

Whether the implementation accepted the request. Note that returning true here doesn't mean an origin has already been assigned as the validation is asynchronous.

protected abstract int postMessage(CustomTabsSessionToken sessionToken, java.lang.String message, Bundle extras)

Sends a postMessage request using the origin communicated via CustomTabsService.requestPostMessageChannel(CustomTabsSessionToken, Uri). Fails when called before PostMessageServiceConnection.notifyMessageChannelReady(Bundle) is received on the client side.

Parameters:

sessionToken: The unique identifier for the session. Can not be null.
message: The message that is being sent.
extras: Reserved for future use.

Returns:

An integer constant about the postMessage request result. Will return CustomTabsService.RESULT_SUCCESS if successful.

protected abstract boolean validateRelationship(CustomTabsSessionToken sessionToken, int relation, Uri origin, Bundle extras)

Request to validate a relationship between the application and an origin. If this method returns true, the validation result will be provided through CustomTabsCallback.onRelationshipValidationResult(int, Uri, boolean, Bundle). Otherwise the request didn't succeed. The client must call CustomTabsClient.warmup(long) before this.

Parameters:

sessionToken: The unique identifier for the session. Can not be null.
relation: Relation to check, must be one of the CustomTabsService#RELATION_* constants.
origin: Origin for the relation query.
extras: Reserved for future use.

Returns:

true if the request has been submitted successfully.

protected abstract boolean receiveFile(CustomTabsSessionToken sessionToken, Uri uri, int purpose, Bundle extras)

Receive a file from client by given Uri, e.g. in order to display a large bitmap in a Custom Tab. Prior to calling this method, the client grants a read permission to the target Custom Tabs provider via . The file is read and processed (where applicable) synchronously.

Parameters:

sessionToken: The unique identifier for the session.
uri: of the file.
purpose: Purpose of transferring this file, one of the constants enumerated in CustomTabsService#FilePurpose.
extras: Reserved for future use.

Returns:

true if the file was received successfully.

protected boolean isEngagementSignalsApiAvailable(CustomTabsSessionToken sessionToken, Bundle extras)

Returns whether the Engagement Signals API is available. The availability of the Engagement Signals API may change at runtime. If an EngagementSignalsCallback has been set, an EngagementSignalsCallback.onSessionEnded(boolean, Bundle) signal will be sent if the API becomes unavailable later.

Parameters:

sessionToken: The unique identifier for the session.
extras: Reserved for future use.

Returns:

Whether the Engagement Signals API is available. A false value means CustomTabsService.setEngagementSignalsCallback(CustomTabsSessionToken, EngagementSignalsCallback, Bundle) will return false and not set the callback.

protected boolean setEngagementSignalsCallback(CustomTabsSessionToken sessionToken, EngagementSignalsCallback callback, Bundle extras)

Sets an EngagementSignalsCallback to execute callbacks for events related to the user's engagement with the webpage within the tab.

Parameters:

sessionToken: The unique identifier for the session.
callback: The EngagementSignalsCallback to execute the callbacks.
extras: Reserved for future use.

Returns:

Whether the callback connection is allowed. If false, no callbacks will be called for this session.

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.browser.customtabs;

import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.support.customtabs.ICustomTabsCallback;
import android.support.customtabs.ICustomTabsService;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.SimpleArrayMap;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Abstract service class for implementing Custom Tabs related functionality. The service should
 * be responding to the action ACTION_CUSTOM_TABS_CONNECTION. This class should be used by
 * implementers that want to provide Custom Tabs functionality, not by clients that want to launch
 * Custom Tabs.
 */
public abstract class CustomTabsService extends Service {
    /**
     * The Intent action that a CustomTabsService must respond to.
     */
    public static final String ACTION_CUSTOM_TABS_CONNECTION =
            "android.support.customtabs.action.CustomTabsService";

    /**
     * An Intent filter category to signify that the Custom Tabs provider supports customizing
     * the color of the navigation bar ({@link CustomTabsIntent.Builder#setNavigationBarColor}).
     */
    public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION =
            "androidx.browser.customtabs.category.NavBarColorCustomization";

    /**
     * An Intent filter category to signify that the Custom Tabs provider supports selecting and
     * customizing color schemes via {@link CustomTabsIntent.Builder#setColorScheme} and
     * {@link CustomTabsIntent.Builder#setColorSchemeParams}.
     */
    public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION =
            "androidx.browser.customtabs.category.ColorSchemeCustomization";

    /**
     * An Intent filter category to signify that the Custom Tabs provider supports Trusted Web
     * Activities (see {@link TrustedWebUtils} for more details).
     */
    public static final String TRUSTED_WEB_ACTIVITY_CATEGORY =
            "androidx.browser.trusted.category.TrustedWebActivities";

    /**
     * An Intent filter category to signify that the Trusted Web Activity provider supports
     * sending shared data according to the Web Share Target v2 protocol defined in
     * https://wicg.github.io/web-share-target/level-2/.
     */
    public static final String CATEGORY_WEB_SHARE_TARGET_V2 =
            "androidx.browser.trusted.category.WebShareTargetV2";

    /**
     * An Intent filter category to signify that the Trusted Web Activity provider supports
     * immersive mode.
     */
    public static final String CATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE =
            "androidx.browser.trusted.category.ImmersiveMode";

    /**
     * For {@link CustomTabsService#mayLaunchUrl} calls that wants to specify more than one url,
     * this key can be used with {@link Bundle#putParcelable(String, android.os.Parcelable)}
     * to insert a new url to each bundle inside list of bundles.
     */
    public static final String KEY_URL =
            "android.support.customtabs.otherurls.URL";

    /**
     * The key to use to store a boolean in the returns bundle of {@link #extraCommand} method,
     * to indicate the command is executed successfully.
     */
    public static final String KEY_SUCCESS = "androidx.browser.customtabs.SUCCESS";

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({RESULT_SUCCESS, RESULT_FAILURE_DISALLOWED,
            RESULT_FAILURE_REMOTE_ERROR, RESULT_FAILURE_MESSAGING_ERROR})
    public @interface Result {
    }

    /**
     * Indicates that the postMessage request was accepted.
     */
    public static final int RESULT_SUCCESS = 0;
    /**
     * Indicates that the postMessage request was not allowed due to a bad argument or requesting
     * at a disallowed time like when in background.
     */
    public static final int RESULT_FAILURE_DISALLOWED = -1;
    /**
     * Indicates that the postMessage request has failed due to a {@link RemoteException} .
     */
    public static final int RESULT_FAILURE_REMOTE_ERROR = -2;
    /**
     * Indicates that the postMessage request has failed due to an internal error on the browser
     * message channel.
     */
    public static final int RESULT_FAILURE_MESSAGING_ERROR = -3;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({RELATION_USE_AS_ORIGIN, RELATION_HANDLE_ALL_URLS})
    public @interface Relation {
    }

    /**
     * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. For
     * App -> Web transitions, requests the app to use the declared origin to be used as origin for
     * the client app in the web APIs context.
     */
    public static final int RELATION_USE_AS_ORIGIN = 1;
    /**
     * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Requests the
     * ability to handle all URLs from a given origin.
     */
    public static final int RELATION_HANDLE_ALL_URLS = 2;


    /**
     * Enumerates the possible purposes of files received in {@link #receiveFile}.
     *
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE})
    public @interface FilePurpose {
    }

    /**
     * A constant to be used with {@link CustomTabsSession#receiveFile} indicating that the file
     * is a splash image to be shown on top of a Trusted Web Activity while the web contents
     * are loading.
     */
    public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1;

    private static final String TAG = "CustomTabsService";

    final SimpleArrayMap<IBinder, DeathRecipient> mDeathRecipientMap = new SimpleArrayMap<>();

    private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {

        @Override
        public boolean warmup(long flags) {
            return CustomTabsService.this.warmup(flags);
        }

        @Override
        public boolean newSession(@NonNull ICustomTabsCallback callback) {
            return newSessionInternal(callback, null);
        }

        @Override
        public boolean newSessionWithExtras(@NonNull ICustomTabsCallback callback,
                @Nullable Bundle extras) {
            return newSessionInternal(callback, getSessionIdFromBundle(extras));
        }

        private boolean newSessionInternal(@NonNull ICustomTabsCallback callback,
                @Nullable PendingIntent sessionId) {
            final CustomTabsSessionToken sessionToken =
                    new CustomTabsSessionToken(callback, sessionId);
            try {
                DeathRecipient deathRecipient = () -> cleanUpSession(sessionToken);
                synchronized (mDeathRecipientMap) {
                    callback.asBinder().linkToDeath(deathRecipient, 0);
                    mDeathRecipientMap.put(callback.asBinder(), deathRecipient);
                }
                return CustomTabsService.this.newSession(sessionToken);
            } catch (RemoteException e) {
                return false;
            }
        }

        @Override
        public boolean mayLaunchUrl(@Nullable ICustomTabsCallback callback, @Nullable Uri url,
                @Nullable Bundle extras, @Nullable List<Bundle> otherLikelyBundles) {
            return CustomTabsService.this.mayLaunchUrl(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(extras)),
                    url, extras, otherLikelyBundles);
        }

        @SuppressWarnings("NullAway")  // TODO: b/142938599
        @Override
        public Bundle extraCommand(@NonNull String commandName, @Nullable Bundle args) {
            return CustomTabsService.this.extraCommand(commandName, args);
        }

        @Override
        public boolean updateVisuals(@NonNull ICustomTabsCallback callback,
                @Nullable Bundle bundle) {
            return CustomTabsService.this.updateVisuals(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(bundle)), bundle);
        }

        @Override
        public boolean requestPostMessageChannel(@NonNull ICustomTabsCallback callback,
                @NonNull Uri postMessageOrigin) {
            return CustomTabsService.this.requestPostMessageChannel(
                    new CustomTabsSessionToken(callback, null), postMessageOrigin,
                    null, new Bundle());
        }

        @Override
        public boolean requestPostMessageChannelWithExtras(@NonNull ICustomTabsCallback callback,
                @NonNull Uri postMessageOrigin, @NonNull Bundle extras) {
            return CustomTabsService.this.requestPostMessageChannel(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(extras)),
                    postMessageOrigin, getTargetOriginFromBundle(extras), extras);
        }

        @Override
        public int postMessage(@NonNull ICustomTabsCallback callback, @NonNull String message,
                @Nullable Bundle extras) {
            return CustomTabsService.this.postMessage(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(extras)),
                    message, extras);
        }

        @Override
        public boolean validateRelationship(
                @NonNull ICustomTabsCallback callback, @Relation int relation,
                @NonNull Uri origin, @Nullable Bundle extras) {
            return CustomTabsService.this.validateRelationship(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(extras)),
                    relation, origin, extras);
        }

        @Override
        public boolean receiveFile(@NonNull ICustomTabsCallback callback, @NonNull Uri uri,
                @FilePurpose int purpose, @Nullable Bundle extras) {
            return CustomTabsService.this.receiveFile(
                    new CustomTabsSessionToken(callback, getSessionIdFromBundle(extras)),
                    uri, purpose, extras);
        }

        @Override
        public boolean isEngagementSignalsApiAvailable(ICustomTabsCallback customTabsCallback,
                @NonNull Bundle extras) {
            return CustomTabsService.this.isEngagementSignalsApiAvailable(
                    new CustomTabsSessionToken(customTabsCallback, getSessionIdFromBundle(extras)),
                    extras);
        }

        @Override
        public boolean setEngagementSignalsCallback(
                @NonNull ICustomTabsCallback customTabsCallback, @NonNull IBinder callback,
                @NonNull Bundle extras) {
            EngagementSignalsCallback remote = EngagementSignalsCallbackRemote.fromBinder(
                    callback);
            return CustomTabsService.this.setEngagementSignalsCallback(
                    new CustomTabsSessionToken(customTabsCallback, getSessionIdFromBundle(extras)),
                    remote, extras);
        }

        @SuppressWarnings("deprecation")
        private @Nullable PendingIntent getSessionIdFromBundle(@Nullable Bundle bundle) {
            if (bundle == null) return null;

            PendingIntent sessionId = bundle.getParcelable(CustomTabsIntent.EXTRA_SESSION_ID);
            bundle.remove(CustomTabsIntent.EXTRA_SESSION_ID);
            return sessionId;
        }

        @SuppressWarnings("deprecation")
        private @Nullable Uri getTargetOriginFromBundle(@Nullable Bundle bundle) {
            if (bundle == null) return null;
            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                return Api33Impl.getParcelable(bundle, CustomTabsSession.TARGET_ORIGIN_KEY,
                        Uri.class);
            } else {
                return bundle.getParcelable(CustomTabsSession.TARGET_ORIGIN_KEY);
            }
        }
    };

    @Override
    @NonNull
    public IBinder onBind(@Nullable Intent intent) {
        return mBinder;
    }

    /**
     * Called when the client side {@link IBinder} for this {@link CustomTabsSessionToken} is dead.
     * Can also be used to clean up {@link DeathRecipient} instances allocated for the given token.
     *
     * @param sessionToken The session token for which the {@link DeathRecipient} call has been
     *                     received.
     * @return Whether the clean up was successful. Multiple calls with two tokens holdings the
     * same binder will return false.
     */
    protected boolean cleanUpSession(@NonNull CustomTabsSessionToken sessionToken) {
        try {
            synchronized (mDeathRecipientMap) {
                IBinder binder = sessionToken.getCallbackBinder();
                if (binder == null) return false;
                DeathRecipient deathRecipient = mDeathRecipientMap.get(binder);
                binder.unlinkToDeath(deathRecipient, 0);
                mDeathRecipientMap.remove(binder);
            }
        } catch (NoSuchElementException e) {
            return false;
        }
        return true;
    }

    /**
     * Warms up the browser process asynchronously.
     *
     * @param flags Reserved for future use.
     * @return Whether warmup was/had been completed successfully. Multiple successful
     * calls will return true.
     */
    protected abstract boolean warmup(long flags);

    /**
     * Creates a new session through an ICustomTabsService with the optional callback. This session
     * can be used to associate any related communication through the service with an intent and
     * then later with a Custom Tab. The client can then send later service calls or intents to
     * through same session-intent-Custom Tab association.
     *
     * @param sessionToken Session token to be used as a unique identifier. This also has access
     *                     to the {@link CustomTabsCallback} passed from the client side through
     *                     {@link CustomTabsSessionToken#getCallback()}.
     * @return Whether a new session was successfully created.
     */
    protected abstract boolean newSession(@NonNull CustomTabsSessionToken sessionToken);

    /**
     * Tells the browser of a likely future navigation to a URL.
     * <p>
     * The method {@link CustomTabsService#warmup(long)} has to be called beforehand.
     * The most likely URL has to be specified explicitly. Optionally, a list of
     * other likely URLs can be provided. They are treated as less likely than
     * the first one, and have to be sorted in decreasing priority order. These
     * additional URLs may be ignored.
     * All previous calls to this method will be deprioritized.
     *
     * @param sessionToken       The unique identifier for the session. Can not be null.
     * @param url                Most likely URL.
     * @param extras             Reserved for future use.
     * @param otherLikelyBundles Other likely destinations, sorted in decreasing
     *                           likelihood order. Each Bundle has to provide a url.
     * @return Whether the call was successful.
     */
    protected abstract boolean mayLaunchUrl(@NonNull CustomTabsSessionToken sessionToken,
            @Nullable Uri url, @Nullable Bundle extras, @Nullable List<Bundle> otherLikelyBundles);

    /**
     * Unsupported commands that may be provided by the implementation.
     * <p>
     * <p>
     * <strong>Note:</strong>Clients should <strong>never</strong> rely on this method to have a
     * defined behavior, as it is entirely implementation-defined and not supported.
     * <p>
     * <p> This call can be used by implementations to add extra commands, for testing or
     * experimental purposes.
     *
     * A return value of {@code null} will be used to signify that the client does not know how to
     * handle the request.
     *
     * As optional best practices, {@link #KEY_SUCCESS} could be use to identify
     * that command was *successfully* handled. For example, when returning a message with result:
     * <pre><code>
     *     Bundle result = new Bundle();
     *     result.putString("message", message);
     *     if (success)
     *         result.putBoolean(KEY_SUCCESS, true);
     *     return result;
     * </code></pre>
     * The caller side:
     * <pre><code>
     *     Bundle result = service.extraCommand(commandName, args);
     *     if (result.getBoolean(service.KEY_SUCCESS)) {
     *         // Command was successfully handled
     *     }
     * </code></pre>
     *
     * @param commandName Name of the extra command to execute.
     * @param args        Arguments for the command
     * @return The result {@link Bundle}, or {@code null}.
     */
    @Nullable
    protected abstract Bundle extraCommand(@NonNull String commandName, @Nullable Bundle args);

    /**
     * Updates the visuals of custom tabs for the given session. Will only succeed if the given
     * session matches the currently active one.
     *
     * @param sessionToken The currently active session that the custom tab belongs to.
     * @param bundle       The action button configuration bundle. This bundle should be constructed
     *                     with the same structure in {@link CustomTabsIntent.Builder}.
     * @return Whether the operation was successful.
     */
    protected abstract boolean updateVisuals(@NonNull CustomTabsSessionToken sessionToken,
            @Nullable Bundle bundle);

    /**
     * Sends a request to create a two way postMessage channel between the client and the browser
     * linked with the given {@link CustomTabsSession}.
     *
     * @param sessionToken      The unique identifier for the session. Can not be null.
     * @param postMessageOrigin A origin that the client is requesting to be identified as
     *                          during the postMessage communication.
     * @return Whether the implementation accepted the request. Note that returning true
     * here doesn't mean an origin has already been assigned as the validation is
     * asynchronous.
     */
    protected abstract boolean requestPostMessageChannel(
            @NonNull CustomTabsSessionToken sessionToken, @NonNull Uri postMessageOrigin);

    /**
     * Same as above method with specifying the target origin to establish communication with.
     *
     * @param sessionToken      The unique identifier for the session. Can not be null.
     * @param postMessageOrigin A origin that the client is requesting to be identified as
     *                          during the postMessage communication.
     * @param postMessageTargetOrigin The target Origin to establish PostMessageChannel with and
     *                                 send messages to.
     * @param extras  Reserved for future use.
     * @return Whether the implementation accepted the request. Note that returning true
     * here doesn't mean an origin has already been assigned as the validation is
     * asynchronous.
     */
    protected boolean requestPostMessageChannel(
            @NonNull CustomTabsSessionToken sessionToken, @NonNull Uri postMessageOrigin,
            @Nullable Uri postMessageTargetOrigin, @NonNull Bundle extras) {
        return requestPostMessageChannel(sessionToken, postMessageOrigin);
    }

    /**
     * Sends a postMessage request using the origin communicated via
     * {@link CustomTabsService#requestPostMessageChannel(
     *CustomTabsSessionToken, Uri)}. Fails when called before
     * {@link PostMessageServiceConnection#notifyMessageChannelReady(Bundle)} is received on the
     * client side.
     *
     * @param sessionToken The unique identifier for the session. Can not be null.
     * @param message      The message that is being sent.
     * @param extras       Reserved for future use.
     * @return An integer constant about the postMessage request result. Will return
     * {@link CustomTabsService#RESULT_SUCCESS} if successful.
     */
    @Result
    protected abstract int postMessage(@NonNull CustomTabsSessionToken sessionToken,
            @NonNull String message, @Nullable Bundle extras);

    /**
     * Request to validate a relationship between the application and an origin.
     *
     * If this method returns true, the validation result will be provided through
     * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
     * Otherwise the request didn't succeed. The client must call
     * {@link CustomTabsClient#warmup(long)} before this.
     *
     * @param sessionToken The unique identifier for the session. Can not be null.
     * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
     *                 constants.
     * @param origin Origin for the relation query.
     * @param extras Reserved for future use.
     * @return true if the request has been submitted successfully.
     */
    protected abstract boolean validateRelationship(@NonNull CustomTabsSessionToken sessionToken,
            @Relation int relation, @NonNull Uri origin, @Nullable Bundle extras);

    /**
     * Receive a file from client by given Uri, e.g. in order to display a large bitmap in a Custom
     * Tab.
     *
     * Prior to calling this method, the client grants a read permission to the target
     * Custom Tabs provider via {@link android.content.Context#grantUriPermission}.
     *
     * The file is read and processed (where applicable) synchronously.
     *
     * @param sessionToken The unique identifier for the session.
     * @param uri {@link Uri} of the file.
     * @param purpose Purpose of transferring this file, one of the constants enumerated in
     *                {@code CustomTabsService#FilePurpose}.
     * @param extras Reserved for future use.
     * @return {@code true} if the file was received successfully.
     */
    protected abstract boolean receiveFile(@NonNull CustomTabsSessionToken sessionToken,
            @NonNull Uri uri, @FilePurpose int purpose, @Nullable Bundle extras);

    /**
     * Returns whether the Engagement Signals API is available. The availability of the Engagement
     * Signals API may change at runtime. If an {@link EngagementSignalsCallback} has been set, an
     * {@link EngagementSignalsCallback#onSessionEnded} signal will be sent if the API becomes
     * unavailable later.
     *
     * @param sessionToken The unique identifier for the session.
     * @param extras Reserved for future use.
     * @return Whether the Engagement Signals API is available. A false value means
     *         {@link #setEngagementSignalsCallback} will return false and not set the callback.
     */
    protected boolean isEngagementSignalsApiAvailable(@NonNull CustomTabsSessionToken sessionToken,
            @NonNull Bundle extras) {
        return false;
    }

    /**
     * Sets an {@link EngagementSignalsCallback} to execute callbacks for events related to
     * the user's engagement with the webpage within the tab.
     *
     * @param sessionToken The unique identifier for the session.
     * @param callback The {@link EngagementSignalsCallback} to execute the callbacks.
     * @param extras Reserved for future use.
     * @return Whether the callback connection is allowed. If false, no callbacks will be called for
     *         this session.
     */
    // This is called by the implementation which already has the ability to decide on which
    // thread to run the callbacks.
    @SuppressWarnings("ExecutorRegistration")
    protected boolean setEngagementSignalsCallback(
            @NonNull CustomTabsSessionToken sessionToken,
            @NonNull EngagementSignalsCallback callback, @NonNull Bundle extras) {
        return false;
    }
}