Skip to content

Commit 7481c37

Browse files
Merge branch 'main' of github.com:appdevforall/KnowledgeToGo into feat/K2GO-99-code-addons-offline-gallery
2 parents 8357a87 + eb6aa0d commit 7481c37

10 files changed

Lines changed: 92 additions & 315 deletions

File tree

‎controller/.snyk‎

Lines changed: 68 additions & 294 deletions
Large diffs are not rendered by default.

‎controller/app/build.gradle‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ def hasGoogleServices = file("google-services.json").exists()
3434
android {
3535
// 2. Identity
3636
namespace 'org.appdevforall.k2go'
37-
compileSdk 34
37+
compileSdk 35
3838
ndkVersion "26.3.11579264"
3939

4040
defaultConfig {
4141
applicationId "org.appdevforall.k2go"
4242
minSdk 24
43-
// VITAL FOR PROOT AND W^X! Do not go up unless strictly necessary
44-
targetSdk 28
43+
// targetSdk 35 for Play. proot is NOT blocked above 28: AOSP neverallows only
44+
// execute_no_trans (direct execve) on app_data_file; K2Go runs guest binaries
45+
// through the proot loader (mmap/execute), which AOSP allows on certified devices.
46+
// Verified on-device at 35. See K2GO-438 and controller/docs/ARCHITECTURE.md.
47+
targetSdk 35
4548

4649
// 3. Version Control (OTA)
4750
// NOTE: With splits enabled, this number will be multiplied by 10 (e.g. 50 -> 500, 501, 502)

‎controller/app/src/main/AndroidManifest.xml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
99
<!-- ADFA-4520: LocalOnlyHotspot requires location at runtime on this targetSdk -->
1010
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
11+
<!-- K2GO-438: Android 12+ lets the user grant COARSE only, so declare it with FINE (CoarseFineLocation). -->
12+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
1113
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
1214
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
1315

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import android.content.Context;
1717
import android.content.Intent;
1818
import android.content.IntentFilter;
19-
import android.os.Build;
2019
import android.os.Bundle;
2120
import android.os.Handler;
2221
import android.os.Looper;
@@ -143,11 +142,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
143142
public void onStart() {
144143
super.onStart();
145144
IntentFilter f = new IntentFilter(DashboardRebuildService.ACTION_STATE);
146-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
147-
requireContext().registerReceiver(rebuildState, f, Context.RECEIVER_NOT_EXPORTED);
148-
} else {
149-
requireContext().registerReceiver(rebuildState, f);
150-
}
145+
ContextCompat.registerReceiver(requireContext(), rebuildState, f,
146+
ContextCompat.RECEIVER_NOT_EXPORTED);
151147
resolveInitialUpdatingState();
152148
}
153149

‎controller/app/src/main/java/org/appdevforall/k2go/update/presentation/UpdateController.java‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.appdevforall.k2go.ui.dialog.BrandDialog;
3232
import androidx.appcompat.app.AppCompatActivity;
33+
import androidx.core.content.ContextCompat;
3334
import androidx.core.content.FileProvider;
3435
import androidx.lifecycle.ViewModelProvider;
3536

@@ -79,11 +80,8 @@ public UpdateController(AppCompatActivity activity) {
7980

8081
public void registerDownloadReceiver() {
8182
IntentFilter filter = new IntentFilter(android.app.DownloadManager.ACTION_DOWNLOAD_COMPLETE);
82-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
83-
activity.registerReceiver(downloadReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
84-
} else {
85-
activity.registerReceiver(downloadReceiver, filter);
86-
}
83+
ContextCompat.registerReceiver(activity, downloadReceiver, filter,
84+
ContextCompat.RECEIVER_NOT_EXPORTED);
8785
}
8886

8987
public void unregisterDownloadReceiver() {

‎controller/build.gradle‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ buildscript {
2828
*/
2929

3030
dependencies {
31-
classpath 'com.android.tools.build:gradle:8.4.1'
31+
classpath 'com.android.tools.build:gradle:8.8.0'
3232
// ADFA-4466 Phase 1: Firebase (Google Services) Gradle plugin.
3333
classpath 'com.google.gms:google-services:4.4.2'
3434
}

‎controller/docs/ARCHITECTURE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ This shape is *why* every new feature deepens the debt: there is no seam to add
4040

4141
Some "smells" are load-bearing and must not be naively removed:
4242

43-
- **`targetSdk 28`** is intentional — it exempts the app from Android 10–14 runtime enforcement so proot's W^X memory model works. Raising it (required for Play Store) is a *project*, not a cleanup (see `TECH_DEBT_PLAN.md` §Phase 4).
43+
- **`targetSdk 28`** was believed load-bearing for proot (W^X). That is wrong: proot is NOT blocked at targetSdk 29+. AOSP neverallows only `execute_no_trans` (direct execve) on `app_data_file`; K2Go runs binaries through the proot loader (`mmap`/`execute`), which AOSP allows on every certified device. Verified on-device at targetSdk 35 (Samsung A16 stock Android 16, OnePlus 7T LineageOS 15). It is being raised to 35 for Play (K2GO-438) plus the API-33/34/35 fixes (K2GO-439).
4444
- **`usesCleartextTraffic="true"`** is currently required for the local rsync/APK/HTTP servers. The goal is to *scope* it via a network-security-config, not to flip it off.
4545
- **`MANAGE_EXTERNAL_STORAGE`** and **`PURPOSE`-broad keystore keys** exist for real reasons but are over-broad; tighten, don't delete.
4646
- The build's **SHA256 audit of native binaries at build time** is a genuine strength — preserve it.

‎controller/docs/SECURITY-TRIAGE.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ maintenance, with its own testing, tracked separately, not as a security patch.
7878
them.
7979
- On an AGP or UTP upgrade, re-run the scan and re-check the `.snyk` entries. Remove
8080
the ones the new toolchain fixed. Renew or update the rest.
81-
- Keep `.snyk` at the directory where `snyk test` runs (here: `controller/`). Move it
82-
if the scan root changes.
81+
- Run the scan so the policy actually applies. With `--all-projects`, Snyk resolves
82+
`.snyk` from each manifest's own directory, not from the scan root. Run from
83+
`controller/` with `--policy-path` so the single root policy applies to every
84+
project: `snyk test --all-projects --policy-path=.snyk` (and the same for
85+
`snyk monitor`). Without `--policy-path` the ignores silently do not apply.
86+
- Keep `.snyk` at `controller/` (the scan root). Move it if the scan root changes.
8387

8488
## Pocket rule
8589

‎controller/docs/TECH_DEBT_PLAN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ _Last updated: 2026-09-25. Tracks remediation work against the findings below. I
88

99
> **Current status (2026-09-25): post-redesign snapshot.** The redesign retired the original god classes: `MainActivity` and `DeployFragment` no longer exist, so the `F1` / `D1` targets and the older entries that name them are historical records, not the current tree. The current god-tier classes are `install/presentation/InstallService` (~2.1k LOC), `redesign/SetupProgressActivity` (~1.6k) and `redesign/CloneFragment` (~1.6k); the root `CLAUDE.md` design map holds the full list, the second tier, and the shared debt (public/static state, hand-rolled `HttpURLConnection` across ~27 classes, inline size formatting). The server-lifecycle redesign (`ADR-5343`) and the operation-model work shipped: 31 of 34 tracked tickets are done. The live board is `controller/docs/operation-model-roadmap.svg` and the lifecycle state map is `controller/docs/state-spine.svg`; open work is `K2GO-4` (download contract, last leg `K2GO-255`) and `K2GO-235` (module removal). Project keys moved ADFA -> K2GO on 1 Sep 2026: the `ADFA-XXXX` references in the dated logs below are historical and are kept as-is (Jira redirects each to its current `K2GO-XXXX`).
1010

11-
> **Current status (2026-07-15): register essentially closed at code level; v0.4.x cleanup wrapped.** Corrects stale entries below. **F1 (`MainActivity` God class) — DONE:** carved from ~2,384 to ~900 LOC via a Controller/Host seam (`TerminalController`, `ServerController`, an update controller); the ~727-LOC `addNewTerminalSession` is gone. **D3 — DONE (ADFA-4713):** the box base URL is now a single `config/BoxEndpoints.BASE`; ~10 hardcoded sites reference it (JVM-tested, no behavior change). **S18 — DONE (ADFA-4714):** cleartext scoped via `res/xml/network_security_config.xml` to loopback only (localhost:8085, 127.0.0.1:8114) and denied elsewhere; instrumentation test `NetworkSecurityConfigTest` passed on-device. **S13 — DONE (ADFA-4717):** removed the dead `TermuxCallbackReceiver` + its uncalled feeder chain (`performHeartbeat`/`sendStimulus`/`performDebugPing`) — legacy v1 external-`com.termux` IPC, superseded by the bundled termux-core — plus 7 now-orphaned strings across 34 locales. **D2 follow-up — DONE (ADFA-4718):** single-quoted the extract `xz|tar` pipe paths (the backup pipe was already quoted under D11; the bootstrap command is a static string). **D17 — reviewed, no further work:** `ApkServer` is GET-only and serves one public file (the app's own APK) over the LAN only while the user shares; auth/HTTPS not warranted (friction + self-signed warnings for no real gain). **M9 (`targetSdk 28`) — WON'T MOVE, by design:** load-bearing for PRoot + W^X (proot executes the rootfs guest binaries from writable storage, which targetSdk 29+ forbids); documented at the `targetSdk` line in `build.gradle`. The "blocks Play Store" rationale is moot — K2Go is sideloaded, never Play-distributed. **`minifyEnabled false` — reviewed, kept off:** low value for an offline/sideloaded app; enabling R8 would need ProGuard rules + on-device testing, so it gets its own ticket if ever wanted. **S11 — reviewed, acceptable residual:** the rsync secret is written plaintext to disk only while sharing, now owner-only (`SecretStore`) and deleted on stop; plaintext-at-rest is inherent to rsyncd. **ADFA-4476 — DONE** (was listed open below). **Remaining (verification / docs, no app code):** on-device 32- and 64-bit backup round-trip; the arbitrary-file attack-vector analysis; a `ROOTFS_MANIFEST.md` addendum (origin / algo:none); and the VPN dead-code removal (its own cleanup review). With the code-level register closed, the v0.4.x docs umbrella (ADFA-4452) can rotate/close.
11+
> **Current status (2026-07-15): register essentially closed at code level; v0.4.x cleanup wrapped.** Corrects stale entries below. **F1 (`MainActivity` God class) — DONE:** carved from ~2,384 to ~900 LOC via a Controller/Host seam (`TerminalController`, `ServerController`, an update controller); the ~727-LOC `addNewTerminalSession` is gone. **D3 — DONE (ADFA-4713):** the box base URL is now a single `config/BoxEndpoints.BASE`; ~10 hardcoded sites reference it (JVM-tested, no behavior change). **S18 — DONE (ADFA-4714):** cleartext scoped via `res/xml/network_security_config.xml` to loopback only (localhost:8085, 127.0.0.1:8114) and denied elsewhere; instrumentation test `NetworkSecurityConfigTest` passed on-device. **S13 — DONE (ADFA-4717):** removed the dead `TermuxCallbackReceiver` + its uncalled feeder chain (`performHeartbeat`/`sendStimulus`/`performDebugPing`) — legacy v1 external-`com.termux` IPC, superseded by the bundled termux-core — plus 7 now-orphaned strings across 34 locales. **D2 follow-up — DONE (ADFA-4718):** single-quoted the extract `xz|tar` pipe paths (the backup pipe was already quoted under D11; the bootstrap command is a static string). **D17 — reviewed, no further work:** `ApkServer` is GET-only and serves one public file (the app's own APK) over the LAN only while the user shares; auth/HTTPS not warranted (friction + self-signed warnings for no real gain). **M9 (`targetSdk 28`): reopened for Play (K2GO-438).** The earlier WON'T-MOVE rationale was wrong: proot is NOT blocked at targetSdk 29+. AOSP neverallows only `execute_no_trans` (direct execve) on `app_data_file`; K2Go runs binaries through the proot loader (`mmap`/`execute`), which AOSP allows on every certified device. Verified on-device at targetSdk 35 (Samsung A16 stock Android 16, OnePlus 7T LineageOS 15: box boots, 0 exec denials). Raising targetSdk breaks API-33/34/35 behaviors (NEARBY_WIFI_DEVICES, edge-to-edge, typed FGS) that are fixable (K2GO-439). **`minifyEnabled false` — reviewed, kept off:** low value for an offline/sideloaded app; enabling R8 would need ProGuard rules + on-device testing, so it gets its own ticket if ever wanted. **S11 — reviewed, acceptable residual:** the rsync secret is written plaintext to disk only while sharing, now owner-only (`SecretStore`) and deleted on stop; plaintext-at-rest is inherent to rsyncd. **ADFA-4476 — DONE** (was listed open below). **Remaining (verification / docs, no app code):** on-device 32- and 64-bit backup round-trip; the arbitrary-file attack-vector analysis; a `ROOTFS_MANIFEST.md` addendum (origin / algo:none); and the VPN dead-code removal (its own cleanup review). With the code-level register closed, the v0.4.x docs umbrella (ADFA-4452) can rotate/close.
1212

1313
> **Current status (2026-06-30):** **Phase 3 — Share tab carved: `S14` DONE (epic ADFA-1028, umbrella ADFA-4492).** `SyncFragment`'s mechanic was extracted into a clean, exportable stack: a pure `sync/domain` (rsyncd.conf/argv builders, progress parse, exit-code→outcome, `ShareConfig`, `TransferGuard`, `SyncCredentialValidator` — no `android.*`, JVM-tested), a `TransportEngine` port with `RsyncManager` as its adapter, platform adapters (`NetworkInterfaces`/`QrCodec`/`SecretStore`), and an Activity-scoped `SyncStateViewModel` + `SyncProgressRepository` so the probe/dry-run/transfer survive a configuration-change recreation (PRs #93/#94/#96/#98/#99/#104). Folded in and closed here: `S7` (hardcoded ports → `ShareConfig`), `S8`/`S9`/`S10` (concurrency/lifecycle), `S11` (rsync secret lifecycle), `S15`/`S16` (theming / magic-strings), the `EX1–EX6` export items, and **`D17`** (`ApkServer` now GET-only). **Residual — DONE (ADFA-4506):** the `SyncFragment` view was carved into per-area collaborators via the Controller/Host seam — `ArchCheckController` (#126), `ShareController` (rsync daemon + APK server together, #127) and `ReceiveController` (scan/probe/dry-run/transfer, #128) — leaving a thin ~261 LOC view (from ~810) that only wires the mode toggle, the QR scanner, system-protection and the hosts. No separate `SyncViewModel` was needed beyond the already-carved `SyncStateViewModel`/`SyncProgressRepository`. **Separately, real-world Share robustness (bug ADFA-4496, closed):** Wi-Fi network binding for the receive (#105), IP-under-QR transparency, a phantom-process SIGKILL (exit 137) safety net (#107), and an informed pre-flight + connection-failed hint (#109); the recurring "Connection Failed" was root-caused to **AP client-isolation** (a network condition), not the app. **Phase 3 remainder:** `F1` (`MainActivity` ~2,384 LOC) not carved; `D3` (central config/endpoints) not done.
1414

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4+
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
55
zipStoreBase=GRADLE_USER_HOME
66
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)