Implementation guide

Published on Updated on

A complete example is available on the GitHub sample application. 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.

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

The first step for a Custom Tabs integration is adding the AndroidX Browser Library to your project. Open the app/build.gradle file and add the browser library to the dependencies section.

dependencies {
...
implementation "androidx.browser:browser:1.4.0"
}

Once the Browser Library is added to your project there are two sets of possible customizations:

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

The UI Customizations are done by using the CustomTabsIntent and the CustomTabsIntent.Builder classes; the performance improvements are achieved by using the CustomTabsClient to connect to the Custom Tabs service, warm-up the browser and let it know which urls will be opened.

Opening a Custom Tab

A CustomTabsIntent.Builder can be used to configure a Custom Tab. Once ready, call CustomTabsIntent.Builder.build to create a CustomTabsIntent and launch the desired Url with CustomTabsIntent.launchUrl.

String url = "https://google.com/";
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(this, Uri.parse(url));

Specify the launch height of a Custom Tab

By default, Custom Tabs launch as a full-window activity. Starting in Chrome 107, you can use partial Custom Tabs to specify a different launch height such that users can interact with your app while viewing web content. Users will be able to expand the Custom Tab to full-screen by dragging the toolbar handle up and restoring the initial launch height by dragging the handle down.

Example Partial Tab

You can also choose to make the Custom Tab non-resizable, which prevents users from resizing it. If you set a value below 50% of the screen height in the intent, Chrome automatically adjusts the Custom Tab to 50% of the screen height. Custom Tabs are not supported when users are in multi-window or landscape mode.

Partial Custom Tabs are supported by the AndroidX browser library from version 1.5 onwards:

implementation 'androidx.browser:browser:1.5.0-alpha02'

To turn a Custom Tab into a partial Custom Tab, define the initial launch height in pixels by calling new CustomTabsIntent.Builder().setInitialActivityHeightPx(). Furthermore, you need to either:

  1. start a new browser session via a CustomTabsServiceConnection and pass it to the Custom Tabs intent or
  2. start the Custom Tab activity via startActivityForResult.

Starting a new browser session is the recommended approach to launching a partial Custom Tab, as it enables you to warm up the browser process and pre-render webpages. If you don't need these features, using startActivityForResult() is the easier way to launch a partial Custom Tab.

Important
  • Specifying the initial activity height will not have an effect if the default browser does not support resizing the Custom Tab. In this case, the intent extra will be ignored and the Custom Tab will span the complete display height.

  • Custom Tabs will inherit the host app's color scheme for the user interface properties above. You will be responsible for ensuring visual consistency for these properties before launching a Custom Tab. This means the CustomTabColorScheme.navigationBarColor and CustomTabColorScheme.navigationBarDividerColor properties do not work when building an intent for a partial Custom Tab.

Launch a partial Custom Tab with an existing session

CustomTabsSession customTabsSession;

// ...

CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder(customTabsSession)
.setInitialActivityHeightPx(500)
.setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
// ...
.build();

customTabsIntent.launchUrl(context, Uri.parse(url))

Launch a partial Custom Tab via startActivityForResult

private ActivityResultLauncher<String> mCustomTabLauncher = registerForActivityResult(new ActivityResultContract<String, Integer>() {
@Override
public Integer parseResult(int statusCode, @Nullable Intent intent) {
return statusCode;
}

@NonNull
@Override
public Intent createIntent(@NonNull Context context, String url) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder()
.setInitialActivityHeightPx(500)
.setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
.setToolbarCornerRadiusDp(10);
Intent customTabsIntent = builder.build().intent;
customTabsIntent.setData(Uri.parse(url));
return customTabsIntent;
}
}, new ActivityResultCallback<Integer>() {
@Override
public void onActivityResult(Integer statusCode) {
// ...
}
});

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Button selectButton = findViewById(R.id.select_button);
selectButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mCustomTabLauncher.launch(customTabsIntent.intent);
}
});
}

Configure the color of the address bar

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

The snippet below changes the background color for the address bar. colorInt is an int that specifies a Color.

int colorInt = Color.parseColor("#FF0000"); //red
CustomTabColorSchemeParams defaultColors = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(colorInt)
.build();
intentBuilder.setDefaultColorSchemeParams(defaultColors);

Configure a custom action button

builder.setActionButton(icon, description, pendingIntent, tint);
Screenshot of the Action Button in the Tumblr app

As the developer of your app, you have full control over the Action Button that is presented to your users inside the browser 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 the browser when your user hits the action button. The icon is currenlty 24dp in height and 24-48 dp in width.

It can be customized by calling CustomTabsIntentBuilder#setActionButton:

  • icon is a Bitmap to be used as the image source for the action button.
  • description is a String be used as an accessible description for the button.
  • pendingIntent is a PendingIntent to launch when the action button or menu item was tapped. The browser will be calling PendingIntent#send on taps after adding the url as data. The client app can call Intent#getDataString to get the url.
  • tint is a boolean that defines if the Action Button should be tinted.

Configure a custom menu

builder.addMenuItem(menuItemTitle, menuItemPendingIntent);
Screenshot of the menu on the Twitter app

The 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.

Custom Tabs will have a set of default actions provided by the browser. Those actions can include items like "Forward", "Page Info", "Refresh", "Find in Page" or "Open in Browser".

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

A menu item is added by calling CustomTabsIntent.Builder#addMenuItem with title and a PendingIntent that browser will call on your behalf when the user taps the item are passed as parameters.

Configure custom enter and exit animations

Many Android applications use custom View Entrance and Exit animations when transitioning between Activities on Android. 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.

builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left);
builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right);

Warm up the browser to make pages load faster

By default, when CustomTabsIntent#launchUrl is called, it will spin up the browser 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 that your app can connect to and tell the browser and its native components to warm up. Custom Tabs also provide the ability for you, the developer to tell the browser the likely set of web pages the user will visit. Browsers 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 the browser is as follows:

Connect to the Custom Tabs Service

The CustomTabsClient#bindCustomTabsService method takes away the complexity of connecting to the Custom Tabs service.

Create a class that extends CustomTabsServiceConnection and use onCustomTabsServiceConnected to get an instance of the CustomTabsClient. This instance will be needed on the next steps.

// 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.android.chrome"; // Change when in stable

CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
@Override
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
mCustomTabsClient = client;
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};
boolean ok = CustomTabsClient.bindCustomTabsService(this, mPackageNameToBind, connection);

When targeting API level 30, in order to connect to a Custom Tabs service, you need to add a queries section to your Android Manifest, declaring an intent-filter that matches browsers with Custom Tabs support.

<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService"
/>

</intent>
</queries>

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 if successful.

Create a new tab session

boolean newSession(CustomTabsCallback callback)

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

Tell the browser what URLs the user is likely to open

boolean mayLaunchUrl(Uri url, Bundle extras, List<Bundle> otherLikelyBundles)

This CustomTabsSession method 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 Custom Tab. The navigationEvent int is one of 6 values that defines the state of 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;
/**
* Called when the tab is resized.
*/

private static class ResizeCallback extends CustomTabsCallback {
@Override
public void onActivityResized(int height, int width, @NonNull Bundle extras) {
...
}
}
}

What happens if the user doesn't have a browser that supports Custom Tabs installed?

Custom Tabs is supported by most Android browsers. Nevertheless, since it uses an ACTION_VIEW Intent with key Extras to customize the UI it will open in the system browser, or the user's default browser if Custom Tabs is not supported.

If the user has a browser that supports Custom Tab installed and it is the default browser, it will automatically pick up the EXTRAS and present a customized UI.

How can I check whether the Android device has a browser that supports Custom Tab?

It is possible to use the PackageManager to query the Android device for applications that can handle Custom Tabs. We query for applications that are able to handle http Intents, then check if those applications also declare support for the Custom Tabs Service:

/**
* Returns a list of packages that support Custom Tabs.
*/

public static ArrayList<ResolveInfo> getCustomTabsPackages(Context context) {
PackageManager pm = context.getPackageManager();
// Get default VIEW intent handler.
Intent activityIntent = new Intent()
.setAction(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.fromParts("http", "", null));

// Get all apps that can handle VIEW intents.
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
ArrayList<ResolveInfo> packagesSupportingCustomTabs = new ArrayList<>();
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
// Check if this package also resolves the Custom Tabs service.
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs.add(info);
}
}
return packagesSupportingCustomTabs;
}

Android 11 has introduced package visibility changes. If your Android app is targeting API level 30 or above, adding a queries section to AndroidManifest.xml is needed, otherwise the code snippet above won't return results:

<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService"
/>

</intent>
</queries>

Updated on Improve article

We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.