From b466c0e9a61840a49192f2322e61df87a0aa08ec Mon Sep 17 00:00:00 2001 From: Bryan Chan Date: Mon, 15 Jun 2026 21:40:50 -0400 Subject: [PATCH 1/3] ADFA-4330: init TermuxShellManager in TermuxService on auto-restart Sentry APPDEVFORALL-BR. Use TermuxShellManager.init() instead of getShellManager() so the singleton exists after OS service restart. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/main/java/com/termux/app/TermuxService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/termux/termux-app/src/main/java/com/termux/app/TermuxService.java b/termux/termux-app/src/main/java/com/termux/app/TermuxService.java index 9b5d11d2d0..b08aac6b8e 100644 --- a/termux/termux-app/src/main/java/com/termux/app/TermuxService.java +++ b/termux/termux-app/src/main/java/com/termux/app/TermuxService.java @@ -122,7 +122,10 @@ public void onCreate() { // load and TermuxActivity handles reloads mProperties = TermuxAppSharedProperties.getProperties(); - mShellManager = TermuxShellManager.getShellManager(); + // Use init() instead of getShellManager() so the singleton is lazily created if the + // OS auto-restarted the service after process death (TermuxApplication.onCreate did not + // run, leaving the static singleton null and causing an NPE in buildNotification()). + mShellManager = TermuxShellManager.init(getApplicationContext()); runStartForeground(); From de528daa59b0e696b0bc7fd114dffc7a460c8e4e Mon Sep 17 00:00:00 2001 From: Bryan Chan Date: Wed, 17 Jun 2026 05:43:55 -0700 Subject: [PATCH 2/3] ADFA-4330: add Robolectric repro for TermuxService shellManager NPE on auto-restart Reproduces the OS service auto-restart scenario where TermuxApplication.onCreate() never ran, leaving the static TermuxShellManager singleton null. Drives the real TermuxService.onCreate() via Robolectric; on the pre-fix baseline this NPEs in onCreate -> runStartForeground -> buildNotification -> getTermuxSessionsSize when mShellManager is null. Passes on the fix (init() lazily creates the singleton). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../app/TermuxServiceShellManagerNpeTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java diff --git a/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java b/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java new file mode 100644 index 0000000000..66691ab7cb --- /dev/null +++ b/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java @@ -0,0 +1,68 @@ +package com.termux.app; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.termux.shared.termux.shell.TermuxShellManager; + +import java.lang.reflect.Field; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ServiceController; + +/** + * Repro for ADFA-4330: when the OS auto-restarts {@link TermuxService} after process death, + * {@code TermuxApplication.onCreate()} does NOT run, so the static + * {@link TermuxShellManager} singleton is still {@code null}. + * + *

On the pre-fix baseline, {@code TermuxService.onCreate()} assigned + * {@code mShellManager = TermuxShellManager.getShellManager()} (which returns the null + * singleton), then immediately called {@code runStartForeground() -> buildNotification() -> + * getTermuxSessionsSize()}, dereferencing the null {@code mShellManager} and throwing a + * {@link NullPointerException}. + * + *

The fix changes that assignment to {@code TermuxShellManager.init(applicationContext)}, + * which lazily creates the singleton, so the service starts cleanly. + * + *

This test simulates the auto-restart by forcing the static singleton back to {@code null} + * before creating the service, then asserts the service comes up and + * {@link TermuxService#getTermuxSessionsSize()} returns 0 instead of NPE-ing. + */ +@RunWith(RobolectricTestRunner.class) +public class TermuxServiceShellManagerNpeTest { + + /** + * Reset the static singleton to null to mimic a fresh process where + * TermuxApplication.onCreate() (which would normally call init()) never ran. + */ + @Before + public void clearShellManagerSingleton() throws Exception { + Field f = TermuxShellManager.class.getDeclaredField("shellManager"); + f.setAccessible(true); + f.set(null, null); + } + + @Test + public void onCreateWithNullSingleton_doesNotNpe_andSessionsAreUsable() { + // Sanity: the auto-restart precondition — singleton is null going in. + assertEquals(null, TermuxShellManager.getShellManager()); + + // Drive the REAL service lifecycle. On stage this throws NullPointerException inside + // onCreate() -> runStartForeground() -> buildNotification() -> getTermuxSessionsSize(). + ServiceController controller = + Robolectric.buildService(TermuxService.class).create(); + TermuxService service = controller.get(); + + // After a clean onCreate(), the shell manager must be wired up and queryable. + assertNotNull("mShellManager must be initialized after onCreate()", + TermuxShellManager.getShellManager()); + assertEquals("A freshly created service manages zero sessions", + 0, service.getTermuxSessionsSize()); + + controller.destroy(); + } +} From a495d61a5ce4e8c0f6e479368ea857ff62726106 Mon Sep 17 00:00:00 2001 From: Bryan Chan Date: Fri, 19 Jun 2026 04:48:10 -0700 Subject: [PATCH 3/3] ADFA-4330: add KDoc for docstring coverage (CodeRabbit) Co-Authored-By: Claude Opus 4.8 --- .../java/com/termux/app/TermuxServiceShellManagerNpeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java b/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java index 66691ab7cb..c9c9ad8ca9 100644 --- a/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java +++ b/termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java @@ -46,6 +46,7 @@ public void clearShellManagerSingleton() throws Exception { f.set(null, null); } + /** Service onCreate() with a null shell-manager singleton must not NPE and must expose usable sessions. */ @Test public void onCreateWithNullSingleton_doesNotNpe_andSessionsAreUsable() { // Sanity: the auto-restart precondition — singleton is null going in.