android-browser-helper: Custom Tabs and TWA without the boilerplate
The Android Browser Helper library helps developers use Custom Tabs and Trusted Web Activities on top of the AndroidX browser support library.
At a glance
- What is it?
- The Android Browser Helper library gives an Android app default implementations for launching Custom Tabs and Trusted Web Activities, including a launcher activity, provider selection and inbound intent validation. It is Apache-2.0 licensed and maintained by GoogleChrome, and it also ships a quality enforcer that crashes your app on purpose under some conditions.
- Who is it for?
- Adopt android-browser-helper if you are shipping an Android app that opens web content and want a TWA or Custom Tabs launcher with inbound intent validation you would otherwise have to write and get wrong. Do not adopt it if you need a normal WebView, since the library exists to hand that work to the browser, and read the QualityEnforcer section before shipping, because the default behaviour is to throw an uncaught exception on your main thread.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 11 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library does for a TWA application
The Android Browser Helper library helps developers use Custom Tabs and Trusted Web Activities on top of the AndroidX browser support library. Its pitch is that it contains default implementations of many of the common tasks a developer finds themselves requiring, and the README lists three examples.
The first is creating a Launcher Activity that simply launches a Trusted Web Activity. The second is code for choosing an appropriate Custom Tabs provider. The third is creating an Activity to launch the browser's site settings for a TWA.
Each of those is small in isolation, and each is a place where a mistake is expensive. The launcher activity is the entry point of your whole application, since in a TWA app it is the activity the launcher icon points at, and it is where the decision about what URL to open and whether that URL is trusted gets made. The provider selection matters because a device may have several browsers installed and some of them will not support the features you need, and choosing badly produces a broken experience on those devices rather than a crash. The site settings activity is a convenience that removes a screen of manual navigation.
The wider point is what the library does about the browser. A TWA is a web app running inside a browser that the operating system treats as part of your application, which means the browser keeps its own UI, its own cookie jar and its own security model. That is a good deal for security and for rendering fidelity, and it means the developer no longer owns a WebView. The library is the set of glue that makes handing the work over work reliably.
The repository is Java, Apache-2.0 licensed, and hosted under the GoogleChrome organisation. The layout shows separate modules: androidbrowserhelper for the core, locationdelegation for the optional location sharing, playbilling for the optional purchase history sharing, plus demos, docs and a gradle setup. The billing module has its own release, billing-1.2.0, which tells you the optional features version independently of the core.
Adding the dependency and what gets shared with the browser
The library comes from Google Maven, so the dependency goes into the application build.gradle:
dependencies {
//...
implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.7.4'
}The version 2.7.4 matches the most recent release listed for the library, and there is no configuration step beyond that in the README.
What the library does share with the browser is documented at length, because Google Play's data disclosure requirements demand it, and that section is worth reading before you ship rather than after a review question arrives.
Web browsing information is shared. URLs handled by the application are shared with the browser when a Custom Tab or a Trusted Web Activity are launched. URLs are also shared by certain features like mayLaunchUrl(), so that the browser can speed up loading performance of those pages. That second one is easy to miss: a feature that sounds like a local check has a privacy consequence, because the point of the check is to let the browser pre-warm a page.
If the WebView fallback feature is enabled by the developer, the application may store the navigation history and browser storage, like cookies, on the device.
Two optional features share more. The SDK may share location data with the host browser when the location delegation library is used, and users control that with the Android permission dialogs and system settings. The SDK may share purchase history data with the host browser when the Google Play billing library is used, and only purchases made within the application are shared.
The last line of that section is the important one for a security review. This SDK does not transfer any information over the network. The data goes to the browser on the device, and web browsing information may be stored if the WebView fallback is enabled.
QualityEnforcer crashes your app on purpose
This is the section that deserves the most attention, because it is unusual behaviour that is documented precisely and is easy to miss.
By design, QualityEnforcer is the default CustomTabsCallback wired by LauncherActivity.getCustomTabsCallback(), which returns a new QualityEnforcer, by ShortcutTrampolineActivity, and by the convenience TwaLauncher.launch(Uri) overload.
When the connected Trusted Web Activity provider sends the quality_enforcement.crash extra callback, which is QualityEnforcer.CRASH, for example when a verified-origin navigation fails or violates TWA quality criteria, QualityEnforcer deliberately throws an uncaught RuntimeException on the host application's main thread.
The README is explicit that this behaviour is pre-existing and supported. It is not a bug and it is not going to be fixed. The reasoning behind it is that a TWA whose quality criteria are violated is broken in a way that is better surfaced loudly than masked, and a crash in development is how you find out.
If your application does not want provider-triggered quality-enforcement crashes, you can override the protected LauncherActivity.getCustomTabsCallback() method and return a plain CustomTabsCallback, or pass a plain CustomTabsCallback to the full TwaLauncher.launch(...) overload. There is one important limitation on that escape hatch, and it is stated: overriding LauncherActivity.getCustomTabsCallback() does not affect shortcut launches, because ShortcutTrampolineActivity constructs a new QualityEnforcer itself.
So opting out is partial by design. An application that uses shortcuts will still crash if the provider sends the crash extra on a shortcut launch, and there is no documented way to change that.
Shortcuts need the trampoline activity, not the launcher
The second half of the README is a specific, reproducible bug with a specific fix, and the fix is a separate activity rather than a configuration flag.
The problem: when implementing shortcuts, for example from shortcuts.xml, in a TWA application, launching the TWA through LauncherActivity on Android Desktop, such as ChromeOS, can result in unresponsive windows due to window manager interactions with translucent activities.
The solution is to use the dedicated ShortcutTrampolineActivity for all of your app's shortcut intents.
The implementation is a resource file at res/xml/shortcuts.xml, with the targetClass pointing at the trampoline and the shortcut target URL in the android:data field:
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="twa_shortcut"
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutShortLabel="@string/shortcut_label">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="YOUR_PACKAGE_NAME"
android:targetClass="com.google.androidbrowserhelper.trusted.ShortcutTrampolineActivity"
android:data="https://your-twa-domain.com/shortcut-target-url" />
</shortcut>
</shortcuts>And then the manifest has to reference it inside the launcher activity:
<activity android:name=".MyLauncherActivity" ...>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
...
</activity>The reasoning for why the trampoline exists is in the last paragraph of that section. ShortcutTrampolineActivity runs with Theme.NoDisplay and processes the shortcut launch securely by validating the URL against your configured TWA domains, routes the launch asynchronously using the application context, and closes itself instantly before any window transitions are impacted.
That is the whole point of the design. A theme with no display means no window is created, so the window manager never has an opportunity to interact badly with a translucent activity, and the URL validation happens before any of that. The cost is that you must route every shortcut intent through this activity rather than through your own launcher, and the manifest change is what enforces that.
Inbound intent validation and the punycode trap
The validation rules are the most detailed part of the documentation, and they describe a security boundary rather than a convenience.
LauncherActivity, including URLs synthesized by getUrlForIntent(), and ShortcutTrampolineActivity verify that the target origin matches android.support.customtabs.trusted.DEFAULT_URL or an entry in android.support.customtabs.trusted.ADDITIONAL_TRUSTED_ORIGINS, or that the URI matches a BROWSABLE intent-filter with a concrete android:host declared on the launcher activity or its activity-alias, including any android:path, pathPrefix or pathPattern constraints on that filter. Filters whose host is the wildcard * are ignored.
The comparison rules are exact and have teeth. Schemes and hosts are compared ASCII-case-insensitively. A URI or configured origin whose scheme or host contains non-ASCII characters, including percent-encoded ones, never matches, so internationalised domains have to be declared in punycode form with the xn-- prefix.
That last rule is the one that will cost you an afternoon if you do not know it. A developer with a domain in a non-Latin script writes it in Unicode in their manifest or their trusted origins list, the comparison silently fails, and the application falls back to the default URL or drops the launch. There is no error message, because from the library's point of view an untrusted URL is a normal thing to reject.
The failure behaviour differs between the two activities, and the difference matters. LauncherActivity falls back to DEFAULT_URL for an untrusted https URI and does not forward it via EXTRA_ORIGINAL_LAUNCH_URL. ShortcutTrampolineActivity drops the launch. So a misconfigured shortcut fails silently while a misconfigured launcher opens the wrong page.
There is one more asymmetry to know about. ShortcutTrampolineActivity does not consult LauncherActivity.isTrustedIntentUrl(Uri) overrides, so if you have subclassed LauncherActivity and overridden that method to add your own trust logic, the trampoline will not use it. Shortcut trust is governed by the declared TWA domains and the manifest filters only.
WebViewFallbackActivity is the third case, and it is different in kind. It trusts the launch URL and the EXTRA_ORIGINS it is started with, because they come from the application's own configuration or from URLs already validated by LauncherActivity or ShortcutTrampolineActivity. The documentation is cut off mid-sentence about how to declare it, so the details of that declaration are not in the text available here and are worth checking in the source.
Maintenance, licence and where this sits
The repository is not archived and the last push was on 2026-10-12, with the library release android-browser-helper-2.7.4 dated 2026-10-02 and 2.7.3 from 2026-08-12. The billing module released separately as billing-1.2.0 on 2026-07-30, which confirms the optional features version on their own schedule.
The licence is Apache-2.0, with the LICENSE file at the repository root. That is the licence you want for a library that goes into an application you distribute, since it carries an explicit patent grant and no copyleft obligation on your application code.
The maintenance arrangement is a single organisation, and that is the thing to weigh. This is a library published by the team that builds the browser it integrates with, which means the API is shaped by one company's implementation priorities rather than by a community's needs. That is also why the documentation is unusually specific about behaviour that would otherwise be implementation detail, such as the exact fallback semantics of the two activities. You are relying on an internal contract, and the documentation is telling you what the contract currently is.
The alternative approaches differ in what they assume. A hand written Custom Tabs launch is a dozen lines, and many teams that only need to open a URL should write it themselves rather than take a dependency. Building a TWA from scratch means implementing the launcher activity, the service worker association, the asset links file and the trusted origin list, which is where the value of this library sits. A different approach entirely is embedding a WebView, which keeps the page inside your process at the cost of giving up the browser's rendering, its security model and its shared cookies, and which is the reason the WebView fallback here is explicitly optional and flagged for data disclosure.
The Play Services path is worth naming too, since it is the other way to launch a browser from an Android app. The difference is that it depends on Google Play Services and adds a dependency on the Play ecosystem, while this library works against any browser on the device that supports Custom Tabs, and works with a Trusted Web Activity on the browsers that implement it.
The thing to verify before shipping is the interaction between the quality enforcer and your own error handling, because the default is to crash on the main thread, and a crash in a Play Store build is a very visible way to learn that a verified-origin navigation failed.
Editorial conclusion
Adopt android-browser-helper if you are shipping an Android app that opens web content and want a TWA or Custom Tabs launcher with inbound intent validation you would otherwise have to write and get wrong. Do not adopt it if you need a normal WebView, since the library exists to hand that work to the browser, and read the QualityEnforcer section before shipping, because the default behaviour is to throw an uncaught exception on your main thread. Verify first that your TWA domains are declared in punycode form, that your shortcut intents target ShortcutTrampolineActivity rather than LauncherActivity, and that the version you depend on matches the 2.7.x line.
Frequently asked questions
How do I add the Android Browser Helper to my app?
Add it as a dependency in your application build.gradle, using `implementation 'com.google.androidbrowserhelper:androidbrowserhelper:2.7.4'`. The library is published on Google Maven, and no further configuration is described in the README.
What is QualityEnforcer and why does my app crash?
QualityEnforcer is the default CustomTabsCallback. When the TWA provider sends the quality_enforcement.crash extra, for example after a verified-origin navigation fails, it deliberately throws an uncaught RuntimeException on the main thread. The behaviour is pre-existing and supported, and you can opt out by overriding LauncherActivity.getCustomTabsCallback() and returning a plain CustomTabsCallback, though that does not affect shortcut launches.
Why do my TWA shortcuts hang on ChromeOS?
Launching a TWA through LauncherActivity on Android Desktop can result in unresponsive windows because of window manager interactions with translucent activities. Route all shortcut intents through ShortcutTrampolineActivity instead, which runs with Theme.NoDisplay, validates the URL against your TWA domains and closes itself before any window transition.
How do I declare an internationalised domain for a Trusted Web Activity?
In punycode form with the xn-- prefix. Schemes and hosts are compared ASCII-case-insensitively, and a URI or configured origin whose scheme or host contains non-ASCII characters, including percent-encoded ones, never matches, so a Unicode domain will silently fail to validate.
What data does the Android Browser Helper share with the browser?
URLs handled by the application when a Custom Tab or TWA is launched, and URLs passed to features such as mayLaunchUrl() so the browser can speed up loading. If the WebView fallback is enabled, the application may store navigation history and browser storage such as cookies. The library states that it does not transfer any information over the network.
What license is the Android Browser Helper released under?
Apache-2.0, with the LICENSE file at the repository root and the code maintained in the GoogleChrome GitHub organisation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/googlechrome-android-browser-helper)Was this useful?