Skip to content

Commit fa457f1

Browse files
committed
fix(ui): keep launch request ids counting across activity recreation
A refuting review of the blocked-log routing found that a recreated MainActivity restarted its request ids at 0 while the tab and Blocked-log guards restored the ids they had already handled. The next shortcut or summary tap could reuse a handled id and be ignored; on the emulator a cold start from the summary, a font-scale relaunch and a second tap left the user on Home (reproduced on the pre-fix build, fixed on this one). The saved state now carries the current id, so numbering continues. A launch request that switches tabs also closes an open search. Switching to the tab already selected never fired the tab-change effect, so the search kept covering the Blocked log. MainActivityIntentTest gains a regression test for the restored id; no existing test changed.
1 parent bf4c729 commit fa457f1

3 files changed

Lines changed: 56 additions & 13 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ All notable changes to CallShield will be documented in this file.
176176

177177
- Tapping the "blocked recently" notification opens the Blocked log now. It
178178
used to do nothing at all.
179+
- After the screen rotated or the font size changed, the next tap on an app
180+
shortcut or on that notification could be ignored. It opens its tab every
181+
time now, and it closes an open search, which used to cover the tab.
179182

180183
- Setup now says what to do when Android won't let a permission through. Apps
181184
installed outside an app store run into Android's restricted settings. The

‎app/src/main/java/com/sysadmindoc/callshield/ui/MainActivity.kt‎

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,15 @@ class MainActivity : AppCompatActivity() {
7878
// deep link / shortcut was handled in a previous incarnation and must
7979
// not re-run a scan, re-open a closed detail screen, or yank the
8080
// user's tab back to the shortcut target.
81-
launchRequest =
82-
if (savedInstanceState?.getBoolean(KEY_LAUNCH_CONSUMED) == true) {
83-
LaunchRequest(id = 0)
84-
} else {
85-
intent.toLaunchRequest(nextId = 1)
86-
}
81+
launchRequest = launchRequestFor(intent, savedInstanceState)
8782
consumeLaunchIntent()
8883

8984
setContent { CallShieldRoot(launchRequest = launchRequest) }
9085
}
9186

9287
override fun onSaveInstanceState(outState: Bundle) {
9388
super.onSaveInstanceState(outState)
94-
outState.putBoolean(KEY_LAUNCH_CONSUMED, true)
89+
outState.putLaunchState(launchRequest)
9590
}
9691

9792
override fun onNewIntent(intent: Intent) {
@@ -104,17 +99,13 @@ class MainActivity : AppCompatActivity() {
10499
/**
105100
* Strip the consumed launch payload (deep-link number, shortcut action)
106101
* after it has been folded into [launchRequest]. Defense-in-depth next to
107-
* the [KEY_LAUNCH_CONSUMED] saved-state flag (which is what actually
108-
* survives recreation — see onCreate).
102+
* the saved-state flag (which is what actually survives recreation, see
103+
* [launchRequestFor]).
109104
*/
110105
private fun consumeLaunchIntent() {
111106
intent = Intent(this, MainActivity::class.java)
112107
}
113108

114-
private companion object {
115-
const val KEY_LAUNCH_CONSUMED = "callshield_launch_consumed"
116-
}
117-
118109
override fun onResume() {
119110
super.onResume()
120111
// Re-evaluate protection health whenever the user returns to the app —
@@ -220,6 +211,12 @@ fun CallShieldApp(
220211
if (tabRequestId != null && tabRequestId != handledTabRequest) {
221212
handledTabRequest = tabRequestId
222213
selectedTab = startTab
214+
// A search left open would cover the tab this request asked for,
215+
// and switching to the tab already selected doesn't close it.
216+
if (showSearch) {
217+
showSearch = false
218+
viewModel.setSearchQuery("")
219+
}
223220
}
224221
}
225222

@@ -874,6 +871,30 @@ internal fun launchTab(shortcutAction: String?): Int =
874871
/** This request's id when it asks for the blocked log, so Activity can switch to it once. */
875872
internal fun LaunchRequest.blockedLogRequestId(): Int? = id.takeIf { shortcutAction == ACTION_OPEN_BLOCKED_LOG }
876873

874+
private const val KEY_LAUNCH_CONSUMED = "callshield_launch_consumed"
875+
private const val KEY_LAUNCH_REQUEST_ID = "callshield_launch_request_id"
876+
877+
/**
878+
* The request a (re)created activity starts with. A recreation keeps the id
879+
* it had instead of restarting the count: the tab and Blocked-log guards saved
880+
* the ids they already handled, and a later tap numbered from scratch could
881+
* land on one of those and be ignored.
882+
*/
883+
internal fun launchRequestFor(
884+
intent: Intent?,
885+
savedInstanceState: Bundle?,
886+
): LaunchRequest =
887+
if (savedInstanceState?.getBoolean(KEY_LAUNCH_CONSUMED) == true) {
888+
LaunchRequest(id = savedInstanceState.getInt(KEY_LAUNCH_REQUEST_ID))
889+
} else {
890+
intent.toLaunchRequest(nextId = 1)
891+
}
892+
893+
internal fun Bundle.putLaunchState(request: LaunchRequest) {
894+
putBoolean(KEY_LAUNCH_CONSUMED, true)
895+
putInt(KEY_LAUNCH_REQUEST_ID, request.id)
896+
}
897+
877898
private val KNOWN_SHORTCUT_ACTIONS =
878899
setOf(
879900
"com.sysadmindoc.callshield.LOOKUP",

‎app/src/test/java/com/sysadmindoc/callshield/ui/MainActivityIntentTest.kt‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.sysadmindoc.callshield.ui
22

33
import android.content.Intent
44
import android.net.Uri
5+
import android.os.Bundle
56
import com.sysadmindoc.callshield.ui.screens.activity.ACTIVITY_BLOCKED
67
import com.sysadmindoc.callshield.ui.screens.activity.ACTIVITY_RECENT
78
import com.sysadmindoc.callshield.ui.screens.activity.initialActivityView
@@ -81,6 +82,24 @@ class MainActivityIntentTest {
8182
assertEquals(ACTIVITY_BLOCKED, initialActivityView(request.blockedLogRequestId()))
8283
}
8384

85+
@Test
86+
fun `a recreated activity keeps counting request ids instead of starting over`() {
87+
// Three summary taps: the first launch, then two onNewIntent calls.
88+
val first = launchRequestFor(Intent(ACTION_OPEN_BLOCKED_LOG), savedInstanceState = null)
89+
val third = Intent(ACTION_OPEN_BLOCKED_LOG).toLaunchRequest(nextId = first.id + 2)
90+
91+
// Rotation or a theme change replays the original intent into onCreate.
92+
val saved = Bundle().apply { putLaunchState(third) }
93+
val recreated = launchRequestFor(Intent(ACTION_OPEN_BLOCKED_LOG), saved)
94+
95+
assertEquals(3, recreated.id)
96+
assertNull(recreated.shortcutAction)
97+
assertNull(recreated.deepLinkNumber)
98+
// The tab and Blocked-log guards saved ids 1 to 3, so the next tap must get 4.
99+
val next = Intent(ACTION_OPEN_BLOCKED_LOG).toLaunchRequest(nextId = recreated.id + 1)
100+
assertEquals(4, next.blockedLogRequestId())
101+
}
102+
84103
@Test
85104
fun `other launches leave Activity on Recent`() {
86105
val lookup = Intent("com.sysadmindoc.callshield.LOOKUP").toLaunchRequest(nextId = 2)

0 commit comments

Comments
 (0)