getreadystack's picture
Upload index.html with huggingface_hub
1eb6437 verified
Raw History Blame Contribute Delete
45.9 kB
<!doctype html><html lang="en"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB</title>
<meta name="description" content="Finds the lines that make Google Play reject your release - target API below 36, Billing Library below 8, 4 KB-only native libs - and prints the date each gate closed.">
<style>
:root{color-scheme:light dark}
body{margin:0;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;
background:#fbfbfa;color:#16161d}
@media(prefers-color-scheme:dark){body{background:#14141a;color:#e8e8ea}}
.wrap{max-width:880px;margin:0 auto;padding:28px 20px 64px}
h1{font-size:1.7rem;line-height:1.25;margin:.2em 0 .3em}
p.sub{margin:0 0 1.4em;opacity:.8}
textarea{width:100%;min-height:230px;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace;
padding:12px;border:1px solid #c9c9d1;border-radius:10px;background:transparent;color:inherit;
box-sizing:border-box}
button{font:600 15px/1 inherit;padding:12px 22px;border:0;border-radius:10px;
background:#1d4ed8;color:#fff;cursor:pointer;margin:12px 0}
button:hover{background:#1a43bd}
table{border-collapse:collapse;width:100%;margin-top:14px;font-size:14px}
th,td{border-bottom:1px solid #d8d8e0;padding:8px 6px;text-align:left;vertical-align:top}
th{font-weight:600;opacity:.75}
td.n{width:64px;font:13px ui-monospace,monospace;opacity:.7}
.ok{padding:14px;border-radius:10px;background:#e8f5ec;color:#14532d;margin-top:14px}
@media(prefers-color-scheme:dark){.ok{background:#15321f;color:#bbf7d0}}
.next{margin-top:30px;padding:18px;border:1px solid #d8d8e0;border-radius:12px}
.next h2{font-size:1.05rem;margin:0 0 .5em}
.next ul{margin:.3em 0 1em 1.1em;padding:0}
</style>
<div class="wrap">
<h1>Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB</h1>
<p class="sub">Finds the lines that make Google Play reject your release - target API below 36, Billing Library below 8, 4 KB-only native libs - and prints the date each gate closed.</p>
<textarea id="src" placeholder="Paste your build.gradle, build.gradle.kts, libs.versions.toml, AndroidManifest.xml or app.json here.">android {
namespace = "com.acme.tracker"
compileSdk = 35
defaultConfig {
applicationId = "com.acme.tracker"
minSdk = 24
targetSdk = 35
versionCode = 812
}
ndkVersion = "27.0.12077973"
packaging { jniLibs { useLegacyPackaging = true } }
}
dependencies {
implementation("com.android.billingclient:billing-ktx:7.1.1")
implementation("com.google.android.play:core:1.10.3")
classpath("com.android.tools.build:gradle:8.4.2")
}
&lt;!-- AndroidManifest.xml --&gt;
&lt;uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /&gt;
&lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
&lt;uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /&gt;
&lt;application android:extractNativeLibs="true" android:usesCleartextTraffic="true"&gt;
&lt;activity android:name=".MainActivity" android:screenOrientation="portrait" android:resizeableActivity="false" /&gt;
&lt;/application&gt;</textarea>
<button id="go">Find my release blockers</button>
<div id="out"></div>
<div class="next"><h2>In the full version</h2><ul><li>Every module, flavour and manifest in the repo at once</li><li>Machine-readable output your CI can fail on</li><li>A dated release-readiness report you can hand to a client or a lead</li><li>Re-checks on every save while you migrate</li><li><a href="https://getreadystack.com">Get the full version - $29</a></li><li style="opacity:.85">$29 once &middot; one licence key per person or team seat &middot; Upwork publishes a $25/hr median for Android developers.</li></ul></div>
</div>
<script>
var RULES=[{"pattern": "targetSdk(?:Version)?[\"\\']?\\s*[:=]?\\s*[\"\\']?35\\b", "flags": "i", "sev": "error", "message": "[error] Play stopped accepting this on 2026-08-31: a new release must target Android 16 (API 36). API 35 keeps the listing alive but no update can be published. The extension window closes 2026-11-01. Fix: targetSdk = 36", "fix": "targetSdk = 36"}, {"pattern": "targetSdk(?:Version)?[\"\\']?\\s*[:=]?\\s*[\"\\']?3[0-4]\\b", "flags": "i", "sev": "error", "message": "[error] Two Play gates fail on this line: releases have needed API 36 since 2026-08-31, and an existing app below API 35 is hidden from new users on devices running a newer Android than it targets. Fix: targetSdk = 36", "fix": "targetSdk = 36"}, {"pattern": "targetSdk(?:Version)?[\"\\']?\\s*[:=]?\\s*[\"\\']?2[1-9]\\b", "flags": "i", "sev": "error", "message": "[error] Target API below 30. Play has refused updates at this level for years and the listing is invisible to new users on modern devices. Go straight to API 36, not one step at a time. Fix: targetSdk = 36", "fix": "targetSdk = 36"}, {"pattern": "compileSdk(?:Version)?[\"\\']?\\s*[:=]?\\s*[\"\\']?3[0-5]\\b", "flags": "i", "sev": "warn", "message": "[warn] You cannot target API 36 while compiling against a lower SDK. Raise compileSdk first, then raise targetSdk, then fix what stops building. Fix: compileSdk = 36", "fix": "compileSdk = 36"}, {"pattern": "com\\.android\\.billingclient:billing(?:-ktx)?:[1-7]\\.", "flags": "i", "sev": "error", "message": "[error] Play Billing Library below 8.0.0. Since 2026-08-31 Play rejects any new release using it. Builds already published keep transacting, so you only find out when you try to ship a fix. Extension closes 2026-11-01. Fix: com.android.billingclient:billing:8.0.0", "fix": "com.android.billingclient:billing:8.0.0"}, {"pattern": "billing(?:client)?\\s*=\\s*[\"\\'][1-7]\\.", "flags": "i", "sev": "error", "message": "[error] The version catalog pins Play Billing Library below 8.0.0, which Play stopped accepting in new releases on 2026-08-31. Fix: billing = \"8.0.0\"", "fix": "billing = \"8.0.0\""}, {"pattern": "android:extractNativeLibs\\s*=\\s*[\"\\']true[\"\\']", "flags": "i", "sev": "error", "message": "[error] Compressed native libraries cannot be 16 KB aligned. Play has required 16 KB page-size support from releases targeting Android 15 and above since 2025-11-01; the Play Console extension ran out on 2026-05-31. Fix: android:extractNativeLibs=\"false\"", "fix": "android:extractNativeLibs=\"false\""}, {"pattern": "useLegacyPackaging\\s*[:=]\\s*true", "flags": "i", "sev": "error", "message": "[error] useLegacyPackaging = true compresses your .so files and breaks the 16 KB alignment Play has required since 2025-11-01. Fix: useLegacyPackaging = false", "fix": "useLegacyPackaging = false"}, {"pattern": "max-page-size=0x1000", "flags": "i", "sev": "error", "message": "[error] The linker is pinned to a 4 KB page. On a device with 16 KB pages this library will not load, and Play refuses the upload. Fix: -Wl,-z,max-page-size=16384", "fix": "-Wl,-z,max-page-size=16384"}, {"pattern": "ndkVersion\\s*[\"\\']?\\s*[:=]?\\s*[\"\\']?(?:1[0-9]|2[0-7])\\.", "flags": "i", "sev": "warn", "message": "[warn] NDK below r28 does not emit 16 KB aligned shared libraries by default. Google names NDK r28 or newer as the fix for the 16 KB requirement. Fix: ndkVersion = \"28.0.13004108\"", "fix": "ndkVersion = \"28.0.13004108\""}, {"pattern": "com\\.android\\.tools\\.build:gradle:(?:[0-7]\\.|8\\.[0-4]\\.)", "flags": "i", "sev": "warn", "message": "[warn] Android Gradle Plugin below 8.5.1 does not align native libraries to 16 KB by default. Google names AGP 8.5.1 or newer as the tooling floor for that requirement. Fix: com.android.tools.build:gradle:8.5.1 or newer", "fix": "com.android.tools.build:gradle:8.5.1 or newer"}, {"pattern": "agp\\s*=\\s*[\"\\'](?:[0-7]\\.|8\\.[0-4]\\.)", "flags": "i", "sev": "warn", "message": "[warn] The version catalog pins AGP below 8.5.1, under the tooling floor Google names for 16 KB alignment. Fix: agp = \"8.5.1\"", "fix": "agp = \"8.5.1\""}, {"pattern": "windowOptOutEdgeToEdgeEnforcement", "flags": "i", "sev": "error", "message": "[error] This opt-out is ignored once the app targets API 36. Your content will draw behind the status and navigation bars, so handle window insets instead of opting out. Fix: remove it and pad with WindowInsets", "fix": "remove it and pad with WindowInsets"}, {"pattern": "android:screenOrientation\\s*=\\s*[\"\\'](?:portrait|sensorPortrait|reversePortrait|userPortrait|landscape|sensorLandscape|reverseLandscape|userLandscape)[\"\\']", "flags": "i", "sev": "warn", "message": "[warn] Ignored on displays 600dp wide and above once the app targets API 36. The activity fills a tablet or unfolded screen in whatever orientation the user holds it, with no pillarboxing. Fix: make the layout adaptive rather than locking the orientation", "fix": "make the layout adaptive rather than locking the orientation"}, {"pattern": "android:resizeableActivity\\s*=\\s*[\"\\']false[\"\\']", "flags": "i", "sev": "warn", "message": "[warn] Resizability restrictions no longer apply on displays 600dp and wider for apps targeting API 36. The activity is resized anyway, so test it before Play forces the target. Fix: android:resizeableActivity=\"true\"", "fix": "android:resizeableActivity=\"true\""}, {"pattern": "android:maxAspectRatio", "flags": "i", "sev": "warn", "message": "[warn] Aspect-ratio limits are ignored on large screens for apps targeting API 36; the app fills the display window whatever its ratio. Fix: remove it and test a 16:10 tablet layout", "fix": "remove it and test a 16:10 tablet layout"}, {"pattern": "android:enableOnBackInvokedCallback\\s*=\\s*[\"\\']false[\"\\']", "flags": "i", "sev": "info", "message": "[info] Predictive back animations run across the system on Android 16, including back-to-home, cross-task and cross-activity. Opting out leaves your app the only one without them. Fix: android:enableOnBackInvokedCallback=\"true\"", "fix": "android:enableOnBackInvokedCallback=\"true\""}, {"pattern": "android:debuggable\\s*=\\s*[\"\\']true[\"\\']", "flags": "i", "sev": "error", "message": "[error] Play rejects any bundle whose manifest is debuggable. Delete the attribute; the debug build type sets it for you. Fix: delete android:debuggable from the manifest", "fix": "delete android:debuggable from the manifest"}, {"pattern": "com\\.google\\.android\\.play:core:", "flags": "i", "sev": "error", "message": "[error] The monolithic Play Core library is deprecated and crashes on Android 14 and newer. Split it into the libraries you actually use. Fix: com.google.android.play:app-update / :asset-delivery / :review", "fix": "com.google.android.play:app-update / :asset-delivery / :review"}, {"pattern": "jcenter\\s*\\(", "flags": "", "sev": "warn", "message": "[warn] JCenter is read-only and being shut down. A build that resolves through it can stop resolving without warning, and a build you cannot run is a release you cannot ship. Fix: mavenCentral()", "fix": "mavenCentral()"}, {"pattern": "QUERY_ALL_PACKAGES", "flags": "", "sev": "error", "message": "[error] Broad package visibility needs an approved Play Console declaration and is refused for most app categories. Without it the release is rejected. Fix: replace with a <queries> element listing the packages you need", "fix": "replace with a <queries> element listing the packages you need"}, {"pattern": "MANAGE_EXTERNAL_STORAGE", "flags": "", "sev": "error", "message": "[error] All-files access needs an approved Play declaration and is granted only to a short list of app types. MediaStore or the Storage Access Framework passes review. Fix: use MediaStore or the Storage Access Framework", "fix": "use MediaStore or the Storage Access Framework"}, {"pattern": "REQUEST_INSTALL_PACKAGES", "flags": "", "sev": "error", "message": "[error] Installing other packages needs a Play Console declaration form and is limited to eligible app types. Undeclared, the release is rejected. Fix: remove it, or file the declaration before you upload", "fix": "remove it, or file the declaration before you upload"}, {"pattern": "android\\.permission\\.(?:READ_SMS|RECEIVE_SMS|SEND_SMS|READ_CALL_LOG|WRITE_CALL_LOG|PROCESS_OUTGOING_CALLS)", "flags": "", "sev": "error", "message": "[error] SMS and Call Log are restricted permissions. Play accepts them only from an app whose approved default-handler role needs them, with a declaration on file. Fix: use the SMS Retriever API or an intent instead", "fix": "use the SMS Retriever API or an intent instead"}, {"pattern": "WRITE_EXTERNAL_STORAGE[\"\\'](?:(?!maxSdkVersion)[^>])*/?>", "flags": "", "sev": "warn", "message": "[warn] WRITE_EXTERNAL_STORAGE with no android:maxSdkVersion. Under scoped storage it does nothing on API 29 and above, and it still reads as broad storage access on your listing. Fix: android:maxSdkVersion=\"28\"", "fix": "android:maxSdkVersion=\"28\""}, {"pattern": "SCHEDULE_EXACT_ALARM", "flags": "", "sev": "warn", "message": "[warn] Exact alarms need a Play declaration and are granted mainly to alarm, clock and calendar apps. Anything else should use setAndAllowWhileIdle or WorkManager. Fix: use WorkManager, or USE_EXACT_ALARM if the app qualifies", "fix": "use WorkManager, or USE_EXACT_ALARM if the app qualifies"}, {"pattern": "android\\.permission\\.FOREGROUND_SERVICE[\"\\']", "flags": "", "sev": "warn", "message": "[warn] Since API 34 every foreground service also needs a typed FOREGROUND_SERVICE_* permission and android:foregroundServiceType, and Play wants a declaration explaining the use. Fix: add FOREGROUND_SERVICE_DATA_SYNC (or the right type) and foregroundServiceType", "fix": "add FOREGROUND_SERVICE_DATA_SYNC (or the right type) and foregroundServiceType"}, {"pattern": "com\\.google\\.android\\.gms\\.permission\\.AD_ID", "flags": "", "sev": "info", "message": "[info] Declaring the advertising ID permission commits you to a matching Data safety declaration, and an app aimed at children must not declare it at all. Fix: remove it if you do not read the advertising ID", "fix": "remove it if you do not read the advertising ID"}, {"pattern": "android:usesCleartextTraffic\\s*=\\s*[\"\\']true[\"\\']", "flags": "i", "sev": "warn", "message": "[warn] Cleartext HTTP is flagged by the Play pre-launch report and has to be disclosed on the Data safety form. Move the endpoints to HTTPS or scope it in a network security config. Fix: android:usesCleartextTraffic=\"false\" plus a network security config", "fix": "android:usesCleartextTraffic=\"false\" plus a network security config"}, {"pattern": "<uses-sdk", "flags": "i", "sev": "info", "message": "[info] <uses-sdk> in the manifest is overridden by the Gradle values, so the number here can disagree with what Play actually receives. Keep the SDK levels in build.gradle only. Fix: delete <uses-sdk> and set the levels in build.gradle", "fix": "delete <uses-sdk> and set the levels in build.gradle"}, {"pattern": "<(?:activity|activity-alias|service|receiver)\\b(?:(?!android:exported)[^>])*>", "flags": "i", "sev": "info", "message": "[info] No android:exported on this line. Every component with an intent filter has had to declare it since API 31, and a missing value fails the build before Play ever sees it. Fix: android:exported=\"false\"", "fix": "android:exported=\"false\""}];
function scan(t){var L=String(t).split(/\r?\n/),h=[],i,r,re;
for(i=0;i<L.length;i++){for(var k=0;k<RULES.length;k++){r=RULES[k];
try{re=new RegExp(r.pattern,r.flags||'')}catch(e){continue}
if(re.test(L[i]))h.push({line:i+1,msg:r.message})}}
return h}
document.getElementById('go').onclick=function(){
var t=document.getElementById('src').value,o=document.getElementById('out');
if(!t.trim()){o.innerHTML='';return}
var h=scan(t);
if(!h.length){o.innerHTML='<div class="ok">No Play release blocker in this file.</div>';return}
var s='<table><tr><th>Line</th><th>Finding</th></tr>';
for(var i=0;i<h.length;i++){s+='<tr><td class="n">'+h[i].line+'</td><td>'+
String(h[i].msg).replace(/&/g,'&amp;').replace(/</g,'&lt;')+'</td></tr>'}
o.innerHTML=s+'</table>'};
document.getElementById('go').click();
</script>
</html>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.play-release-blocker-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'play-release-blocker-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/play-release-blocker-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'play-release-blocker-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/play-release-blocker-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'play-release-blocker-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/play-release-blocker-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'play-release-blocker-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/play-release-blocker-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'play-release-blocker-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.play-release-blocker-lint
npx @readystack/play-release-blocker-lint &lt;file&gt;
npx @readystack/play-release-blocker-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 31 rules Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"play-release-blocker-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Finds the lines that make Google Play reject your release - target API below 36, Billing Library below 8, 4 KB-only native libs - and prints the date each gate closed.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Upwork publishes a $25/hr median for Android developers (typical range $15-$35); one rejected release burns more than that in rebuild-and-resubmit time.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_gyOsuR4TciVB1eezarZh5whJXfxrUOu4SDZCZ2hu1dw" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-09">Updated 2026-09-09</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-09","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku29682_result_card.jpg" alt="Six lines Google Play now rejects - and none of them warn you in the editor" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six lines in an ordinary Android build file are now enough for Google Play to refuse a release, and the indie Flutter and Kotlin developers who own the upload button usually find out only after the bundle has been built and rejected.</p>
<p>Three Play gates closed on 31 August 2026. A new release has to target Android 16 (API level 36). A new release has to use Play Billing Library 8 or later. And since 1 November 2025, releases targeting Android 15 and above have had to support 16 KB memory pages, with the Play Console extension for that one running out on 31 May 2026. The extension window for the first two shuts on 1 November 2026.</p>
<p>None of that is visible where you work. Android Lint checks API compatibility, not Google&#x27;s publishing calendar, so a <code>billing-ktx:7.1.1</code> dependency draws no warning at all. Gradle builds it happily. The Play Console does know, but it only tells you once you have uploaded the bundle, which means the fastest feedback loop available to a solo developer is: build, sign, upload, wait, get rejected, read the message, guess which of the three gates it was.</p>
<p>So I wrote the check into the editor.</p>
<p><strong>Play Release Blocker Lint</strong> reads <code>build.gradle</code>, <code>build.gradle.kts</code>, <code>libs.versions.toml</code>, <code>AndroidManifest.xml</code> and Expo&#x27;s <code>app.json</code>, and prints the lines that stop a release with the date each gate closed and the line to write instead:</p>
<pre><code> 8: [error] Play stopped accepting this on 2026-08-31: a new release must target
Android 16 (API 36). API 35 keeps the listing alive but no update can be
published. The extension window closes 2026-11-01. Fix: targetSdk = 36
16: [error] Play Billing Library below 8.0.0. Since 2026-08-31 Play rejects any
new release using it. Fix: com.android.billingclient:billing:8.0.0
25: [error] Compressed native libraries cannot be 16 KB aligned. Play has required
16 KB page-size support since 2025-11-01.
Fix: android:extractNativeLibs=&quot;false&quot;</code></pre>
<p>There are 31 rules. The publishing gates with a date are only the first group. The second is what actually breaks on the day you raise the target: apps targeting API 36 can no longer opt out of edge-to-edge, and orientation, resizability and aspect-ratio restrictions are ignored on displays 600dp wide and above, so a <code>screenOrientation=&quot;portrait&quot;</code> activity fills a tablet in whatever orientation the user is holding it. That is the part that turns a one-line version bump into a week of layout work, and it is worth knowing before you promise a date.</p>
<p>The third group is permissions that need an approved Play declaration before review will pass — <code>QUERY_ALL_PACKAGES</code>, <code>MANAGE_EXTERNAL_STORAGE</code>, <code>REQUEST_INSTALL_PACKAGES</code>, SMS and Call Log, exact alarms, untyped foreground services. The fourth is bundles Play refuses outright: a debuggable manifest, the deprecated monolithic Play Core library that crashes on Android 14 and newer, JCenter still sitting in your repositories block.</p>
<p>Checking the file you have open is free and it is the whole job — all 31 rules, every line, every date, every fix, no watermark and no run counter. The same 31 rules also run in the browser, so you can paste a file in and see the answer without installing anything.</p>
<p>What the licence adds is a different kind of work rather than a bigger helping of the same one: scanning every module, flavour and manifest in the repo in one command, because the blocker is usually in the library module you did not have open; a JSON output your CI can fail on, so the build breaks on your runner days before it breaks on the upload; a dated release-readiness report you can hand to a client; and a re-check on every save while you migrate.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku29682_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku29682_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does this actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads your Gradle files, version catalogs, Android manifests and Expo app.json, and flags the exact lines that make Google Play refuse a release: a targetSdk below API 36, a Play Billing Library below 8, native libraries that cannot align to 16 KB pages, and permissions that need an approved Play declaration. Each finding carries the date that gate closed and the line to write instead.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Indie Android developers and small studios who ship Kotlin, Flutter, React Native or Expo apps to Google Play and do not have a release engineer. If you own the upload button and find out about a policy deadline when the Play Console rejects your bundle, this is written for you.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a free tool not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Android Lint and the Gradle warnings check API compatibility, not Google Play&#x27;s publishing calendar, so they stay silent on a Billing Library 7 dependency. Play Console does know, but it only tells you after you have built and uploaded the bundle. A chatbot confuses API 35, the availability floor, with API 36, the publishing gate, and quotes last year&#x27;s date.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what is paid?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Checking the file you have open is free and complete: all 31 rules, every line, every date, every suggested fix, with no watermark, no time limit and no locked results. The licence adds a different kind of work: scanning every module in the repo at once, failing your CI, re-checking on save, and exporting a dated report you can hand to a client.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Upwork publishes a $25 per hour median for Android developers, with a typical range of $15 to $35, and benchmarks for experienced freelance Android work run far above that. One rejected upload costs a rebuild, a re-upload and another review wait, which is more than an hour before anyone has fixed anything.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Android Release Lint - target API 36, Play Billing and polic checks the file you open against 31 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does this actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your Gradle files, version catalogs, Android manifests and Expo app.json, and flags the exact lines that make Google Play refuse a release: a targetSdk below API 36, a Play Billing Library below 8, native libraries that cannot align to 16 KB pages, and permissions that need an approved Play declaration. Each finding carries the date that gate closed and the line to write instead."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Indie Android developers and small studios who ship Kotlin, Flutter, React Native or Expo apps to Google Play and do not have a release engineer. If you own the upload button and find out about a policy deadline when the Play Console rejects your bundle, this is written for you."}}, {"@type": "Question", "name": "Why is a free tool not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Android Lint and the Gradle warnings check API compatibility, not Google Play's publishing calendar, so they stay silent on a Billing Library 7 dependency. Play Console does know, but it only tells you after you have built and uploaded the bundle. A chatbot confuses API 35, the availability floor, with API 36, the publishing gate, and quotes last year's date."}}, {"@type": "Question", "name": "What is free and what is paid?", "acceptedAnswer": {"@type": "Answer", "text": "Checking the file you have open is free and complete: all 31 rules, every line, every date, every suggested fix, with no watermark, no time limit and no locked results. The licence adds a different kind of work: scanning every module in the repo at once, failing your CI, re-checking on save, and exporting a dated report you can hand to a client."}}, {"@type": "Question", "name": "What would this cost otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "Upwork publishes a $25 per hour median for Android developers, with a typical range of $15 to $35, and benchmarks for experienced freelance Android work run far above that. One rejected upload costs a rebuild, a re-upload and another review wait, which is more than an hour before anyone has fixed anything."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Android Release Lint - target API 36, Play Billing and polic checks the file you open against 31 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:29682,slug:"play-release-blocker-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/cms-0057-api-gate"><span class="yj-rel-t">CMS-0057 API Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/firmware-release-gate"><span class="yj-rel-t">Firmware Release Gate for sdkconfig and prj.conf</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/renpy-release-gate"><span class="yj-rel-t">Ren&#x27;Py Release Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pyproject-release-gate"><span class="yj-rel-t">pyproject.toml Release Gate (PEP 639)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/content-calendar-template"><span class="yj-rel-t">Content Calendar Template with Capacity &amp; Cost Calculator for Small Content Te</span><span class="yj-rel-p">$90</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mobile-app-ui-kit-account-deletion"><span class="yj-rel-t">Mobile App UI Kit — Account Deletion Flow for App Store 5.1.1(v) &amp; Google Play</span><span class="yj-rel-p">$168</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section>