Skip to content

Commit 4117811

Browse files
Merge pull request #616 from appdevforall/feat/K2GO-442-play-compliance
K2GO-442 feat: Google Play build compliance (OTA self-updater off, All-files-access optional)
2 parents 002818c + 9d17b24 commit 4117811

5 files changed

Lines changed: 102 additions & 2 deletions

File tree

‎controller/app/build.gradle‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ android {
134134
isDefault = true
135135
buildConfigField "boolean", "ANALYTICS_ENABLED", "${hasGoogleServices}"
136136
}
137+
// Google Play build (K2GO-398). Same as standard (Firebase OK, crash reporting default on) but
138+
// with the in-app OTA self-updater OFF: Google Play forbids an app updating itself outside Play,
139+
// and Play delivers updates. REQUEST_INSTALL_PACKAGES (used only by the OTA updater) is removed in
140+
// src/play/AndroidManifest.xml. applicationId is unchanged: this is the same app via the Play channel.
141+
play {
142+
dimension "distribution"
143+
buildConfigField "boolean", "ANALYTICS_ENABLED", "${hasGoogleServices}"
144+
buildConfigField "boolean", "OTA_ENABLED", "false"
145+
}
137146
// F-Droid / fully-free build. No Firebase dependency is declared for it (see dependencies), so
138147
// the AAR cannot link. Analytics is off; crash reporting defaults OFF (opt-in), which fulfils the
139148
// F-Droid default that K2GO-401 deferred to this recipe.
@@ -146,6 +155,14 @@ android {
146155
}
147156
}
148157

158+
// K2GO-398: "standard" and "play" both ship Firebase Analytics, so they share the one Firebase
159+
// bridge (the only class that references the non-free AAR) from src/withFirebase instead of
160+
// duplicating it. "fdroid" keeps its own no-op twin in src/fdroid and links no Firebase.
161+
sourceSets {
162+
standard.java.srcDirs += 'src/withFirebase/java'
163+
play.java.srcDirs += 'src/withFirebase/java'
164+
}
165+
149166
packagingOptions {
150167
jniLibs {
151168
useLegacyPackaging true
@@ -233,7 +250,8 @@ android {
233250
def shortAbi = abi == null ? "universal" : abi.replace("arm64-", "").replace("armeabi-", "")
234251
// K2GO-402: two flavors can share versionName+abi+buildType. Disambiguate the fdroid APK
235252
// with a "-fdroid" marker; keep the standard build's filename unchanged (CI expects it).
236-
def flavorMark = variant.flavorName == "fdroid" ? "-fdroid" : ""
253+
def flavorMark = variant.flavorName == "fdroid" ? "-fdroid"
254+
: variant.flavorName == "play" ? "-play" : ""
237255
output.outputFileName = "K2Go-${variant.versionName}${flavorMark}-${shortAbi}-${variant.buildType.name}.apk"
238256
}
239257
}
@@ -303,6 +321,10 @@ dependencies {
303321
// standardImplementation ONLY: the fdroid flavor must not link this non-free AAR (F-Droid).
304322
standardImplementation platform('com.google.firebase:firebase-bom:33.7.0')
305323
standardImplementation 'com.google.firebase:firebase-analytics'
324+
// K2GO-398: the Play flavor carries the same Firebase as standard (Play allows it; the Data Safety
325+
// form must still declare it). firebase-bom aligns versions; firebase-analytics exposes the Java API.
326+
playImplementation platform('com.google.firebase:firebase-bom:33.7.0')
327+
playImplementation 'com.google.firebase:firebase-analytics'
306328

307329
// Local Module: Our C++ engine (Termux)
308330
implementation project(':termux-core')

‎controller/app/src/main/java/org/appdevforall/k2go/permissions/StoragePermission.java‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@ public static boolean isGranted(Context ctx) {
4444
== PackageManager.PERMISSION_GRANTED;
4545
}
4646

47+
// K2GO-442: isRequired() is a process constant (the manifest declaration and this device's SDK level
48+
// do not change at runtime), so memoize it: the wizard calls it on several onboarding paths and each
49+
// uncached call is a PackageManager IPC. This caches an immutable fact, not coordination state.
50+
private static volatile Boolean requiredMemo;
51+
52+
/**
53+
* K2GO-442: true when this build DECLARES the broad-storage permission for the current OS version,
54+
* so the grant is actually reachable. The Play flavor removes MANAGE_EXTERNAL_STORAGE, so on R+ this
55+
* returns false there and callers must treat storage as optional: never gate onboarding on a
56+
* permission the user can never grant. standard/fdroid still declare it, so it stays required for them.
57+
* Self-correcting (reads the manifest), so no flavor flag is needed. Memoized (see requiredMemo).
58+
*/
59+
public static boolean isRequired(Context ctx) {
60+
Boolean cached = requiredMemo;
61+
if (cached != null) return cached;
62+
String perm = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
63+
? Manifest.permission.MANAGE_EXTERNAL_STORAGE
64+
: Manifest.permission.WRITE_EXTERNAL_STORAGE;
65+
try {
66+
String[] declared = ctx.getPackageManager()
67+
.getPackageInfo(ctx.getPackageName(), PackageManager.GET_PERMISSIONS)
68+
.requestedPermissions;
69+
boolean required = false;
70+
if (declared != null) {
71+
for (String p : declared) {
72+
if (perm.equals(p)) { required = true; break; }
73+
}
74+
}
75+
requiredMemo = required; // cache only a definitive answer
76+
return required;
77+
} catch (PackageManager.NameNotFoundException e) {
78+
return true; // our own package always resolves; fail safe, do not cache a transient miss
79+
}
80+
}
81+
4782
/**
4883
* Trigger the correct grant flow for this OS version. No-op if the permission is already held.
4984
* Use this from a surface that already offers its own "open app settings" escape hatch (e.g. the

‎controller/app/src/main/java/org/appdevforall/k2go/redesign/WizardActivity.java‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ protected void onCreate(Bundle b) {
127127
findViewById(R.id.perm_storage).setOnClickListener(v -> requestStorage());
128128
findViewById(R.id.perm_battery).setOnClickListener(v -> requestBattery());
129129

130+
// K2GO-442: hide the storage row when the build does not declare the broad-storage permission
131+
// (the Play flavor), so the user is never shown a toggle they can never satisfy.
132+
if (!org.appdevforall.k2go.permissions.StoragePermission.isRequired(this)) {
133+
findViewById(R.id.perm_storage).setVisibility(android.view.View.GONE);
134+
}
135+
130136
// set-up-library choices
131137
findViewById(R.id.setup_download).setOnClickListener(v -> {
132138
// ADFA-4982: do NOT mark setup complete here — only a real install does (startWizardInstall).
@@ -297,10 +303,16 @@ private void setStatus(TextView t, boolean granted) {
297303
t.setTextColor(ContextCompat.getColor(this, granted ? R.color.k2go_leaf : R.color.k2go_teal));
298304
}
299305
private boolean allPermsGranted() {
300-
boolean ok = hasStorage() && hasBattery();
306+
boolean ok = storageSatisfied() && hasBattery();
301307
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) ok = ok && hasNotif();
302308
return ok;
303309
}
310+
// K2GO-442: the Play build removes All-files-access, so the grant is unreachable there. Treat storage
311+
// as satisfied when the build does not declare the permission, so onboarding is never a dead-end (the
312+
// row is hidden too). standard/fdroid still declare it, so it stays a hard requirement for them.
313+
private boolean storageSatisfied() {
314+
return !org.appdevforall.k2go.permissions.StoragePermission.isRequired(this) || hasStorage();
315+
}
304316
private boolean hasNotif() {
305317
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
306318
return NotificationManagerCompat.from(this).areNotificationsEnabled();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<!-- K2GO-398: the Google Play build drops the OTA self-updater permission. Google Play forbids an
6+
app updating itself outside Play and delivers updates itself, so the in-app updater is compiled
7+
out here (BuildConfig.OTA_ENABLED=false) and REQUEST_INSTALL_PACKAGES, which only the updater
8+
uses, is removed from the merged manifest so Play review never sees it. -->
9+
<uses-permission
10+
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
11+
tools:node="remove" />
12+
13+
<!-- K2GO-442: the Play build drops All-files-access (MANAGE_EXTERNAL_STORAGE), which Google Play
14+
scrutinizes heavily. The running box works without it (the rootfs is internal); onboarding no
15+
longer hard-requires it (StoragePermission.isRequired is false when it is not declared, so the
16+
wizard hides the row and does not gate on it). Storage-dependent extras (the /sdcard bind,
17+
backup/restore to shared storage) degrade gracefully when it is absent. -->
18+
<uses-permission
19+
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
20+
tools:node="remove" />
21+
22+
<application>
23+
<!-- K2GO-398 / K2GO-402: same Firebase privacy hardening as the standard flavor (this flavor also
24+
ships Firebase). Disable Advertising ID / SSAID collection and ad personalization (K2Go may
25+
serve minors; operational analytics only). These keys stay in the Google-ful flavors, not in
26+
main, so the fdroid build carries no stray Google reference. -->
27+
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
28+
<meta-data android:name="google_analytics_ssaid_collection_enabled" android:value="false" />
29+
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="false" />
30+
</application>
31+
</manifest>

controller/app/src/standard/java/org/appdevforall/k2go/analytics/FirebaseAnalyticsBridge.java renamed to controller/app/src/withFirebase/java/org/appdevforall/k2go/analytics/FirebaseAnalyticsBridge.java

File renamed without changes.

0 commit comments

Comments
 (0)