11package com.sysadmindoc.callshield.data
22
33import android.app.Application
4+ import android.provider.CallLog
45import androidx.test.core.app.ApplicationProvider
6+ import com.sysadmindoc.callshield.data.CallFrequencyEvidenceTest.CallRow
7+ import com.sysadmindoc.callshield.data.CallFrequencyEvidenceTest.FakeCallLog
58import com.sysadmindoc.callshield.data.checker.PipelineTraceVerdict
69import com.sysadmindoc.callshield.data.model.BlockedCall
710import com.sysadmindoc.callshield.domain.model.BlockReasonCode
@@ -11,6 +14,7 @@ import org.junit.Assert.assertEquals
1114import org.junit.Before
1215import org.junit.Test
1316import org.junit.runner.RunWith
17+ import org.robolectric.Robolectric
1418import org.robolectric.RobolectricTestRunner
1519import org.robolectric.annotation.Config
1620
@@ -22,16 +26,20 @@ import org.robolectric.annotation.Config
2226@Config(application = Application ::class , sdk = [34 ])
2327class MeetingModeEvidenceTest {
2428 private lateinit var fixture: IsolatedRepositoryFixture
29+ private lateinit var callLog: FakeCallLog
2530 private val number = " +12125550123"
26- private val since = 1_735_689_600_000L
31+ private val now = System .currentTimeMillis()
32+ private val meetingTimes = (1 .. 3 ).map { now - it * 60_000L }
2733
2834 @Before
2935 fun setUp () {
36+ callLog = Robolectric .setupContentProvider(FakeCallLog ::class .java, CallLog .Calls .CONTENT_URI .authority)
3037 fixture = IsolatedRepositoryFixture (ApplicationProvider .getApplicationContext())
3138 runBlocking {
3239 fixture.dao.insertBlockedCalls(
33- (1 .. 3 ).map { BlockedCall (number = number, timestamp = since + it, matchReason = " meeting_mode" , logKey = " meeting-$it " ) } +
34- BlockedCall (number = number, timestamp = since + 10 , matchReason = " heuristic" , logKey = " heuristic" ),
40+ meetingTimes.mapIndexed { index, time ->
41+ BlockedCall (number = number, timestamp = time, matchReason = " meeting_mode" , logKey = " meeting-$index " )
42+ } + BlockedCall (number = number, timestamp = now - 10 * 60_000L , matchReason = " heuristic" , logKey = " heuristic" ),
3543 )
3644 }
3745 }
@@ -41,6 +49,13 @@ class MeetingModeEvidenceTest {
4149 fixture.close()
4250 }
4351
52+ private suspend fun frequencyVerdict (): PipelineTraceVerdict =
53+ fixture.repository
54+ .traceRules(number)
55+ .entries
56+ .single { it.checkerName == " frequency" }
57+ .verdict
58+
4459 @Test
4560 fun `meeting silences are stored under their own reason code` () =
4661 runBlocking {
@@ -52,17 +67,33 @@ class MeetingModeEvidenceTest {
5267 @Test
5368 fun `meeting silences don't count toward the repeat-caller frequency` () =
5469 runBlocking {
55- val frequency =
56- fixture.repository
57- .traceRules(number)
58- .entries
59- .single { it.checkerName == " frequency" }
60- assertEquals(PipelineTraceVerdict .PASS , frequency.verdict)
70+ // Android logs a call the screener silenced as missed, with a reason from API 31.
71+ // 15 s off the meeting rows, past the 10 s match window, so the reason alone must do it.
72+ callLog.rows =
73+ meetingTimes.map {
74+ CallRow (number, it + 15_000L , CallLog .Calls .MISSED_TYPE , CallLog .Calls .USER_MISSED_CALL_SCREENING_SERVICE_SILENCED )
75+ }
76+ assertEquals(PipelineTraceVerdict .PASS , frequencyVerdict())
77+
78+ // The same three calls with nothing to say they were silenced do escalate,
79+ // so the provider is being read and the pass above means something.
80+ callLog.rows = listOf (3 , 4 , 5 ).map { CallRow (number, now - it * 3_600_000L , CallLog .Calls .MISSED_TYPE ) }
81+ assertEquals(PipelineTraceVerdict .BLOCK , frequencyVerdict())
82+ }
83+
84+ @Test
85+ @Config(sdk = [29 ])
86+ fun `before API 31 the matching meeting rows keep silences out of the frequency` () =
87+ runBlocking {
88+ // No missed-call reason exists yet; the local meeting rows are the only evidence.
89+ callLog.rows = meetingTimes.map { CallRow (number, it + 1_500L , CallLog .Calls .MISSED_TYPE ) }
90+
91+ assertEquals(PipelineTraceVerdict .PASS , frequencyVerdict())
6192 }
6293
6394 @Test
6495 fun `meeting silences don't count as recent blocks for rapid-fire detection` () =
6596 runBlocking {
66- assertEquals(listOf (" heuristic" ), fixture.dao.getRecentBlockedNumbers(since ).map { it.matchReason })
97+ assertEquals(listOf (" heuristic" ), fixture.dao.getRecentBlockedNumbers(now - 60 * 60_000L ).map { it.matchReason })
6798 }
6899}
0 commit comments