> For the complete documentation index, see [llms.txt](https://cmp.docs.sirdata.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/versions.md).

# Android — Versions

All notable changes to the Android SDK. Entries are newest first.

Sections follow the same headings throughout: **Features** for new or changed behaviour, **Fixes** for corrections, **Requirements** at the end. Breaking changes are called out in their own block at the top of the entry.

> Maintainers: a new entry is dated on the day the version is actually published, not when it is written. An entry marked `(unreleased)` has not been tagged yet — see the release checklist.

## Next release (unreleased)

**Fixes**

* **When the configuration could not be loaded, the consent screen says so.** It showed the banner with the default theme and empty lists; it now shows a message and a Close button, like iOS. The back button closes it without reporting a close.

## 2.0.0 (2026-10-05)

Published on Maven Central, like 1.2.0. The version number moves to 2.0.0 on every channel at once, because the iOS SDK opens its CocoaPods channel with it.

**Breaking changes**

* `ConsentButtonStyle.CLOSE` is removed from the consent UI's public `ConsentButtonStyle` enum. The "Close link" refusal style is now rendered as a link in the banner header, which left the value without a use.
* `scopeReminderOpensHostnames` is removed from the consent UI's public `ConsentUiState`, which gains `scopeReminderSegments` and `purposesText2Segments`. The scope reminder now carries its links as segments, each opening its own target, instead of making the whole text one tap target. Unlike the data classes below, no overload keeps the previous constructor or `copy` of `ConsentUiState`.
* Four public data classes gain a field: `GoogleConsentState.adSignalsFromTcf`, `UserAction.page_view_id`, `Settings.sirdataRestrictions` and `Info.lastUpdate`. Their previous constructor and `copy` signatures are kept as overloads, so source code is unaffected; code compiled against 1.2.0 that calls them with default arguments must be recompiled.

**Features**

* **The consent adapters forward each choice to Firebase Analytics, AppsFlyer, Adjust and Branch.** Measured against the latest version of each SDK, none of the four received it correctly in 1.2.0, and nothing said so: the AppsFlyer and Branch adapters called APIs that have moved or do not exist, the Adjust adapter turned measurement off after an "Accept all", and in a minified release build the Firebase adapter sent nothing. A failing adapter is now logged under its name, on the `SirDataCMP` Logcat tag; only an absent SDK stays silent. See [Sharing consent with vendor SDKs](/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md).
* **The SDK ships its R8 rules.** A minified release build no longer fails on `org.slf4j.impl.StaticLoggerBinder`, and the adapters survive shrinking: the rules you added for the SDK can go.
* **"TCF/Google Consent Mode Interoperability" (`consentModeFromTcf`) behaves as on the web.** The SDK writes `IABTCF_EnableAdvertiserConsentMode` = 1, `ping` and `getTCData` carry `enableAdvertiserConsentMode: true`, and Firebase Analytics receives `analytics_storage` only, so that it derives the three ad signals from the TC string. 1.2.0 sent those three as `denied`, whatever the user chose.
* **Publisher restrictions are applied.** The consent UI no longer asks a partner for a purpose the publisher forbids it, the TC string and the `IABTCF_PublisherRestrictions{ID}` keys carry the restrictions, and the signals sent to Google through Firebase and AppsFlyer take them into account.
* The Sirdata consent string carries the restrictions the configuration applies to Sirdata partners, and the version of the Sirdata list it was built from (it always said 1).
* The three non-versioned lists (Global Vendor List, Sirdata list, Google Additional Consent providers) are requested with the visitor's country, when it is known, so the API can filter them.
* The consent archive carries the country and the revision of the configuration that was displayed. Every hit carries a `page_view_id`, drawn once per `initialize()`, so the load, the clicks and the answer of one display share it.
* **The banner's refusal button follows the publisher's style as the web does.** "Close link" puts the refusal label in the header, in place of the close cross (it was a discreet text button next to the other buttons); "Big button (secondary color)" is outlined like "Customize my choices" (it was a solid grey block); "Big button (main color)" looks exactly like "Accept all" (it was drawn at 70 % opacity). The manual re-opening shows a "Reject all" button whatever the refusal type and style, with the close cross at the top; it showed no refusal at all.
* The publisher's text size scales the consent UI by the web's ratios, 12/13 and 14/13 for "small" and "large" (about ±7.7 %), instead of ±15 %.
* The purposes and partners screens carry the links of their scope reminder, as on the web: the list of the sites the consent covers, and the Consent Framework site.
* Outside the US screen, the close button that saves nothing reports its answer, `response:close`, along with its click, `ui:close`, as the web does; 1.2.0 sent the click alone. The US screen's close button still reports `ccpa_response:close` alone. Under the CNIL variant, where the close button saves the choice, nothing changes.
* The system back button is reported like the close button that saves nothing (`response:close` and `ui:close`, or `ccpa_response:close` on the US screen). It already closed the banner, without any report.
* `isGdprApplies()` and the in-app TCF API return `context.gdprApplies` as served by the API; the SDK no longer adds `forceGdpr` on top of it. The server already folds `forceGdpr` into that value, so what an integrator sees does not change.

**Fixes**

* **The SDK no longer fails to start on a configuration that serves publisher restrictions.** The API serves `restrictionType` as a number, which 1.2.0 could not decode: `initialize()` failed and the CMP never loaded.
* Toggling a stack records only the members the screen shows, and a purpose row records only the legal bases the served list asks for. Both could record a consent or a legitimate interest the user was never asked for.
* `Utils.buildAddtlConsent` returns the string the SDK stores in `IABTCF_AddtlConsent`, prefixed `2~`. It returned a `1~` string that no device ever stored.
* In dark mode, a logo or watermark set to "no image" no longer displays the word `NONE`, and an empty dark-mode image falls back to the light-mode one, as on the web.

**Requirements**

* Android API 23 (6.0) or higher, `compileSdk` 34, Kotlin 1.9.22 or higher; verified with Android Gradle Plugin 8.2.2 on JDK 17. See [Setup — Android](/en/mobile-apps-native-sdk/mobile-sdk/setup.md), step 1.

## 1.2.0 (2026-09-23)

First public release of the Android SDK. Earlier version numbers exist in the repository history but were never resolvable by an integrator: no artifact had been published under `com.sirdata.cmp` on Maven Central, so this entry is the starting point of the public history rather than a delta.

**Breaking changes**

* Two public symbols are removed. Neither has ever been reachable from a published artifact — nothing had been published under `com.sirdata.cmp` — so this is announced for the record rather than as a migration:
  * `TcStringEncoder.convertTcStringToGppString(tcString)`. It built a GPP string carrying the TCF section alone, which stopped being the production path when the SDK started emitting the US National section: the GPP string and `IABGPP_GppSID` are now assembled together from one section list. Reading `IABGPP_HDR_GppString` is the supported way to obtain it.
  * `Context.isAskLaterCappingActive(store)`. A dead duplicate of the "ask later" capping rule, and it used a *different* window from the live one — `context.cappingInHours` instead of the fixed one hour. The rule the SDK actually applies is internal to the display cascade; call `awaitConsentDisplayDecision()` to ask whether the banner should be shown.

**Features**

* **The main-thread contract of `initialize`, `shutdown` and `setCcpaOptOut` is enforced rather than only documented.** The three read and re-encode the consent state — jurisdiction, configuration and store — together and without a lock, so calling them from a background thread is a race. Off the main thread the SDK now logs the violation, naming the entry point; declare `debugMode(true)` on the configuration and it throws instead, so the fault surfaces in your own debug build rather than as a rare, silent race in production. `debugMode` has no other effect.
* IAB TCF v2.2 in-app CMP API (registered CMP ID 92), IAB GPP and CCPA/USP support. All signals are written to `SharedPreferences` under the standard IAB keys, so compliant vendor SDKs read consent with no integration code.
* Native Jetpack Compose consent UI: banner, purposes and partners screens, themed from the publisher configuration.
* Display cascade ported from the web CMP — `awaitConsentDisplayDecision()` decides whether and how to present the UI, covering partial consent, capping windows and the cookie wall.
* `MANUAL_DISPLAY` and `APPLY_CHOICES` workflows for re-opening the UI from a settings screen and for asking only what is left to decide.
* Google Additional Consent string and Google Consent Mode state derived from TCF purposes, with consent adapters for automatic forwarding.
* The consent UI does not list a partner for which the user has no decision to make — a partner with neither a consent nor a legitimate-interest toggle.
* **50 languages** — every locale the Sirdata API serves. BCP 47 tags are matched with their full label and case-insensitively, the way `Locale.toLanguageTag()` actually reports them, and `nb-NO` / `nn-NO` / `fil-PH` resolve to Norwegian and Filipino through an alias table.
* **Right-to-left languages render right-to-left.** The consent UI sets its own layout direction from the banner's language instead of inheriting the host app's — LTR as explicitly as RTL, which is what closes the inheritance for the 48 left-to-right languages.
* In the "apply choices" workflow, the refusal button is not rendered when the publisher set the no-consent button to `NONE` — that is, when they explicitly chose not to offer a refusal on the banner.
* `forceGdpr` is the single supported way to override GDPR applicability. The overlapping `setGdprApplies` command was removed before this first release, so it never reached an integrator.
* **GPP US National (section 7)** is emitted alongside the deprecated `uspv1` section, and `IABGPP_GppSID` is built from the same section list as the string rather than derived from it — it used to be read back out of a Fibonacci-encoded header, which cannot work, so it never announced anything but `2`.
* **The publisher configuration is now fetched from the `cacheApiRoot` host** (`choices.consentframework.com` by default) rather than `apiRoot` (`api.consentframework.com`). Both hosts serve the same endpoint, and the two responses were compared key by key: 614 keys against 615, no differing value, the single extra key being the `context.region` that only `choices` can carry — it sits behind the CDN that resolves the visitor's country and US state, which the US National section needs. Integrators who pin hosts in a network allowlist, a proxy rule or a certificate-pinning configuration should confirm `cacheApiRoot` is reachable — the other two hosts are unchanged, and all three remain configurable.
* `getUspString()` returns an empty string when no opt-out decision has been recorded. `"1---"` is not a neutral default: the US Privacy spec makes it an explicit assertion that the consumer falls outside a US Privacy jurisdiction. The sale opt-out itself is recorded under its own key, so a value fabricated by an earlier SDK version can never be mistaken for a decision the user made.

**Fixes**

* `ping` reports the undetermined state until the publisher configuration is loaded, instead of a default determination the CMP had not computed.
* The library declares its `material-icons-core` dependency explicitly. It used to arrive transitively through the Compose BOM's material3; apps resolving a newer material3 — which no longer carries the icons — crashed with `NoClassDefFoundError` on the purposes screen and on the manual-display close button.
* The library declares the `INTERNET` and `ACCESS_NETWORK_STATE` permissions in its own manifest, so integrators get them through manifest merging. An app without `INTERNET` previously crashed on first launch instead of degrading.
* `removeEventListener` accepts a boxed `Long`, which is what a Java caller passes naturally (`Long.valueOf(id)`). It previously fell through to the null branch and the listener was never removed.
* `ping` and `getTCData` read the TCF policy version and GDPR applicability through the same storage reader, so the two commands can no longer report different values in the same session.

**Requirements**

* Android API 23 (6.0) or higher, Kotlin 1.9.22 or higher.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/versions.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
