# Chrome on Android

> Chrome on Android offers the Chromium APIs WhiskerJS reads, plus the audio output latency, which the library reads only on Android and Safari.

Source: https://whiskerjs.com/browsers/chrome-android/ (last updated 2026-10-26)

> **In plain words:**
>
> Chrome on Android runs the same engine as desktop Chrome, so WhiskerJS reads almost the same set of signals, including the Chromium-only ones such as the device memory and Client Hints. On a phone, Client Hints usually say more about the device (it is mobile, and often which model). WhiskerJS also reads the audio output latency here, which it skips on desktop Chrome. Nothing is excluded on purpose.

## At a glance

- Browser class: `chromium` (the user agent has a `Chrome/<number>` token); `chromium_webview` for Android WebView (in-app browsers).
- Excluded components: none.
- Read here but skipped on desktop Chrome: `audio_latency` (the user agent says Android).
- Likely different from desktop: `touch_support`, `ua_client_hints` (mobile, model, form factors), `plugins`, `keyboard_layout`.
- Lab sample: none for Android yet.

## What works

- **Chromium-only components**: [`device_memory`](https://whiskerjs.com/signals/device-memory/) (from Chromium 146 with the tag `;c146`, buckets 2, 4 or 8 on Android), [`heap_limit`](https://whiskerjs.com/signals/heap-limit/), [`ua_client_hints`](https://whiskerjs.com/signals/ua-client-hints/) and [`ua_versions`](https://whiskerjs.com/signals/ua-versions/) read the same APIs as on desktop. In `ua_client_hints`, `mobile=1`, the `model` and the `form` factors come from the browser’s own answer to `getHighEntropyValues()`, so they often tell phone models apart better than the user agent string.
- **[`audio_latency`](https://whiskerjs.com/signals/audio-latency/)**: WhiskerJS creates an `AudioContext`, reads `baseLatency`, and closes the context at once. Nothing is played. It is outside both ids because it follows the audio output device (headphones, speaker, Bluetooth).
- **[`touch_support`](https://whiskerjs.com/signals/touch-support/)**: `navigator.maxTouchPoints`, whether a `TouchEvent` can be created, and whether `ontouchstart` exists; a phone typically gives something like `points=5,event=1,start=1`.
- **[`screen_resolution`](https://whiskerjs.com/signals/screen-resolution/)**: the larger side first, so rotating the phone does not change it.
- Graphics, audio rendering, fonts, font preferences, Math, codecs, locale and the display preference queries are read as on any current browser.

## What is unavailable or blocked

- [`keyboard_layout`](https://whiskerjs.com/signals/keyboard-layout/) uses `navigator.keyboard.getLayoutMap()`, an API meant for physical keyboards; expect it to be `unavailable` on Android. It is outside the ids either way.
- [`plugins`](https://whiskerjs.com/signals/plugins/): mobile builds often list no plugins, and then the component reports the plain value `none`.
- [`apple_pay`](https://whiskerjs.com/signals/apple-pay/) and [`private_click`](https://whiskerjs.com/signals/private-click/) report the plain value `none`.
- [`webgpu`](https://whiskerjs.com/signals/webgpu/): availability depends on the Chrome version, the Android version and the GPU; it is `unavailable` (`no_api` or `no_context`) where there is no adapter.
- `ua_client_hints` and `ua_versions` report `he=blocked` when a Permissions Policy refuses the detailed hints.

## What WhiskerJS excludes and why

Nothing. The `chromium` row of the exclusion table is empty. If the browser or an extension adds noise to canvas or WebGL readback, the [double render](https://whiskerjs.com/glossary/#double-render) turns `canvas` or `webgl_image` into `noisy` (`render_mismatch`), and the [known-answer probes](https://whiskerjs.com/signals/noise-profile/) catch noise that is the same on both reads (`seeded_noise`, also for `audio`), instead of letting the noise change the id on every run.

### Android WebView

Pages shown inside Android apps run in Android WebView, which is Chromium too. WhiskerJS classes it `chromium_webview` (since signalsVersion 6) when the user agent’s platform part carries the `wv` token (kept by the reduced WebView user agent) or `navigator.userAgentData` has the brand `Android WebView`. Chrome for Android has neither and stays `chromium`. The `chromium_webview` row is empty: WebView randomises nothing, and the class itself tells your server that the visit comes from an in-app browser.

## Integrity flags to expect

- [`headless_markers`](https://whiskerjs.com/docs/integrity/#headless_markers) checks for a missing `window.chrome` only when the user agent is desktop Chrome: user agents with `Mobile` or `Android` are skipped by that part of the check, so normal Android Chrome does not raise it that way. A 0x0 outer window still raises it.
- [`automation_webdriver`](https://whiskerjs.com/docs/integrity/#automation_webdriver) when the browser is controlled through WebDriver or a similar protocol, for example in device test farms.
- [`software_renderer`](https://whiskerjs.com/docs/integrity/#software_renderer) in emulators that render WebGL in software, and [`vm_renderer`](https://whiskerjs.com/docs/integrity/#vm_renderer) in the Android emulator (`Android Emulator`, `OpenGL ES Translator`).
- [`ua_ch_mismatch`](https://whiskerjs.com/docs/integrity/#ua_ch_mismatch) when the user agent disagrees with Client Hints. “Request desktop site” (a Linux user agent with the platform `Android`) is accepted, and the check does not run on Android WebView, where apps set their own user agent.
- [`media_query_mismatch`](https://whiskerjs.com/docs/integrity/#media_query_mismatch) when a coarse pointer is reported with no touch points, or the CSS device size disagrees with `screen`, as bad mobile emulation does.
- [`native_tampering`](https://whiskerjs.com/docs/integrity/#native_tampering) and related lie checks if something overrides browser functions.

None of them is a verdict; see [Integrity checks](https://whiskerjs.com/docs/integrity/#using-the-flags).

## Early lab observations

There is no lab data for Chrome on Android yet. The early, small sample at signalsVersion 1 covered four pairs only: Mac Chrome, Mac Safari, iPhone Chrome and iPhone Safari. The table below is filled from the code, not from measurements on Android.

## Testing it yourself

Open the [live demo](https://whiskerjs.com/demo/) on the phone, run it a few times, rotate the phone, switch between headphones and the speaker, and watch which components move: `screen_resolution` should stay the same and `audio_latency` may change, which is why it is outside the ids. The [lab mode](https://whiskerjs.com/demo/#lab) copies one line per run for your own records. Nothing is sent anywhere.

## Support table for Chrome on Android

Expected status of every component in this browser at signalsVersion 9, from [the support matrix](https://whiskerjs.com/browsers/support-matrix/) (updated 2026-10-26). Basis "code" means expected from the library code and documentation, not yet measured; "lab" means seen in the early lab sample.

| Component | Status | Basis | Note |
| --- | --- | --- | --- |
| [`apple_pay`](https://whiskerjs.com/signals/apple-pay/) | none | code | No ApplePaySession: reports the plain value none. |
| [`audio`](https://whiskerjs.com/signals/audio/) | ok | code | noisy / seeded_noise if the audio known-answer probe sees noise. |
| [`audio_latency`](https://whiskerjs.com/signals/audio-latency/) | ok | code | Read when the user agent says Android. |
| [`browser_class`](https://whiskerjs.com/signals/browser-class/) | ok | code | Class chromium; chromium_webview in Android WebView (in-app browsers). |
| [`canvas`](https://whiskerjs.com/signals/canvas/) | ok | code | noisy / render_mismatch or seeded_noise if an extension or setting adds noise to readback. |
| [`canvas_readback`](https://whiskerjs.com/signals/canvas-readback/) | ok | code |   |
| [`color_depth`](https://whiskerjs.com/signals/color-depth/) | ok | code |   |
| [`color_gamut`](https://whiskerjs.com/signals/color-gamut/) | ok | code |   |
| [`content_blockers`](https://whiskerjs.com/signals/content-blockers/) | ok | code | none, or the bait names a content blocker hid. |
| [`cpu_architecture`](https://whiskerjs.com/signals/cpu-architecture/) | ok | code |   |
| [`device_memory`](https://whiskerjs.com/signals/device-memory/) | ok | code | From Chromium 146 the value carries the tag ;c146 (new buckets). |
| [`dynamic_range`](https://whiskerjs.com/signals/dynamic-range/) | ok | code |   |
| [`font_preferences`](https://whiskerjs.com/signals/font-preferences/) | ok | code |   |
| [`fonts`](https://whiskerjs.com/signals/fonts/) | ok | code |   |
| [`fonts_canvas`](https://whiskerjs.com/signals/fonts-canvas/) | ok | code |   |
| [`forced_colors`](https://whiskerjs.com/signals/forced-colors/) | ok | code |   |
| [`hardware_concurrency`](https://whiskerjs.com/signals/hardware-concurrency/) | ok | code | Read 16 times; noisy / read_varies if the reads disagree. Some browsers cap or round the value. |
| [`heap_limit`](https://whiskerjs.com/signals/heap-limit/) | ok | code |   |
| [`intl_locale`](https://whiskerjs.com/signals/intl-locale/) | ok | code |   |
| [`keyboard_layout`](https://whiskerjs.com/signals/keyboard-layout/) | varies | code | Chromium API meant for physical keyboards; on Android expect unavailable. |
| [`languages`](https://whiskerjs.com/signals/languages/) | ok | code |   |
| [`math`](https://whiskerjs.com/signals/math/) | ok | code |   |
| [`media_codecs`](https://whiskerjs.com/signals/media-codecs/) | ok | code |   |
| [`noise_profile`](https://whiskerjs.com/signals/noise-profile/) | ok | code | canvas=exact,webgl=exact,audio=exact on a stock browser; webgl=na without WebGL. |
| [`pdf_viewer`](https://whiskerjs.com/signals/pdf-viewer/) | ok | code |   |
| [`permissions`](https://whiskerjs.com/signals/permissions/) | ok | code |   |
| [`pixel_ratio`](https://whiskerjs.com/signals/pixel-ratio/) | ok | code |   |
| [`platform`](https://whiskerjs.com/signals/platform/) | ok | code |   |
| [`plugins`](https://whiskerjs.com/signals/plugins/) | varies | code | Mobile builds often list no plugins and then report the plain value none. |
| [`prefers_contrast`](https://whiskerjs.com/signals/prefers-contrast/) | ok | code |   |
| [`private_click`](https://whiskerjs.com/signals/private-click/) | none | code | No attributionSourceId on anchors: reports the plain value none. |
| [`reduced_motion`](https://whiskerjs.com/signals/reduced-motion/) | ok | code |   |
| [`screen_frame`](https://whiskerjs.com/signals/screen-frame/) | ok | code |   |
| [`screen_resolution`](https://whiskerjs.com/signals/screen-resolution/) | ok | code |   |
| [`speech_voices`](https://whiskerjs.com/signals/speech-voices/) | ok | code | Digest of the voice list, or none; some browsers fill the list late. |
| [`storage`](https://whiskerjs.com/signals/storage/) | ok | code |   |
| [`time_zone`](https://whiskerjs.com/signals/time-zone/) | ok | code |   |
| [`touch_support`](https://whiskerjs.com/signals/touch-support/) | ok | code |   |
| [`ua_client_hints`](https://whiskerjs.com/signals/ua-client-hints/) | ok | code | he=blocked when a Permissions Policy refuses the detailed hints. |
| [`ua_versions`](https://whiskerjs.com/signals/ua-versions/) | ok | code |   |
| [`vendor`](https://whiskerjs.com/signals/vendor/) | ok | code |   |
| [`vendor_flavors`](https://whiskerjs.com/signals/vendor-flavors/) | ok | code |   |
| [`webgl_image`](https://whiskerjs.com/signals/webgl-image/) | ok | code | noisy / render_mismatch or seeded_noise if an extension or setting adds noise to readback. |
| [`webgl_params`](https://whiskerjs.com/signals/webgl-params/) | ok | code | Where WebGL 1 is available; unavailable / no_context without it. |
| [`webgl_renderer`](https://whiskerjs.com/signals/webgl-renderer/) | ok | code |   |
| [`webgl_vendor`](https://whiskerjs.com/signals/webgl-vendor/) | ok | code |   |
| [`webgpu`](https://whiskerjs.com/signals/webgpu/) | varies | code | Depends on browser version, operating system and GPU; needs a secure context. unavailable / no_api without navigator.gpu, no_context without an adapter. |

## Questions and answers

### Does rotating the phone change the id?

No. screen_resolution reports the larger side first, so portrait and landscape give the same value. pixel_ratio and screen_frame are outside the ids anyway.

### Why is audio_latency read on Android but not on desktop Chrome?

Desktop Chrome prints a console warning when a page creates an AudioContext without a user gesture, and WhiskerJS writes nothing to the console. The library reads the latency only for Safari and for user agents that say Android.

### What about in-app browsers on Android?

Pages inside Android apps run in Android WebView. WhiskerJS recognises it by the wv token in the user agent or the Android WebView brand in userAgentData and reports the class chromium_webview (since signalsVersion 6). Nothing is excluded: WebView randomises nothing, and the class itself tells your server it is an in-app browser.

### Why does device_memory end in ;c146?

Chromium 146 changed the memory buckets; on Android they are 2, 4 or 8 GB. From that version WhiskerJS tags the value, for example 8;c146, so a server can tell which scheme it comes from. Expect one id change per user at the update to 146.

### Is there lab data for Android?

Not yet. The early lab sample covered Mac Chrome, Mac Safari, iPhone Chrome and iPhone Safari only.

## Related pages

- [Chrome (desktop)](https://whiskerjs.com/browsers/chrome-desktop/): The same engine on computers.
- [Samsung Internet](https://whiskerjs.com/browsers/samsung-internet/): Another Chromium browser on Android.
- [touch_support](https://whiskerjs.com/signals/touch-support/): Touch points and touch events.
- [audio_latency](https://whiskerjs.com/signals/audio-latency/): Read only on Android and in Safari.
- [Browser support matrix](https://whiskerjs.com/browsers/support-matrix/): All browsers side by side.
