Skip to content

Commit 39aa9fe

Browse files
committed
fix(overlay): stop the service only for the newest start when an overlay closes
dismiss() called plain stopSelf(), which stops the service even when a newer start is already queued. The start intent is then delivered, shows its overlay, and onDestroy removes it right after. The withdrawal added in fc53929 makes that reachable: a rejected caller's withdrawal followed within milliseconds by the next caller's overlay start. The service now records the newest startId it was handed, and dismiss() calls stopSelf(latestStartId). Android ignores that while a newer start is pending, and stops the service exactly as before otherwise. Found by the verification pass over fc53929. Tests: CallerIdOverlayWithdrawTest gains a case that checks the stop names the newest start; no existing test changed. A control run with plain stopSelf() back in dismiss() fails it.
1 parent fc53929 commit 39aa9fe

3 files changed

Lines changed: 25 additions & 5 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<p align="center">
1313
<a href="https://github.com/SysAdminDoc/CallShield/releases/latest"><img src="https://img.shields.io/github/v/release/SysAdminDoc/CallShield?style=flat-square&color=a6e3a1" alt="Release"></a>
1414
<img src="https://img.shields.io/badge/Spam%20Numbers-51%2C403-f38ba8?style=flat-square" alt="51,403 Numbers">
15-
<img src="https://img.shields.io/badge/JVM%20unit%20tests-2061-94e2d5?style=flat-square" alt="2061 JVM unit tests">
15+
<img src="https://img.shields.io/badge/JVM%20unit%20tests-2062-94e2d5?style=flat-square" alt="2062 JVM unit tests">
1616
<img src="https://img.shields.io/badge/Android-10%2B-89b4fa?style=flat-square" alt="Android 10+">
1717
<img src="https://img.shields.io/badge/License-MIT-cba6f7?style=flat-square" alt="MIT License">
1818
<img src="https://img.shields.io/badge/API%20Keys-None-fab387?style=flat-square" alt="No required API keys">
@@ -592,12 +592,12 @@ RELEASE_KEY_PASSWORD=...
592592
## Testing
593593

594594
```bash
595-
./gradlew testDebugUnitTest # 2061 tests
595+
./gradlew testDebugUnitTest # 2062 tests
596596
./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.sysadmindoc.callshield.platform.TargetSdkBehaviorSmokeTest
597597
./gradlew verifyPipelineTests # Cloudflare Worker (node) + data-pipeline and translation checks (python)
598598
```
599599

600-
The suite is **2061 total JVM unit tests**, run with Robolectric wherever a screen, a service or the database is involved.
600+
The suite is **2062 total JVM unit tests**, run with Robolectric wherever a screen, a service or the database is involved.
601601

602602
Two GitHub workflows run without building the app. **Validation** runs the Worker and
603603
pipeline suites on every push except report-only ones (`run-pipeline-tests.ps1 -CorrectnessOnly`),
@@ -634,7 +634,7 @@ language in [issue #7](https://github.com/SysAdminDoc/CallShield/issues/7).
634634
| Community API | Cloudflare Workers |
635635
| URL Safety | Local spam-domain data, with optional PhishTank and OpenPhish |
636636
| Verification | Local Gradle, lint, and release-artifact checks |
637-
| Tests | 2061 JVM unit tests (JUnit) |
637+
| Tests | 2062 JVM unit tests (JUnit) |
638638
| Strings | 1794 string resources and 49 plural groups (translation-ready) |
639639
| Accessibility | 100+ content descriptions, 48dp touch targets |
640640
| Min SDK | 29 (Android 10) |

‎app/src/main/java/com/sysadmindoc/callshield/service/CallerIdOverlayService.kt‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,12 @@ class CallerIdOverlayService : Service() {
302302

303303
@Volatile private var activeConfidence = 0
304304

305+
// The newest start delivered. Closing an overlay stops the service only
306+
// for this start, so a start queued behind it (the next caller's overlay
307+
// right after a withdrawal) keeps the service running instead of being
308+
// torn down as soon as it shows.
309+
private var latestStartId = 0
310+
305311
// UI elements we need to update
306312
private var headerText: TextView? = null
307313
private var scoreText: TextView? = null
@@ -323,6 +329,7 @@ class CallerIdOverlayService : Service() {
323329
flags: Int,
324330
startId: Int,
325331
): Int {
332+
latestStartId = startId
326333
val number = intent?.getStringExtra(EXTRA_NUMBER) ?: ""
327334
val confidence = intent?.getIntExtra(EXTRA_CONFIDENCE, 0) ?: 0
328335
val reason = intent?.getStringExtra(EXTRA_REASON) ?: ""
@@ -1311,7 +1318,7 @@ class CallerIdOverlayService : Service() {
13111318
handler.removeCallbacksAndMessages(null)
13121319
deactivateOverlaySession()
13131320
removeOverlay()
1314-
stopSelf()
1321+
stopSelf(latestStartId)
13151322
}
13161323

13171324
private fun isCurrentSession(sessionId: Long): Boolean = isOverlayActive && activeSessionId == sessionId && overlayView != null

‎app/src/test/java/com/sysadmindoc/callshield/service/CallerIdOverlayWithdrawTest.kt‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ class CallerIdOverlayWithdrawTest {
7979
assertEquals(1, overlayViews().size)
8080
}
8181

82+
@Test
83+
fun `closing an overlay stops the service only for the newest start`() {
84+
CallerIdOverlayService.startIncoming(application, CALLER, 40, "invalid_format")
85+
deliverNextStart()
86+
CallerIdOverlayService.withdrawIncoming(application, CALLER)
87+
deliverNextStart()
88+
89+
// Android ignores stopSelf(id) when a start newer than id is queued,
90+
// so the next caller's overlay can't be torn down as it shows.
91+
assertEquals(startId, shadowOf(controller.get()).stopSelfId)
92+
assertTrue(overlayViews().isEmpty())
93+
}
94+
8295
@Test
8396
fun `a withdrawal with nothing shown stops the service`() {
8497
controller.get().onStartCommand(withdrawIntent(CALLER), 0, ++startId)

0 commit comments

Comments
 (0)