Skip to content

Commit 9c24096

Browse files
committed
SpamUtils.kt: add missing callbacks to proceed normally when not spam and improve API checkers speed
1 parent a8dfee8 commit 9c24096

File tree

1 file changed

+63
-19
lines changed

1 file changed

+63
-19
lines changed

app/src/main/java/com/addev/listaspam/util/SpamUtils.kt

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ import com.addev.listaspam.R
2020
import com.google.i18n.phonenumbers.PhoneNumberUtil
2121
import kotlinx.coroutines.CoroutineScope
2222
import kotlinx.coroutines.Dispatchers
23+
import kotlinx.coroutines.ExperimentalCoroutinesApi
2324
import kotlinx.coroutines.channels.Channel
2425
import kotlinx.coroutines.coroutineScope
2526
import kotlinx.coroutines.launch
2627
import kotlinx.coroutines.runBlocking
28+
import kotlinx.coroutines.selects.onTimeout
29+
import kotlinx.coroutines.selects.select
2730
import kotlinx.coroutines.withContext
2831
import okhttp3.OkHttpClient
2932
import okhttp3.Request
3033
import org.jsoup.Jsoup
3134
import java.io.IOException
3235
import java.util.Locale
36+
import java.util.concurrent.atomic.AtomicInteger
3337
import java.util.logging.Logger
3438

3539
/**
@@ -132,13 +136,14 @@ class SpamUtils {
132136
callback: (isSpam: Boolean) -> Unit = {}
133137
) {
134138
CoroutineScope(Dispatchers.IO).launch {
135-
val number = if (details != null) getRawPhoneNumber(details) else phoneNumber;
136-
137139
if (!isBlockingEnabled(context)) {
138140
showToast(context, context.getString(R.string.blocking_disabled), Toast.LENGTH_LONG)
141+
callback(false)
139142
return@launch
140143
}
141144

145+
val number = if (details != null) getRawPhoneNumber(details) else phoneNumber;
146+
142147
val sharedPreferences = context.getSharedPreferences(SPAM_PREFS, Context.MODE_PRIVATE)
143148
val blockedNumbers = sharedPreferences.getStringSet(BLOCK_NUMBERS_KEY, null)
144149

@@ -153,18 +158,21 @@ class SpamUtils {
153158
)
154159
return@launch
155160
} else {
161+
callback(false)
156162
return@launch
157163
}
158164
}
159165

160166
// Check whitelist first - if whitelisted, always allow
161167
if (isNumberWhitelisted(context, number)) {
168+
callback(false)
162169
return@launch
163170
}
164171

165172
// Don't check number if is in contacts
166173
val isNumberInAgenda = isNumberInAgenda(context, number)
167174
if (isNumberInAgenda) {
175+
callback(false)
168176
return@launch
169177
}
170178

@@ -250,42 +258,79 @@ class SpamUtils {
250258
callback
251259
)
252260
} else {
253-
handleNonSpamNumber(context, number)
261+
// handleNonSpamNumber(context, number)
262+
callback(false)
254263
return@launch
255264
}
256265
}
257266
}
258267

259268
/**
260-
* Runs a list of suspend functions in parallel to check if a number is spam.
269+
* Performs a "race" among multiple spam checkers to determine if a phone number is spam.
270+
*
271+
* Each checker is a suspend function that returns `true` if the number is spam.
272+
* The function returns `true` as soon as the first checker reports spam.
273+
* If all checkers finish and none report spam, it returns `false`.
274+
* A timeout can be provided to handle long-running or stuck checkers.
261275
*
262-
* Launches all checks simultaneously and returns `true` as soon as
263-
* any function returns `true`. At that point, it cancels all other running tasks.
264-
* If none return `true`, it returns `false`.
276+
* This function launches all checkers concurrently and cancels remaining jobs
277+
* as soon as a result is determined, to save resources.
265278
*
266-
* @param spamCheckers List of suspend functions that take a number (String) and return a Boolean indicating spam status.
267-
* @param number The number (String) to be evaluated by the spam checkers.
268-
* @return `true` if at least one function determines the number is spam; `false` otherwise.
279+
* @param spamCheckers A list of suspend functions that each take a phone number
280+
* and return `true` if it is spam.
281+
* @param number The phone number to check for spam.
282+
* @param timeoutMs Maximum time in milliseconds to wait for a result before returning `false`.
283+
* Default is 5000ms.
284+
*
285+
* @return `true` if any checker reports spam, `false` if none report spam or timeout occurs.
269286
*/
287+
@OptIn(ExperimentalCoroutinesApi::class)
270288
private suspend fun isSpamRace(
271289
spamCheckers: List<suspend (String) -> Boolean>,
272-
number: String
290+
number: String,
291+
timeoutMs: Long = 5000
273292
): Boolean = coroutineScope {
274-
val resultChannel = Channel<Boolean>()
293+
if (spamCheckers.isEmpty()) return@coroutineScope false
294+
295+
val resultChannel = Channel<Boolean>(capacity = Channel.UNLIMITED)
296+
val remaining = AtomicInteger(spamCheckers.size)
275297

276298
val jobs = spamCheckers.map { checker ->
277299
launch {
300+
val start = System.currentTimeMillis()
278301
val result = runCatching { checker(number) }.getOrDefault(false)
279-
if (result) resultChannel.send(true)
302+
val elapsed = System.currentTimeMillis() - start
303+
304+
Logger.getLogger("SpamUtils").info(
305+
"Spam checker for $number completed in ${elapsed}ms, result: $result"
306+
)
307+
308+
if (result) {
309+
resultChannel.send(true)
310+
} else if (remaining.decrementAndGet() == 0) {
311+
resultChannel.close()
312+
}
280313
}
281314
}
282315

283-
val isSpam = resultChannel.receive()
284-
285-
// Cancel all other jobs
286-
jobs.forEach { it.cancel() }
316+
val isSpam = try {
317+
select<Boolean> {
318+
resultChannel.onReceiveCatching { result ->
319+
result.getOrNull() ?: false
320+
}
321+
onTimeout(timeoutMs) {
322+
Logger.getLogger("SpamUtils").warning(
323+
"Spam check timed out after ${timeoutMs}ms for $number"
324+
)
325+
false
326+
}
327+
}
328+
} finally {
329+
jobs.forEach { it.cancel() }
330+
resultChannel.cancel()
331+
}
287332

288-
return@coroutineScope isSpam
333+
isSpam
289334
}
290335

291336
private fun buildSpamCheckers(context: Context): List<suspend (String) -> Boolean> {
@@ -443,7 +488,6 @@ class SpamUtils {
443488
context.getString(R.string.incoming_call_not_spam),
444489
10000
445490
)
446-
removeSpamNumber(context, number)
447491
}
448492
}
449493

0 commit comments

Comments
 (0)