{"record":{"id":"1597c8600d176eba","repo":"MuntashirAkon/AppManager","slug":"legacy-app-cannot-have-not-granted-runtime-permission","errorCode":null,"errorMessage":"Legacy app cannot have not-granted runtime permission ${permissionName}","messagePattern":"Legacy app cannot have not-granted runtime permission (.+?)","errorType":"exception","errorClass":"PermissionException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/permission/PermissionMutation.java","lineNumber":154,"sourceCode":"\n                    // Disabling an AppOp may leave a legacy app holding state it should no longer\n                    // access. Restarting it matches the existing runtime-permission behaviour.\n                    killApp = true;\n                }\n\n                // Mark that the permission remains granted only for compatibility.\n                if (!permission.isRevokedCompat()) {\n                    permission.setRevokedCompat(true);\n                }\n            }\n        }\n        return killApp;\n    }\n\n    private static void ensureLegacyRuntimePermissionIsGranted(@NonNull Permission permission)\n            throws PermissionException {\n        if (permission.isRuntime() && !permission.isGranted()) {\n            throw new PermissionException(\"Legacy app cannot have not-granted runtime permission \"\n                    + permission.getName());\n        }\n    }\n\n    private static boolean supportsRuntimePermissions(@NonNull PackageInfo packageInfo) {\n        return packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;\n    }\n}\n","sourceCodeStart":136,"sourceCodeEnd":163,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/permission/PermissionMutation.java#L136-L163","documentation":"App Manager's permission module enforces an invariant for legacy apps (targetSdk <= 22): such apps do not use Android runtime permissions, so a runtime permission on a legacy app must always be marked granted. When prepareGrant or prepareRevoke encounters a runtime permission that is not granted on a legacy app, it throws PermissionException because the package state contradicts what the platform allows.","triggerScenarios":"Calling prepareGrant or prepareRevoke on a PackageInfo whose applicationInfo.targetSdkVersion <= LOLLIPOP_MR1 while the Permission object reports isRuntime() == true and isGranted() == false.","commonSituations":"Importing or mutating permission rules exported from a newer-device backup and applying them to a legacy (targetSdk 21-22) app; corrupted or hand-edited permission dumps; device-odexed data restored from a different Android version.","solutions":["Re-check how the Permission object was constructed; ensure granted state is read from the platform for the correct user before mutation","Skip mutation of runtime permissions for apps with targetSdkVersion <= 22, since legacy apps grant at install time","If the state is genuinely inconsistent, reinstall or clear/re-grant the permission via system settings so the platform state matches","Update the exported rules file so legacy apps only reference install-time permissions"],"exampleFix":"// before\npermissionController.prepareGrant(permission);\n// after\nif (packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {\n    permissionController.prepareGrant(permission);\n}","handlingStrategy":"validation","validationCode":"if (permission.isRuntime() && !permission.isGranted()\n        && packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {\n    throw new IllegalStateException(\"Legacy app with ungranted runtime permission: \" + permission.getName());\n}","typeGuard":"boolean isLegacyApp(PackageInfo info) {\n    return info.applicationInfo == null\n            || info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;\n}","tryCatchPattern":"try {\n    permissionController.prepareGrant(permission);\n} catch (PermissionException e) {\n    Log.w(TAG, \"Skipping permission for legacy app\", e);\n}","preventionTips":["Check targetSdkVersion before mutating runtime permissions","Refresh permission granted state from the platform immediately before mutation","Exclude legacy apps from runtime-permission rule imports"],"tags":["android","permissions","legacy-apps","state-validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}