Skip to content

APK metadata

Jordan Samhi edited this page Oct 25, 2026 · 2 revisions

APK metadata without Soot

com.jordansamhi.androspecter.metadata.ApkInfo.read(Path) reads the binary AndroidManifest.xml directly from an APK and returns immutable, detached values. It requires no Android SDK, constructs no call graph, and does not initialize, reset, or modify Soot. It can be used before an analysis or alongside a live analysis session without acquiring that session's ownership.

This API targets AndroSpecter 2.0.0 on JDK 17 and uses the org.soot-oss:axml:2.1.4 parser already used by FlowDroid. It does not use FlowDroid's component wrappers, which can apply defaults or access Soot when interpreting attributes. See Getting started for installation.

Run the example

From the AndroSpecter repository root, install the library locally and build the separate example application:

mvn -B install
mvn -B -f examples/pom.xml package dependency:copy-dependencies

APK=/absolute/path/app.apk
java -cp 'examples/target/classes:examples/target/dependency/*' \
  com.jordansamhi.androspecter.examples.PrintApkInfo "$APK"

PrintApkInfo accepts exactly one argument, the APK path. It prints the package, version, declared minimum/target SDK, requested permissions, and components with their declared exported flag, alias target, and provider authorities. It does not need a platforms argument and does not open AndroidAnalysis.

See the example source and examples README. The executable example is separate from the library artifact.

Read metadata from Java

This complete utility class needs only an APK path:

import com.jordansamhi.androspecter.metadata.ApkInfo;
import java.io.IOException;
import java.nio.file.Path;

public final class MetadataExample {
    /**
     * Reads detached manifest declarations without initializing an analysis session.
     * @param apk readable APK file
     * @return immutable metadata that remains usable after the file is closed
     * @throws IOException if the APK or its binary manifest cannot be read
     */
    public static ApkInfo describe(Path apk) throws IOException {
        ApkInfo info = ApkInfo.read(apk);
        System.out.println(info.packageName());
        System.out.println("Requested permissions: " + info.permissions());
        for (ApkInfo.Component component : info.components()) {
            System.out.println(component.kind() + " " + component.name()
                + " declaredExported=" + component.declaredExported());
            for (ApkInfo.IntentFilter filter : component.intentFilters()) {
                System.out.println("  actions=" + filter.actions());
                System.out.println("  categories=" + filter.categories());
                System.out.println("  data declarations=" + filter.data());
            }
        }
        return info;
    }
}

The ZIP and manifest streams are closed before read returns. Results contain no live Soot or parser objects and can be retained across analysis closure, G.reset(), file deletion, or thread boundaries. Missing files, missing manifests, and malformed binary manifests are reported as IOException; a null path is rejected with NullPointerException.

Available fields

Accessor Meaning
packageName() Manifest package name; a missing literal package name causes a read error
versionName() Literal version name, or null
versionCode() Combined versionCodeMajor/versionCode as a Long, or null
minSdkVersion(), targetSdkVersion() Declared numeric SDK values as Integer, or null; no Android defaults are filled in
permissions() Unique literal names requested by all supported uses-permission variants; not granted permissions
usesPermissions() Ordered declarations retaining tag, name, maximum SDK, and raw attributes such as usesPermissionFlags
components() Ordered Activity, Activity alias, Service, Receiver, and Provider declarations
manifestAttributes() Raw typed attributes of the manifest element
applicationAttributes() Raw typed application attributes, empty when no application is declared
sdkAttributes() Raw typed attributes of the first uses-sdk element

uses-permission, uses-permission-sdk-23, and uses-permission-sdk-m declarations are retained separately, including duplicates. permissions() offers a deduplicated projection and omits names that are not literal strings. SDK restrictions and flags are preserved, not evaluated.

Components and intent filters

component.kind() is one of ACTIVITY, ACTIVITY_ALIAS, SERVICE, RECEIVER, or PROVIDER. name() expands .MainActivity and bare MainActivity against the manifest package; a fully qualified name remains unchanged. declaredName() preserves the original literal spelling. For aliases, targetActivity() is the expanded target, while name() identifies the alias itself.

Convenience accessors include declaredExported(), declaredEnabled(), permission(), process(), and the provider attributes authorities(), readPermission(), writePermission(), and grantUriPermissions(). Authorities remain the declared string, including semicolon separators. These values describe the component's own attributes: application permissions, enabled state, provider permission fallbacks, and defaults are not inherited or calculated.

Each intentFilters() entry contains literal action and category names, separate raw attribute maps for each direct <data> child, and the filter's own attributes such as priority and autoVerify. Resource-backed action/category names are omitted from the convenience lists. Data maps preserve declared scheme, host, path, MIME, and other attributes; they do not calculate Android's intent-matching combinations. This API does not evaluate intent resolution or synthesize inherited declarations.

Missing, unresolved, and exported values

A nullable convenience value can mean an attribute is absent, is a resource reference, or has an unsupported value. Check the corresponding raw attribute map to distinguish these cases. SDK codenames remain available in sdkAttributes() even when the numeric SDK accessor returns null.

Raw maps use android:name keys for the Android namespace, plain names for unqualified attributes, and {namespace-uri}name for other namespaces. Each AttributeValue contains the binary XML type() and scalar value(). Strings remain literal, booleans use true/false, integer values are decimal, and unresolved resource references use forms such as @0x7f010001. Unnamed attributes retain a #0xRESOURCEID key rather than triggering Soot lookup. The reader does not resolve resources.arsc.

For example, component.declaredExported() can be:

  • true or false when a supported literal declaration is present;
  • null with no android:exported key when the declaration is missing;
  • null with an android:exported entry when the value is unresolved or unsupported.

Declared exported is not effective Android exposure. The reader does not infer exported values from intent filters, target SDK, component type, permissions, or installation rules. It also does not evaluate runtime permission grants, validate APK signatures, merge split APKs, or establish that a component is reachable or exploitable.

Clone this wiki locally