{"record":{"id":"54fcf43f85657f1b","repo":"microg/GmsCore","slug":"droidguard-should-not-be-available-locally","errorCode":null,"errorMessage":"DroidGuard should not be available locally","messagePattern":"DroidGuard should not be available locally","errorType":"exception","errorClass":"IllegalAccessException","httpStatus":null,"severity":"error","filePath":"play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt","lineNumber":31,"sourceCode":"import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest\nimport okio.ByteString.Companion.decodeHex\nimport okio.ByteString.Companion.of\nimport org.microg.gms.droidguard.*\nimport org.microg.gms.profile.Build\nimport org.microg.gms.profile.ProfileManager\nimport org.microg.gms.utils.singleInstanceOf\nimport java.io.File\nimport java.util.*\nimport com.android.volley.Request as VolleyRequest\nimport com.android.volley.Response as VolleyResponse\n\nclass NetworkHandleProxyFactory(private val context: Context) : HandleProxyFactory(context) {\n    private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context)\n    private val version = VersionUtil(context)\n    private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }\n\n    fun createHandle(packageName: String, flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy {\n        if (!DroidGuardPreferences.isLocalAvailable(context)) throw IllegalAccessException(\"DroidGuard should not be available locally\")\n        val (vmKey, byteCode, bytes) = readFromDatabase(flow) ?: fetchFromServer(flow, packageName)\n        return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request)\n    }\n\n    fun createPingHandle(packageName: String, flow: String, callback: GuardCallback, pingData: PingData?): HandleProxy {\n        if (!DroidGuardPreferences.isLocalAvailable(context)) throw IllegalAccessException(\"DroidGuard should not be available locally\")\n        val (vmKey, byteCode, bytes) = fetchFromServer(flow, createRequest(flow, packageName, pingData))\n        return createHandleProxy(flow, vmKey, byteCode, bytes, callback, DroidGuardResultsRequest().also { it.clientVersion = 0 })\n    }\n\n    fun createLowLatencyHandle(flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy {\n        if (!DroidGuardPreferences.isLocalAvailable(context)) throw IllegalAccessException(\"DroidGuard should not be available locally\")\n        val (vmKey, byteCode, bytes) = readFromDatabase(\"fast\") ?: throw Exception(\"low latency (fast) flow not available\")\n        return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request)\n    }\n\n    fun SignedResponse.unpack(): Response {\n        if (SignatureVerifier.verifySignature(data_!!.toByteArray(), signature!!.toByteArray())) {","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt#L13-L49","documentation":"microG's DroidGuard NetworkHandleProxyFactory.createHandle() refuses to build a local guard handle when DroidGuardPreferences.isLocalAvailable(context) is false. Local DroidGuard execution (bytecode from DB or fetched from server, run on-device) is an opt-in/conditional capability; when it is not available the factory throws IllegalAccessException instead of silently downgrading. The same guard exists in createPingHandle() and createLowLatencyHandle().","triggerScenarios":"Calling createHandle(packageName, flow, callback, request) on a device where DroidGuardPreferences.isLocalAvailable(context) returns false — i.e. local DroidGuard execution is disabled in microG settings or the device/build is not provisioned for local execution.","commonSituations":"User has not enabled 'DroidGuard local execution' in microG settings; a custom ROM or unsupported Build.FINGERPRINT lacks local DroidGuard support; an app integrates the DroidGuard API assuming local handling while the install is remote-only.","solutions":["Enable local DroidGuard execution in the microG settings app (DroidGuard section) and retry","Verify DroidGuardPreferences.isLocalAvailable(context) before calling createHandle and route to a remote handle (RemoteHandleImpl with a configured network server URL) as a fallback","Update microG to a version that supports local DroidGuard on your device fingerprint","Catch IllegalAccessException around handle creation and surface a 'local DroidGuard unavailable' state to the caller"],"exampleFix":"// before\nval handle = factory.createHandle(packageName, flow, callback, request)\n\n// after\nif (DroidGuardPreferences.isLocalAvailable(context)) {\n    val handle = factory.createHandle(packageName, flow, callback, request)\n} else {\n    val handle = RemoteHandleImpl(context, packageName).also { it.init(flow) }\n}","handlingStrategy":"validation","validationCode":"if (!DroidGuardPreferences.isLocalAvailable(context)) {\n    // fall back to remote handle or disable feature\n} else {\n    val handle = factory.createHandle(packageName, flow, callback, request)\n}","typeGuard":null,"tryCatchPattern":"try {\n    factory.createHandle(packageName, flow, callback, request)\n} catch (e: IllegalAccessException) {\n    switchToRemoteHandle(context, packageName, flow)\n}","preventionTips":["Always gate DroidGuard local calls on DroidGuardPreferences.isLocalAvailable(context)","Keep local execution enabled in microG settings for deployments that require it","Design flows with a remote-handle fallback path"],"tags":["android","microg","droidguard","configuration","feature-flag"],"backgroundTag":"feature-not-enabled","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}