Chrome Custom Tabs

Last updated by Paul Kinlan: Monday August 30, 2015.

Note: In the latest version we have removed two of the navigation callbacks in the Chrome Custom Tabs Service.

What are Chrome custom tabs?

App developers face a choice when a user taps a URL to either launch a browser, or build their own in-app browser using WebViews.

Both options present challenges — launching the browser is a heavy context switch that isn't customizable, while WebViews don't share state with the browser and add maintenance overhead.

Chrome custom tabs give apps more control over their web experience, and make transitions between native and web content more seamless without having to resort to a WebView.

Chrome custom tabs allow an app to customize how Chrome looks and feels. An app can change things like:

  • Toolbar color
  • Enter and exit animations
  • Add custom actions to the Chrome toolbar and overflow menu

Chrome custom tabs also allow the developer to pre-start Chrome and pre-fetch content for faster loading.

You can test this now with our Chrome custom tab sample on Github.

When should I use Chrome custom tabs vs WebView?

The WebView is good solution if you are hosting your own content inside your app. If your app directs people to URLs outside your domain, we recommend that you use Chrome custom tabs for these reasons:

  • Simple to implement. No need to build code to manage requests, permission grants or cookie stores.
  • UI customization:
    • Toolbar color
    • Action button
    • Custom menu items
    • Custom in/out animations
  • Navigation awareness: the browser delivers a callback to the application upon an external navigation.
  • Performance optimization:
    • Pre-warming of the Browser in the background, while avoiding stealing resources from the application.
    • Providing a likely URL in advance to the browser, which may perform speculative work, speeding up page load time.
  • Lifecycle management: the browser prevents the application from being evicted by the system while on top of it, by raising its importance to the "foreground" level.
  • Shared Cookie Jar and permissions model so users don't have to log in to sites they are already connected to, or re-grant permissions they have already granted.
  • If the user has turned on Data Saver, they will still benefit from it.
  • Synchronized AutoComplete across devices for better form completion.
  • Simple customization model.
  • Quickly return to app with a single tap.
  • You want to use the latest browser implementations on devices pre-Lollipop (auto updating WebView) instead of older WebViews.
  • When will this be available?

    As of Chrome 45, Chrome custom tabs are available in Chrome Beta Channel, on all of Chrome's supported Android versions (Jellybean onwards). Please note that the API will change slightly over the coming weeks.

    The below spec only applies to Chrome 45.

    We are looking for feedback, questions and suggestions on this project, so we encourage you to file issues on crbug.com and ask questions to our Twitter account @ChromiumDev.

    Implementation guide

    A complete example is available at https://github.com/GoogleChrome/custom-tabs-client. It contains re-usable classes to customize the UI, connect to the background service, and handle the lifecycle of both the application and the custom tab activity. It also contains the AIDL files required to connect to the service, as the ones contained in the Chromium repository are not directly usable with Android Studio.

    If you follow the guidance from this page, you will be able to create a great integration.

    • Customizing the UI and interaction with the custom tabs.
    • Making the page load faster, and keeping the application alive.

    The first one is done by appending extras into the ACTION_VIEW Intent sent to Chrome; the second by connecting to a service exported by Chrome.

    Note: These notes are accurate for Chrome 45;

    Enable Chrome Custom Tabs

    // Using a VIEW intent for compatibility with any other browsers on device.
    // Caller should not be setting FLAG_ACTIVITY_NEW_TASK or 
    // FLAG_ACTIVITY_NEW_DOCUMENT. 
    String url = ¨https://paul.kinlan.me/¨;
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 
    
    //  Must have. Extra used to match the session. Its value is an IBinder passed
    //  whilst creating a news session. See newSession() below. Even if the service is not 
    //  used and there is no valid session id to be provided, this extra has to be present 
    //  with a null value to launch a custom tab.
    
    private static final String EXTRA_CUSTOM_TABS_SESSION = "android.support.customtabs.extra.SESSION";
    Bundle extras = new Bundle;
    extras.putBinder(EXTRA_CUSTOM_TABS_SESSION, 
       sessionICustomTabsCallback.asBinder() /* Set to null for no session */);
    intent.putExtras(extras);

    What happens if the user doesn’t have a recent version of Chrome installed?

    We are using the ACTION_VIEW Intent, this means that by default the page will open in the system browser, or the user's default browser.

    If the user has Chrome installed and it is the default browser, it will automatically pick up the EXTRAS and present a customized UI. It is also possible for another browser to use the Intent extras to provide a similar customized interface.

    How can I check whether Chrome supports Chrome custom tabs?

    All versions of Chrome supporting Chrome custom tabs expose a service (see the later section "Connect to the Service"). To check whether Chrome supports custom tabs, try to bind to the service. If it succeeds, then custom tabs can safely be used.

    Configure the color of the address bar

    One of the most important (and simplest to implement) aspects of Chrome Custom Tabs is the ability for you to change the color of the address bar to be consistent with your app's theme.

    // Extra that changes the background color for the omnibox. colorInt is an int
    // that specifies a Color.
    
    private static final String EXTRA_CUSTOM_TABS_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
    intent.putExtra(EXTRA_CUSTOM_TABS_TOOLBAR_COLOR, colorInt);
    

    Configure a custom action button

    As the developer of your app, you have full control over the Action Button that is presented to your users inside the Chrome tab.

    In most cases, this will be a primary action such as Share, or another common activity that your users will perform.

    The Action Button is represented as a Bundle with an icon of the action button and a pendingIntent that will be called by Chrome when your user hits the action button. The icon is currenlty 24dp in height and 24-48 dp in width.

    // Key that specifies the Bitmap to be used as the image source for the
    // action button.
    
    private static final String KEY_CUSTOM_TABS_ICON = "android.support.customtabs.customaction.ICON";
    // Key that specifies the PendingIntent to launch when the action button
    // or menu item was tapped. Chrome will be calling PendingIntent#send() on
    // taps after adding the url as data. The client app can call
    // Intent#getDataString() to get the url.
    public static final String KEY_CUSTOM_TABS_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
    
    // Optional. Use a bundle for parameters if an the action button is specified.
    public static final String EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE = 
    "android.support.customtabs.extra.ACTION_BUNDLE_BUTTON";
    Bundle actionButtonBundle = new Bundle();
    actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_ICON, icon);
    actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent);
    intent.putExtra(EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE, actionButtonBundle);

    Configure a custom menu

    The Chrome browser has a comprehensive menu of actions that users will perform frequently inside a browser, however they may not be relevant to your application context.

    Chrome custom tabs will have a three icon row with "Forward","Page Info" and "Refresh" on top at all times, with "Find page" and "Open in Browser" on the footer of the menu.

    As the developer, you can add and customize up to three menu items that will appear between the icon row and foot items.

    The menu is represented as an Array of Bundles (currently without an icon), menu text and a pendingIntent that Chrome will call on your behalf when the user taps the item.

    // Key for the title string for a given custom menu item
    public static final String KEY_CUSTOM_TABS_MENU_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
    
    // Optional. Use an ArrayList for specifying menu related params. There 
    // should be a separate Bundle for each custom menu item.
    public static final String EXTRA_CUSTOM_TABS_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
    ArrayList menuItemBundleList = new ArrayList<>();
    
    // For each menu item do:
    Bundle menuItem = new Bundle();
    menuItem.putString(KEY_CUSTOM_TABS_MENU_TITLE, menuItemTitle);
    menuItem.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent);
    menuItemBundleList.add(menuItem);
    
    intent.putParcelableArrayList(EXTRA_CUSTOM_TABS_MENU_ITEMS, menuItemBundleList);

    Configure custom enter and exit animations

    Many Android applications use custom View Entrance and Exit animations when transition between Activities on Android. Chrome custom tabs is no different, you can change the entrance and exit (when the user presses Back) animations to keep them consistent with the rest of your application.

    // Optional. Bundle constructed out of
    ActivityOptions that Chrome will be running when
    // it finishes CustomTabActivity. If you start the Custom Tab with 
    // a customized animation, you can specify a matching animation when Custom Tab 
    // returns to your app.
    
    public static final String EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE =
    "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
    Bundle finishBundle = ActivityOptions.makeCustomAnimation(context, R.anim.clientEnterAnimResId, R.anim.CustomTabExitAnimResId).toBundle; 
    intent.putExtra(EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE, finishBundle);
    Bundle startBundle = ActivityOptions.makeCustomAnimation(context, R.anim.CustomTabEnterAnimResId, R.anim.clientExitAnimResId).toBundle; 
    startActivity(intent, startBundle);

    Warm up Chrome to make pages load faster

    By default, when startActivity is called with the correctly configured ACTION_VIEW Intent it will spin up Chrome and launch the URL. This can take up precious time and impact on the perception of smoothness.

    We believe that users demand a near instantaneous experience, so we have provided a Service in Chrome that your app can connect to and tell Chrome to warm up the browser and the native components. We are also experimenting with the ability for you, the developer to tell Chrome the likely set of web pages the user will visit. Chrome will then be able to perform:

    • DNS pre-resolution of the main domain
    • DNS pre-resolution of the most likely sub-resources
    • Pre-connection to the destination including HTTPS/TLS negotiation.

    The process for warming up Chrome is as follows:

    • Connect to the service.
    • Attach a navigation callback with finishSetup so that you know a page was loaded.
    • On the service, call warmup to start Chrome behind the scenes.
    • Create a newSession, this session is used for all requests to the API.
    • Tell Chrome which pages the user is likely to load with mayLaunchUrl.
    • Launch the VIEW Intent with the session ID.

    Connect to the Chrome Service

    If you are not familiar with Connecting to Android Services, the interface is created with AIDL and automatically creates a proxy service class for you.

    The AIDL that defines this service interface can be found in our Sample on Github.

    // Package name for the Chrome channel the client wants to connect to. This
    // depends on the channel name.
    // Stable = com.android.chrome
    // Beta = com.chrome.beta
    // Dev = com.chrome.dev
    public static final String CUSTOM_TAB_PACKAGE_NAME = "com.chrome.dev";  // Change when in stable
    
    // Action to add to the service intent. This action can be used as a way 
    // generically pick apps that handle custom tabs for both activity and service 
    // side implementations.
    public static final String ACTION_CUSTOM_TABS_CONNECTION =
           "android.support.customtabs.action.CustomTabsService";
    Intent serviceIntent = new Intent(ACTION_CUSTOM_TABS_CONNECTION);
    
    serviceIntent.setPackage(CUSTOM_TAB_PACKAGE_NAME);
    context.bindService(serviceIntent, mServiceConnection,
                        Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
    

    Warm up the Browser Process

    boolean warmup(long flags)

    Warms up the browser process and loads the native libraries. Warmup is asynchronous, the return value indicates whether the request has been accepted. Multiple successful calls will also return true.

    Returns true for success.

    Create a new tab session

    boolean newSession(ICustomTabsCallback callback)

    Session is used in subsequent calls to link mayLaunchUrl call, the VIEW intent and the tab generated to each other. The callback IInterface provided here is associated with the created session and should be passed for any consecutive mayLaunchUrl calls. Any updates for the created session (see Custom Tabs Callback below) is also received through this IInterface. Returns whether a session was created successfully. Multiple calls with the same ICustomTabsCallback or a null value will return false.

    Tell Chrome what URL's the user is likely to open

    boolean mayLaunchUrl(ICustomTabsCallback sessionCallback, Uri url, Bundle extras,List otherLikelyBundles)

    Tells the browser of a likely future navigation to a URL. The method warmup() should be called first as a best practice. The most likely URL has to be specified first. 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. Returns whether the operation completed successfully.

    Custom Tabs Connection Callback

    void onNavigationEvent(int navigationEvent, Bundle extras)

    Will be called when a navigation event happens in the Custome tab. The `navigationEvent int` is one of 6 values that defines the state of the the page is in. See below for more information.

    /**
    * Sent when the tab has started loading a page.
    */
    public static final int NAVIGATION_STARTED = 1;
    
    /**
    * Sent when the tab has finished loading a page.
    */
    public static final int NAVIGATION_FINISHED = 2;
    
    /**
    * Sent when the tab couldn't finish loading due to a failure.
    */
    public static final int NAVIGATION_FAILED = 3;
    
    /**
    * Sent when loading was aborted by a user action before it finishes like clicking on a link
    * or refreshing the page.
    */
    public static final int NAVIGATION_ABORTED = 4;
    
    /**
    * Sent when the tab becomes visible.
    */
    public static final int TAB_SHOWN = 5;
    
    /**
    * Sent when the tab becomes hidden.
    */
    public static final int TAB_HIDDEN = 6;
    

    Using the support library on the client side

    See the sample application here.

    FAQ

    • Is this available on iOS?
    • When will this be available on stable channel?
      • We don't have a fixed date or version of Chrome yet. The normal release cycle from being in the Dev Channel of Chrome is about 12 weeks, however we are looking for feedback from developers first.
    • Where can I ask questions?