2626
2727import androidx .core .app .NotificationCompat ;
2828
29+ import java .util .concurrent .Executors ;
30+ import java .util .concurrent .ScheduledExecutorService ;
31+ import java .util .concurrent .TimeUnit ;
32+
2933public class WatchdogService extends Service {
3034 private static final String TAG = "IIAB-Watchdog" ;
3135 private static final String CHANNEL_ID = "watchdog_channel" ;
@@ -46,6 +50,12 @@ public class WatchdogService extends Service {
4650 private PowerManager .WakeLock wakeLock ;
4751 private WifiManager .WifiLock wifiLock ;
4852
53+ // K2GO-386 (Layer 3): a single background poller checks free space while the box is up. On a critical
54+ // reading DiskGuard reaps and reclaims, then by default keeps the system alive. Started once per
55+ // protected session, stopped on destroy.
56+ private ScheduledExecutorService diskGuardPoller ;
57+ private static final long DISK_GUARD_INTERVAL_S = 25 ;
58+
4959 @ Override
5060 public void onCreate () {
5161 super .onCreate ();
@@ -80,6 +90,9 @@ private void startWatchdog() {
8090 // 2. Acquire CPU WakeLock to prevent sleep during heavy operations (e.g., Tar extraction, Rsync)
8191 acquireHardwareLocks ();
8292
93+ // K2GO-386 (barrier 2): guard free space for the life of this protected session.
94+ startDiskGuard ();
95+
8396 // 3. Notify the UI (MainActivity) that the engine is protected and running
8497 IIABWatchdog .logSessionStart (this );
8598 Intent startIntent = new Intent (ACTION_STATE_STARTED );
@@ -116,6 +129,28 @@ private void releaseHardwareLocks() {
116129 }
117130 }
118131
132+ // K2GO-386 (Layer 3): the free-space guard. One background poller ticks every DISK_GUARD_INTERVAL_S.
133+ // On a CRITICAL reading DiskGuard confirms, reaps the box, reclaims the runaway log, and by default
134+ // lets it restart; the in-box layers cannot stop an off-proot orphan. Started once per session.
135+ private void startDiskGuard () {
136+ if (diskGuardPoller != null ) return ;
137+ diskGuardPoller = Executors .newSingleThreadScheduledExecutor ();
138+ diskGuardPoller .scheduleWithFixedDelay (() -> {
139+ try {
140+ org .appdevforall .k2go .diskguard .DiskGuard .check (getApplicationContext ());
141+ } catch (Throwable t ) {
142+ Log .w (TAG , "K2GO-386: disk-guard tick failed" , t );
143+ }
144+ }, DISK_GUARD_INTERVAL_S , DISK_GUARD_INTERVAL_S , TimeUnit .SECONDS );
145+ }
146+
147+ private void stopDiskGuard () {
148+ if (diskGuardPoller != null ) {
149+ diskGuardPoller .shutdownNow ();
150+ diskGuardPoller = null ;
151+ }
152+ }
153+
119154 @ Override
120155 public void onDestroy () {
121156 RUNNING = false ; // ADFA-5343 (Phase 4b): protection is ending — clear the promoter's state signal
@@ -124,6 +159,9 @@ public void onDestroy() {
124159 stopIntent .setPackage (getPackageName ());
125160 sendBroadcast (stopIntent );
126161
162+ // K2GO-386 (barrier 2): stop the free-space guard — this protected session (box up) is ending.
163+ stopDiskGuard ();
164+
127165 // 2. Release Hardware Locks so the phone can sleep again
128166 releaseHardwareLocks ();
129167
0 commit comments