Compare Android APK and App Bundle releases for permission, exported-component, deep-link, application-policy, SDK, and module-delivery changes.
android-release-contract reads two local Android release artifacts, builds a
neutral contract from their manifests and bundle metadata, and emits a stable
semantic diff. Review-level changes produce exit code 1 without turning a
successful comparison into an execution error.
The tool answers focused release questions: which permissions changed, which components became externally reachable, whether deep links or application policies changed, whether SDK compatibility moved, and whether an App Bundle's module delivery contract changed.
APK Analyzer and bundletool expose artifact information.
android-release-contract turns selected release metadata into a stable
semantic diff and CI decision. It is not a decompiler, malware scanner,
vulnerability scanner, Play Console replacement, or general binary diff.
- APK → APK
- AAB → AAB
- APK → AAB
- AAB → APK
Content structure, ZIP magic, and required manifest paths determine the format;
the filename extension is not trusted by itself. APK set (.apks) archives are
not supported.
Java 17 or newer is required.
Expand-Archive .\android-release-contract-0.1.0.zip
.\android-release-contract-0.1.0\bin\android-release-contract.bat --version
Get-FileHash .\android-release-contract-0.1.0-all.jar -Algorithm SHA256tar -xf android-release-contract-0.1.0.tar
./android-release-contract-0.1.0/bin/android-release-contract --version
java -jar android-release-contract-0.1.0-all.jar --version
sha256sum -c SHA256SUMS.txtJava 17 or newer is required. The build does not require Android Studio or a locally installed Android SDK.
git clone https://github.com/cagrikacmaz/android-release-contract.git
Set-Location android-release-contract
.\gradlew.bat installDist shadowJarGenerated entry points:
build/install/android-release-contract/bin/android-release-contract
build/install/android-release-contract/bin/android-release-contract.bat
build/libs/android-release-contract-all.jar
The standard JAR, fat JAR, install tree, ZIP, and TAR all contain the generated
THIRD-PARTY-NOTICES.txt for the exact resolved runtime dependency set.
android-release-contract compare app-100.apk app-101.apk
android-release-contract compare app-100.aab app-101.aab --format json
android-release-contract compare app-100.apk app-101.aab --format markdown --output report.md
java -jar android-release-contract-all.jar compare app-100.apk app-101.apkOptions:
--format text|json|markdown
--output PATH
--no-color
--max-changes N
--include-info / --no-include-info
--version
--help
Android Release Contract
Before io.github.cagrikacmaz.deeplinkfixture 10 / 1.0 APK
After io.github.cagrikacmaz.deeplinkfixture 11 / 1.1 APK
Scope APK full comparison
REVIEW app-link.auto-verify.disabled
io.github.cagrikacmaz.deeplinkfixture.LinkActivity intent-filter[1]
true → false
Android App Link autoVerify declaration changed
REVIEW deep-link.port.changed
io.github.cagrikacmaz.deeplinkfixture.LinkActivity intent-filter[1]
["443","<absent>"] → ["8443","<absent>"]
Deep-link port matching changed
REVIEW sdk.min.increased
minimum SDK
23 → 26
Minimum SDK increased
INFO version.name.changed
version name
"1.0" → "1.1"
Version name changed
Summary 3 review, 1 info
Result REVIEW REQUIRED
An APK represents one installable, merged manifest contract. Module provenance is normally unavailable.
An AAB is a publishing artifact whose base, feature, and other modules remain separate. AAB comparisons match modules by canonical module name and compare delivery metadata without pretending every module is installed for every user.
APK↔AAB comparisons are structurally asymmetric. Only comparable APK/base-AAB
fields are compared directly; AAB-only modules are reported separately. JSON
sets comparisonScope to cross-format-base and includes the
comparison.cross-format.limited diagnostic. This is useful for release review
but is not equivalent to comparing two AABs.
If package names differ, detailed component/module comparison is suppressed to avoid a misleading flood of renamed subjects.
APK manifests are compiled Android binary XML, not plain XML.
android-release-contract reads them in-process through maintained Android
binary-resource parser artifacts, then converts parser-specific values into a
neutral release-contract model. Normal analysis does not shell out to Android
Studio, apkanalyzer, aapt2, or a locally installed Android SDK.
Android App Bundles use protobuf-encoded module manifests. The AAB reader uses bundletool and AAPT2 protobuf models in-process to parse the base manifest and each dynamic feature manifest separately. Module names, provenance, delivery conditions, instant and fusing settings, and component ownership remain explicit instead of being flattened into one synthetic APK.
Both artifact readers feed the same deterministic semantic differ. It emits stable change IDs, review impact, deterministic ordering, and text, JSON, or Markdown output. APK↔AAB comparisons intentionally cover only comparable APK/base-manifest fields; AAB-only modules remain separate.
- application ID, version, minimum SDK, and target SDK
- requested and custom-defined permissions
- required and optional device features
- debuggable, test-only, backup, cleartext, and related application attributes
- activities, aliases, services, receivers, and providers
- declared and safely derived enabled/exported state
- component permissions, provider authorities, and URI-grant settings
- intent filters, deep-link declarations, and App Link auto-verification
- AAB module inventory, delivery mode, conditions, instant, fusing, and removable state
Resource-backed manifest references such as @xml/network_security_config are
compared as references. Referenced resource-file content is not inspected.
Known numeric minSdk values are classified as increased or decreased. A
missing/unknown value becoming known is sdk.min.introduced; a known value
becoming missing/unknown is sdk.min.removed. Unknown values are never given a
numeric ordering. For compatibility, sdk.target.increased also covers an
unknown targetSdk becoming known, while sdk.target.missing covers the reverse.
Intent-filter boundaries and the ordered structure of their <data>
declarations are preserved. Exact filters are matched first; remaining filters
are paired only through an unambiguous scheme/host or action/category identity.
Ambiguous cases become one filter removal and one filter addition rather than a
guessed field-level diff. Whole-filter additions/removals do not also emit noisy
field additions/removals.
Matched filters compare schemes, hosts, ports, paths, MIME types, actions,
categories, priority, and autoVerify per filter. Multiple <data> elements
remain separate; the tool does not synthesize Cartesian URL combinations.
autoVerify=true is only a manifest declaration and is not proof that Android
successfully verified a domain.
The built-in v0.1 policy is deliberately narrow: a result requires review when
at least one change has impact review. Review means that a person should assess
the release change; it does not assert a vulnerability.
Examples include a permission addition, an effective exported state becoming
true, target SDK movement, application debuggable becoming true, a new external
deep-link host, or module delivery changing. Informational changes remain in
the report by default and can be hidden with --no-include-info.
The stable change-ID registry is documented in docs/change-ids.md.
| Code | Meaning |
|---|---|
| 0 | Comparison completed with no review-level change |
| 1 | Comparison completed and review is required |
| 2 | Usage, input, parsing, output, or internal analysis error |
Exit code 1 is a valid comparison result. CI scripts must handle it separately from execution failures.
JSON output uses schema version 1. The schema is stored at
schema/android-release-contract.schema.json.
Default JSON contains no timestamps, random IDs, temporary paths, usernames, or
absolute input paths.
- name: Build release contract tool
run: ./gradlew shadowJar
- name: Compare releases
shell: bash
run: |
set +e
java -jar build/libs/android-release-contract-all.jar \
compare previous.aab candidate.aab --format json --output contract.json
code=$?
set -e
test "$code" -eq 0 -o "$code" -eq 1
test "$code" -eq 0The first assertion distinguishes a completed review result from an execution error; the second enforces this example project's no-review policy.
Analysis is local and performs no network calls or artifact upload. Artifact code, DEX, and native libraries are never loaded or executed. Archives are read with random access, are never fully extracted, and are subject to bounded entry, module, manifest, recursion, and diagnostic limits. See SECURITY.md.
- Signing certificates and signatures are not compared or verified.
- DEX/bytecode, native libraries, resource sizes, and policy resource contents are not analyzed.
- Complex deep-link patterns are compared structurally; arbitrary regex-language containment is not inferred.
- Effective values remain unknown when Android behavior cannot be derived safely.
- Cross-format results cannot represent every split APK configuration Google Play might serve.
- Unknown future module forms are preserved conservatively and diagnosed.
APK Analyzer is excellent for inspecting APK composition, manifests, resources, DEX, and file-size changes. AAPT2 compiles and links Android resources. Bundletool is Google's reference utility for constructing and transforming App Bundles and APK sets. Diffuse provides broad APK/AAB/JAR structural and size diffs.
This tool uses maintained Android parser artifacts in-process, then adds a narrow release-contract model, stable changes, review policy, deterministic text/JSON/Markdown output, and CI-oriented exit behavior. It does not replace those adjacent tools.
.\gradlew.bat spotlessApply
.\gradlew.bat spotlessCheck
.\gradlew.bat detekt
.\gradlew.bat test
.\gradlew.bat koverVerify
.\gradlew.bat verifyThirdPartyNotices
.\gradlew.bat build installDist shadowJarFixture source and regeneration helpers are under src/test/fixtures. Normal
tests consume committed minimal APK/AAB binaries and do not require an Android
SDK.
The project is licensed under the MIT License. Bundled third-party runtime
components retain their own licenses; see NOTICE and the generated
THIRD-PARTY-NOTICES.txt included in every binary distribution.