MuntashirAkon/AppManager · error · PermissionException

Legacy app cannot have not-granted runtime permission ${perm

Error message

Legacy app cannot have not-granted runtime permission ${permissionName}

What it means

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.

Source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/permission/PermissionMutation.java:154

                    // Disabling an AppOp may leave a legacy app holding state it should no longer
                    // access. Restarting it matches the existing runtime-permission behaviour.
                    killApp = true;
                }

                // Mark that the permission remains granted only for compatibility.
                if (!permission.isRevokedCompat()) {
                    permission.setRevokedCompat(true);
                }
            }
        }
        return killApp;
    }

    private static void ensureLegacyRuntimePermissionIsGranted(@NonNull Permission permission)
            throws PermissionException {
        if (permission.isRuntime() && !permission.isGranted()) {
            throw new PermissionException("Legacy app cannot have not-granted runtime permission "
                    + permission.getName());
        }
    }

    private static boolean supportsRuntimePermissions(@NonNull PackageInfo packageInfo) {
        return packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    }
}

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Re-check how the Permission object was constructed; ensure granted state is read from the platform for the correct user before mutation
  2. Skip mutation of runtime permissions for apps with targetSdkVersion <= 22, since legacy apps grant at install time
  3. If the state is genuinely inconsistent, reinstall or clear/re-grant the permission via system settings so the platform state matches
  4. Update the exported rules file so legacy apps only reference install-time permissions

Example fix

// before
permissionController.prepareGrant(permission);
// after
if (packageInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
    permissionController.prepareGrant(permission);
}
Defensive patterns

Strategy: validation

Validate before calling

if (permission.isRuntime() && !permission.isGranted()
        && packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
    throw new IllegalStateException("Legacy app with ungranted runtime permission: " + permission.getName());
}

Type guard

boolean isLegacyApp(PackageInfo info) {
    return info.applicationInfo == null
            || info.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
}

Try / catch

try {
    permissionController.prepareGrant(permission);
} catch (PermissionException e) {
    Log.w(TAG, "Skipping permission for legacy app", e);
}

Prevention

When it happens

Trigger: Calling prepareGrant or prepareRevoke on a PackageInfo whose applicationInfo.targetSdkVersion <= LOLLIPOP_MR1 while the Permission object reports isRuntime() == true and isGranted() == false.

Common situations: 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.

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 MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/1597c8600d176eba. Report an issue: GitHub.