Skip to content

Commit 31340d6

Browse files
committed
Merge remote-tracking branch 'origin/stage' into fix/ADFA-4387
2 parents a73139a + eabb240 commit 31340d6

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

‎termux/termux-app/src/main/java/com/termux/app/TermuxService.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ public void onCreate() {
122122
// load and TermuxActivity handles reloads
123123
mProperties = TermuxAppSharedProperties.getProperties();
124124

125-
mShellManager = TermuxShellManager.getShellManager();
125+
// Use init() instead of getShellManager() so the singleton is lazily created if the
126+
// OS auto-restarted the service after process death (TermuxApplication.onCreate did not
127+
// run, leaving the static singleton null and causing an NPE in buildNotification()).
128+
mShellManager = TermuxShellManager.init(getApplicationContext());
126129

127130
runStartForeground();
128131

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.termux.app;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
6+
import com.termux.shared.termux.shell.TermuxShellManager;
7+
8+
import java.lang.reflect.Field;
9+
10+
import org.junit.Before;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.robolectric.Robolectric;
14+
import org.robolectric.RobolectricTestRunner;
15+
import org.robolectric.android.controller.ServiceController;
16+
17+
/**
18+
* Repro for ADFA-4330: when the OS auto-restarts {@link TermuxService} after process death,
19+
* {@code TermuxApplication.onCreate()} does NOT run, so the static
20+
* {@link TermuxShellManager} singleton is still {@code null}.
21+
*
22+
* <p>On the pre-fix baseline, {@code TermuxService.onCreate()} assigned
23+
* {@code mShellManager = TermuxShellManager.getShellManager()} (which returns the null
24+
* singleton), then immediately called {@code runStartForeground() -> buildNotification() ->
25+
* getTermuxSessionsSize()}, dereferencing the null {@code mShellManager} and throwing a
26+
* {@link NullPointerException}.
27+
*
28+
* <p>The fix changes that assignment to {@code TermuxShellManager.init(applicationContext)},
29+
* which lazily creates the singleton, so the service starts cleanly.
30+
*
31+
* <p>This test simulates the auto-restart by forcing the static singleton back to {@code null}
32+
* before creating the service, then asserts the service comes up and
33+
* {@link TermuxService#getTermuxSessionsSize()} returns 0 instead of NPE-ing.
34+
*/
35+
@RunWith(RobolectricTestRunner.class)
36+
public class TermuxServiceShellManagerNpeTest {
37+
38+
/**
39+
* Reset the static singleton to null to mimic a fresh process where
40+
* TermuxApplication.onCreate() (which would normally call init()) never ran.
41+
*/
42+
@Before
43+
public void clearShellManagerSingleton() throws Exception {
44+
Field f = TermuxShellManager.class.getDeclaredField("shellManager");
45+
f.setAccessible(true);
46+
f.set(null, null);
47+
}
48+
49+
/** Service onCreate() with a null shell-manager singleton must not NPE and must expose usable sessions. */
50+
@Test
51+
public void onCreateWithNullSingleton_doesNotNpe_andSessionsAreUsable() {
52+
// Sanity: the auto-restart precondition — singleton is null going in.
53+
assertEquals(null, TermuxShellManager.getShellManager());
54+
55+
// Drive the REAL service lifecycle. On stage this throws NullPointerException inside
56+
// onCreate() -> runStartForeground() -> buildNotification() -> getTermuxSessionsSize().
57+
ServiceController<TermuxService> controller =
58+
Robolectric.buildService(TermuxService.class).create();
59+
TermuxService service = controller.get();
60+
61+
// After a clean onCreate(), the shell manager must be wired up and queryable.
62+
assertNotNull("mShellManager must be initialized after onCreate()",
63+
TermuxShellManager.getShellManager());
64+
assertEquals("A freshly created service manages zero sessions",
65+
0, service.getTermuxSessionsSize());
66+
67+
controller.destroy();
68+
}
69+
}

0 commit comments

Comments
 (0)