Implementation guide
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.
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:
- start a new browser session via a
CustomTabsServiceConnection
and pass it to the Custom Tabs intent or - 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.
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
andCustomTabColorScheme.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);
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 aBitmap
to be used as the image source for the action button.description
is aString
be used as an accessible description for the button.pendingIntent
is aPendingIntent
to launch when the action button or menu item was tapped. The browser will be callingPendingIntent#send
on taps after adding the url as data. The client app can callIntent#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);
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:
- Use
CustomTabsClient#bindCustomTabsService
to connect to the service. - Once the service is connected, call
CustomTabsClient#warmup
to start the browser behind the scenes. - Call
CustomTabsClient#newSession
to create a new session. This session is used for all requests to the API. - Optionally, attach a
CustomTabsCallback
as a parameter when creating a new session, so that you know a page was loaded. - Tell the browser which pages the user is likely to load with
CustomTabsSession#mayLaunchUrl
- Call the
CustomTabsIntent.Builder
constructor passing the createdCustomTabsSession
as a parameter.
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
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>