Skip to main content

apk_inspector 0.1.0 copy "apk_inspector: ^0.1.0" to clipboard
apk_inspector: ^0.1.0 copied to clipboard

PlatformAndroid

Inspect installed Android apps and APK files - signing certificates, permissions, exported components, flags and installer - with a permission catalog.

apk_inspector #

Inspect installed Android apps and APK files on disk: signing certificates (with key rotation), requested permissions with grant state and protection level, exported components, manifest flags and the installer. Comes with a plain-language permission catalog and a transparent risk score.

pub package License: MIT

Android only; iOS not supported. On other platforms every call throws ApkInspectorException with ApkInspectorErrorCode.unavailable.

Features #

  • Listing: installedApps(), installedAppsPage() and installedAppsStream() return lightweight AppSummary objects, sorted by package name and fetched in pages so large devices do not block the UI or hit binder limits.
  • Icons: PNG bytes drawn from the app's Drawable at the size you ask for, including adaptive icons (drawn with the device mask). Load them lazily with iconOf(packageName), or inline with includeIcons: true.
  • appDetails(packageName) returns AppDetails:
    • version name, long version code, min/target/compile SDK;
    • first install and last update time;
    • installer, initiating package and update owner through getInstallSourceInfo on API 30+, getInstallerPackageName below;
    • flags: system, updatedSystem, debuggable, testOnly, allowBackup, usesCleartextTraffic, largeHeap, hasCode;
    • sharedUserId, dataDir, sourceDir, split APK paths, total APK size;
    • requested permissions with granted state and protection level;
    • activities, services, receivers and providers with exported, permission and effective enabled state;
    • signing: GET_SIGNING_CERTIFICATES on API 28+ (current signers, past signers from key rotation, multiple signers) and GET_SIGNATURES below. Each certificate reports SHA-256, SHA-1, subject, issuer, validity, serial, signature and key algorithm.
  • inspectApkFile(path): the same details for an APK on disk through getPackageArchiveInfo, with sourceDir/publicSourceDir set so the label and icon resolve. It also reports which signature schemes (v1, v2, v3, v3.1) the file carries, read from the ZIP central directory and the APK Signing Block.
  • Pure-Dart helpers: PermissionCatalog, RiskScore, CertificateFingerprint, ApkSignatureSchemes.
  • The plugin manifest declares nothing: no permissions, no <queries>.

Install #

dependencies:
  apk_inspector: ^0.1.0

The plugin's minSdk is 21.

Package visibility (Android 11+) #

From Android 11 (API 30), an app only sees the packages it declares. Without a declaration, installedApps() returns little more than system packages and your own app, and appDetails() fails with notFound for anything else. This plugin declares nothing on purpose. Your app chooses one of:

  • Narrow <queries> (preferred): list the packages or intents you need.

    <manifest ...>
        <queries>
            <package android:name="com.example.partner" />
            <intent>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent>
        </queries>
    </manifest>
    
  • QUERY_ALL_PACKAGES: sees everything.

    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
    

    Google Play restricts this permission. It is allowed only when seeing all installed apps is part of the app's core purpose (for example device search, antivirus or app management tools), and you must justify it in the Play Console permissions declaration. Apps that do not qualify are rejected or removed. Use <queries> if you can. The example app declares QUERY_ALL_PACKAGES because it is an app inspector.

inspectApkFile() needs no visibility declaration, but your app must be able to read the file path (for example a file in your app's own storage).

Usage #

import 'package:apk_inspector/apk_inspector.dart';

final inspector = ApkInspector();

Future<void> listApps() async {
  // Streams page by page; icons are loaded separately.
  await for (final app in inspector.installedAppsStream(pageSize: 50)) {
    print('${app.label} ${app.packageName} ${app.versionName}');
  }
  final icon = await inspector.iconOf('com.android.chrome', size: 96);
  print('icon bytes: ${icon?.length}');
}

Future<void> details(String packageName) async {
  try {
    final d = await inspector.appDetails(packageName);
    print('target ${d.targetSdk}, installer ${d.installSource?.installer}');
    print('debuggable ${d.flags.debuggable}');
    for (final cert in d.signing?.currentSigners ?? const <CertificateInfo>[]) {
      print('${cert.subject} ${cert.formattedSha256}');
    }
    for (final p in d.permissions) {
      final entry = PermissionCatalog.describe(p.name,
          protectionLevel: p.protectionLevel?.raw);
      print('${p.name} granted=${p.granted} ${p.risk.name}: '
          '${entry.description}');
    }
    for (final c in d.exportedComponents.where((c) => !c.isProtected)) {
      print('open ${c.kind.name}: ${c.name}');
    }
    final risk = d.riskScore;
    print('risk ${risk.score}/100');
    risk.factors.forEach(print);
  } on ApkInspectorException catch (e) {
    if (e.code == ApkInspectorErrorCode.notFound) {
      print('Not installed or not visible to this app.');
    }
  }
}

Future<void> apkFile(String path) async {
  final d = await inspector.inspectApkFile(path);
  print('${d.label} ${d.versionName} schemes: ${d.signingSchemes}');
  const pinned =
      '6973d591ad0002d66bcc0d4a30b080bbaec4627dbeee67e59d9c91378176723f';
  final ok = d.signing?.currentSigners
          .any((c) => CertificateFingerprint.matches(c.sha256, pinned)) ??
      false;
  print('signed by the expected key: $ok');
}

Permission risk buckets #

PermissionRisk.of(level) looks only at the protection level:

Protection level Bucket
normal (any flags) low
dangerous high
signature, signatureOrSystem or internal with appop specialAccess
signature, signatureOrSystem or internal without appop systemOnly
unknown / not reported unknown

RequestedPermission.risk uses the level the device reports, falling back to the AOSP level in PermissionCatalog when the device reports none.

Risk score formula #

RiskScore.of(details) (or details.riskScore) is a heuristic for sorting and triage, not a malware verdict. Every point shows up in RiskReport.factors. The score is the sum below, clamped to 0..100:

Factor Points
Each requested permission: high 6, specialAccess 10, systemOnly 2, unknown 1, low 0 capped at 50
debuggable 15
testOnly 5
usesCleartextTraffic 5
allowBackup 3
targetSdk below 23 (runtime permissions granted at install) 10
Each exported component without a permission (providers need both read and write permissions) 1, capped at 15
sharedUserId set 2

The factors add up to at most 105 before clamping. Requested permissions count whether or not they are granted, so APK files and installed apps score the same way.

Privacy #

The list of installed apps is personal data: it can reveal health conditions, religion, finances or dating habits. This plugin reads package data only on the device and sends nothing anywhere. If your app uploads anything it collects with this plugin, tell users, get consent where the law requires it, and complete the Google Play Data safety form. Google Play treats the installed-apps list as sensitive, and QUERY_ALL_PACKAGES is itself subject to the policy above.

Limitations #

  • Package visibility applies: without <queries> or QUERY_ALL_PACKAGES, Android 11+ hides most packages.
  • exported is the value PackageManager resolved. The plugin does not parse the binary manifest, so it cannot tell an explicit android:exported from a default, and intent filters are not reported.
  • A permission defined by a package your app cannot see has no protection level on the device; the catalog fallback covers platform permissions only.
  • originatingPackage is only revealed to callers with INSTALL_PACKAGES.
  • minSdk needs Android 7.0+ and compileSdk needs Android 12+ on the device running the plugin.
  • For APK files, grant states, install times, installer and dataDir are null. Rotation history for APK files depends on the device's parser.
  • Signature scheme detection reports which schemes are present. It does not verify signatures (PackageManager's certificate data is what the plugin reports for that). ZIP64 archives and v4 .idsig files are not handled.
  • usesCleartextTraffic is the manifest flag; a network security config can refine it per domain.
  • The example app has an emulator integration test (example/integration_test/, run with dart run tool/run_on_device.dart <applicationId> from example/). It compares the installed-app list with pm list packages, and this app's details and the copied debug APK with apksigner verify --print-certs and aapt2 dump badging run on the host against the installed APK. It also checks com.android.settings and the icons.

Tested on: Android Emulator, Pixel 10 system image (sdk_gphone16k_x86_64), API 37. Not yet tested on a physical phone, OEM system apps or split APKs.

Data sources #

  • Protection levels for the 1016 permissions in PermissionCatalog are generated by tool/generate_permission_levels.dart from the AOSP platform manifest frameworks/base/core/res/AndroidManifest.xml, commit 1cdfff555f4a21f71ccc978290e2e212e2f8b168 (https://android.googlesource.com/platform/frameworks/base/+/1cdfff555f4a21f71ccc978290e2e212e2f8b168/core/res/AndroidManifest.xml). Copyright The Android Open Source Project, Apache License 2.0.
  • Protection flag names and values in the Kotlin layer come from the protectionLevel attribute in AOSP frameworks/base/core/res/res/values/attrs_manifest.xml at the same commit (Apache License 2.0).
  • APK Signing Block IDs (v2 0x7109871a, v3 0xf05368c0, v3.1 0x1b93ad61) are the constants in AOSP platform/tools/apksig (V2SchemeConstants.java, V3SchemeConstants.java, commit 184702d9d18877edf9e5296c4e191cf0aa2b5fbb, Apache License 2.0). The block layout follows https://source.android.com/docs/security/features/apksigning/v2.
  • The permission descriptions and groups were written for this package.

License #

MIT. See LICENSE. The generated protection-level table carries the Apache-2.0 notice of its AOSP source in its file header.

Made by Abdeldjalil Chougui.

0
likes
160
points
390
downloads

Documentation

API reference

Publisher

verified publisherabdeldjalilchougui.biz

Weekly Downloads

Inspect installed Android apps and APK files - signing certificates, permissions, exported components, flags and installer - with a permission catalog.

Homepage
Repository (GitHub)
View/report issues

Topics

#android #apk #permissions #security #plugin

License

MIT (license)

Dependencies

flutter

More

Packages that depend on apk_inspector

Packages that implement apk_inspector