{"record":{"id":"b213c348e9080c81","repo":"microg/GmsCore","slug":"app-already-listening","errorCode":null,"errorMessage":"App already listening","messagePattern":"App already listening","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"play-services-auth-api-phone/core/src/main/kotlin/org/microg/gms/auth/phone/SmsRetrieverCore.kt","lineNumber":103,"sourceCode":"        }\n    }\n\n    private suspend fun ensureReady(permissions: Array<String>): Boolean {\n        if (SDK_INT < 19) throw RuntimeException(\"Version not supported\")\n        if (!ensurePermission(permissions)) return false\n        configureBroadcastListenersIfNeeded()\n        return true\n    }\n\n    suspend fun startSmsRetriever(packageName: String) {\n        val appHashString = getHashString(packageName)\n\n        if (!ensureReady(arrayOf(RECEIVE_SMS)))\n            throw RuntimeException(\"Initialization failed\")\n        if (anyOtherPackageHasHashString(packageName, appHashString))\n            throw RuntimeException(\"Collision in hash string, can't use SMS Retriever API\")\n        if (requests.values.any { it.packageName == packageName && it.appHashString == appHashString && it.type == RETRIEVER })\n            throw RuntimeException(\"App already listening\")\n\n        val request = SmsRetrieverRequest(\n            id = requestIdCounter.incrementAndGet(),\n            type = RETRIEVER,\n            packageName = packageName,\n            appHashString = appHashString,\n            timeoutPendingIntent = getTimeoutPendingIntent(context, packageName)\n        )\n        requests[request.id] = request\n        alarmManager.set(AlarmManager.RTC, request.creation + TIMEOUT, request.timeoutPendingIntent)\n    }\n\n    suspend fun startWithConsentPrompt(packageName: String, senderPhoneNumber: String?) {\n        if (!ensureReady(arrayOf(RECEIVE_SMS, READ_CONTACTS)))\n            throw RuntimeException(\"Initialization failed\")\n        if (requests.values.any { it.packageName == packageName && it.senderPhoneNumber == senderPhoneNumber && it.type == USER_CONSENT })\n            throw RuntimeException(\"App already listening\")\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-auth-api-phone/core/src/main/kotlin/org/microg/gms/auth/phone/SmsRetrieverCore.kt#L85-L121","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"// before\nscope.launch { core.startSmsRetriever(packageName) }\n// after\nif (!isListening) {\n    isListening = true\n    scope.launch { core.startSmsRetriever(packageName) }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { core.startSmsRetriever(pkg) } catch (e: RuntimeException) { if (e.message == \"App already listening\") { /* treat as success / no-op */ } else throw e }","preventionTips":["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"],"tags":["android","sms-retriever","duplicate-request","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}