microg/GmsCore · error · RuntimeException
App already listening
Error message
App already listening
What it means
startSmsRetriever throws 'App already listening' when the requests map already contains an active RETRIEVER-type request with the same packageName and appHashString. The library allows only one active SMS retriever listener per app, and starting a second one would produce ambiguous OTP delivery.
Source
Thrown at play-services-auth-api-phone/core/src/main/kotlin/org/microg/gms/auth/phone/SmsRetrieverCore.kt:103
}
}
private suspend fun ensureReady(permissions: Array<String>): Boolean {
if (SDK_INT < 19) throw RuntimeException("Version not supported")
if (!ensurePermission(permissions)) return false
configureBroadcastListenersIfNeeded()
return true
}
suspend fun startSmsRetriever(packageName: String) {
val appHashString = getHashString(packageName)
if (!ensureReady(arrayOf(RECEIVE_SMS)))
throw RuntimeException("Initialization failed")
if (anyOtherPackageHasHashString(packageName, appHashString))
throw RuntimeException("Collision in hash string, can't use SMS Retriever API")
if (requests.values.any { it.packageName == packageName && it.appHashString == appHashString && it.type == RETRIEVER })
throw RuntimeException("App already listening")
val request = SmsRetrieverRequest(
id = requestIdCounter.incrementAndGet(),
type = RETRIEVER,
packageName = packageName,
appHashString = appHashString,
timeoutPendingIntent = getTimeoutPendingIntent(context, packageName)
)
requests[request.id] = request
alarmManager.set(AlarmManager.RTC, request.creation + TIMEOUT, request.timeoutPendingIntent)
}
suspend fun startWithConsentPrompt(packageName: String, senderPhoneNumber: String?) {
if (!ensureReady(arrayOf(RECEIVE_SMS, READ_CONTACTS)))
throw RuntimeException("Initialization failed")
if (requests.values.any { it.packageName == packageName && it.senderPhoneNumber == senderPhoneNumber && it.type == USER_CONSENT })
throw RuntimeException("App already listening")
View on GitHub (pinned to 157c9d86ac)
Solutions
- Track a local 'listening' flag and skip the second startSmsRetriever call
- Wait for the existing request to time out or complete before restarting
- Catch this RuntimeException and treat it as 'already active' rather than a failure
- Deduplicate triggers (debounce button clicks; move start out of onResume into a one-shot init)
Example fix
// before
scope.launch { core.startSmsRetriever(packageName) }
// after
if (!isListening) {
isListening = true
scope.launch { core.startSmsRetriever(packageName) }
} Defensive patterns
Strategy: try-catch
Try / catch
try { core.startSmsRetriever(pkg) } catch (e: RuntimeException) { if (e.message == "App already listening") { /* treat as success / no-op */ } else throw e } Prevention
- Keep a boolean 'listening' flag around retriever start/stop
- Debounce UI triggers; never start retrieval in onResume without a guard
- Rely on the request TIMEOUT before re-attempting
When it happens
Trigger: Calling startSmsRetriever() twice for the same package without the previous request timing out or being completed/cancelled (TIMEOUT-based requests stay in the map until they expire).
Common situations: Double-invoking on Activity/Fragment recreation (e.g. retry in onResume); retry logic that relaunches retrieval after a brief failure while the original request is still alive; rapid user taps triggering the flow twice.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Position already set using positionFromBounds()
- Position already set using position()
- Version not supported
- Initialization failed
- Collision in hash string, can't use SMS Retriever API
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/b213c348e9080c81.
Report an issue: GitHub.