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

  1. Track a local 'listening' flag and skip the second startSmsRetriever call
  2. Wait for the existing request to time out or complete before restarting
  3. Catch this RuntimeException and treat it as 'already active' rather than a failure
  4. 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

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


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/b213c348e9080c81. Report an issue: GitHub.