NAME

Net::ADB::Device - Extract information from an Android device

SYNOPSIS

use Net::ADB;

my $adb    = Net::ADB->new;
my $device = $adb->device(serial => '192.168.0.10:5555');

my $info = $device->info;

say "$info->{manufacturer} $info->{model}";    # Example Example Phone
say "Android $info->{android}";                # Android 13

my $memory = $device->memory;

printf "%d MB available of %d MB\n", $memory->{available} / 2**20, $memory->{total} / 2**20;

say $device->getprop->{'persist.sys.timezone'};    # Europe/Rome

my $res = $device->shell('ls /sdcard');
print $res->stdout if $res->is_success;

DESCRIPTION

Net::ADB::Device extracts information from an Android device by running shell commands through Net::ADB and parsing their output.

There are two kinds of methods:

  • Methods named after a source (e.g. "getprop", "meminfo", "mounts") return its content as is, with the key names used by Android or by the Linux kernel.

  • Methods named after an area (e.g. "info", "memory") return a summary with fixed key names, built from those sources.

The sources vary across Android versions and vendors: a summary value whose source is missing on the device is undef.

Sizes are in bytes.

NOTE: The API is experimental and may change.

CONSTRUCTOR

new

my $device = $adb->device(serial => '192.168.0.10:5555');

Create a new Net::ADB::Device instance. Use "device" in Net::ADB instead of calling new directly: it resolves the device selection to a serial.

Allowed parameters:

  • client, Net::ADB instance (required).

  • serial, device serial (required).

METHODS

Device

serial

say $device->serial;    # 192.168.0.10:5555

Return the serial of the device (the one used by the ADB server, not ro.serialno).

client

my $port = $device->client->forward(serial => $device->serial, local => 'tcp:0', remote => 'tcp:8080');

Return the Net::ADB instance used by the device.

state

say $device->state;    # device

Return the state of the device. See "state" in Net::ADB.

features

my @features = $device->features;
my $features = $device->features;    # array reference

Return the list of features supported by the device (an array reference in scalar context). See "features" in Net::ADB.

shell

my $res = $device->shell('ls /sdcard');
my $res = $device->shell('logcat -d', timeout => 60);

Run a command on the device and return a Net::ADB::Shell::Result instance. See "shell_v2" in Net::ADB.

Allowed parameters:

  • timeout, timeout in seconds for this call.

System

getprop

my $props = $device->getprop;

say $props->{'ro.build.version.release'};    # 13

# Read the properties again from the device
$props = $device->getprop(refresh => 1);

Return a hash reference with all the system properties of the device (getprop).

The properties are read once and cached in the instance: use refresh to read them again, e.g. for properties that change at runtime. The returned hash is a copy of the cache.

Allowed parameters:

  • refresh, read the properties from the device again (default: 0).

settings

my $global = $device->settings('global');

say $global->{adb_enabled};    # 1

Return a hash reference with all the Android settings of a namespace (settings list): global, secure or system.

The settings are read every time (no cache), and the values are returned as is: a setting stored without a value is the string null.

info

my $info = $device->info;

Return a hash reference with a summary of the device, built from the system properties (see "getprop"):

{
    manufacturer   => 'Example',
    brand          => 'Example',
    model          => 'Example Phone',
    marketname     => undef,
    device         => 'example',
    android        => '13',
    sdk            => 33,
    security_patch => '2024-05-01',
    build_id       => 'TP1A.220624.014',
    build_type     => 'user',
    build_date     => '2024-05-20T09:12:44Z',
    fingerprint    => 'Example/example/example:13/TP1A.220624.014/1234567:user/release-keys',
    abi            => 'arm64-v8a',
    abilist        => ['arm64-v8a', 'armeabi-v7a', 'armeabi'],
    platform       => 'mt6789',
    hardware       => 'mt6789',
    serial         => 'EX0123456789',
    emulator       => 0
}
  • manufacturer, brand, model, device: looked up in ro.product.*, ro.product.vendor.*, ro.product.system.* and ro.product.odm.*, because these properties moved between partitions over Android versions.

  • marketname: commercial name, when set by the vendor.

  • android, sdk: Android version and API level.

  • security_patch: security patch level.

  • build_id, build_type, fingerprint: build information.

  • build_date: build date in ISO 8601 format (UTC).

  • abi, abilist: primary and supported ABIs.

  • platform, hardware: SoC and hardware names.

  • serial: hardware serial number (ro.serialno). It may differ from "serial", e.g. for TCP/IP devices.

  • emulator: 1 if ro.boot.qemu or ro.kernel.qemu is set (Android emulator), 0 otherwise.

dumpsys

my $battery = $device->dumpsys('battery');
my $package = $device->dumpsys('package', 'com.example.app');

Run dumpsys for a system service, with optional arguments, and return its output as a string. Croaks if the service does not exist.

The output format is specific to each service and changes across Android versions and vendors.

dumpsys_services

my @services = $device->dumpsys_services;
my $services = $device->dumpsys_services;    # array reference

Return the list of system services that can be passed to "dumpsys" (e.g. activity, battery, package), or an array reference in scalar context (dumpsys -l).

uptime

my $uptime = $device->uptime;

printf "up %.1f hours\n", $uptime->{uptime} / 3600;

Return a hash reference with the content of /proc/uptime, in seconds:

{
    uptime => 40360.45,
    idle   => 201485.9
}
  • uptime: time since boot.

  • idle: time spent idle, summed over all CPUs (so it can be greater than uptime).

kernel

my $kernel = $device->kernel;

Return a hash reference with the Linux kernel version, from /proc/version:

{
    version    => '5.10.43',
    release    => '5.10.43-android12-9-00001',
    build_date => 'Fri Jul 9 10:53:24 UTC 2021',
    banner     => 'Linux version 5.10.43-android12-9-00001 (build-user@build-host) (...) #1 SMP PREEMPT Fri Jul 9 10:53:24 UTC 2021'
}
  • version: numeric version, useful to compare against vulnerable version ranges.

  • release: full release string, as reported by uname -r.

  • build_date: build date as written by the kernel (the time zone may be missing). An old kernel build date on a device with a recent security patch level may mean that the kernel did not receive the patches.

  • banner: the whole content of /proc/version.

Values that cannot be parsed are undef.

Security

security

my $security = $device->security;

warn 'Bootloader is unlocked' if defined $security->{bootloader_locked} && !$security->{bootloader_locked};

Return a hash reference with a summary of the security settings of the device:

{
    security_patch    => '2024-05-01',
    verified_boot     => 'green',
    bootloader_locked => 1,
    debuggable        => 0,
    secure            => 1,
    adb_secure        => 1,
    build_type        => 'user',
    build_tags        => 'release-keys',
    encryption        => 'encrypted',
    encryption_type   => 'file',
    selinux           => 'enforcing',
    adb_enabled       => 1,
    adb_wifi_enabled  => 0,
    developer_options => 1,
    adb_tcp_port      => undef,
    unknown_sources   => undef,
    private_dns       => 'hostname',
    private_dns_host  => 'dns.example.com',
    always_on_vpn     => undef
}

Yes/no values are 1 or 0. Values that are not available on the device are undef, which is not the same as 0: e.g. bootloader_locked is undef when the device does not report the bootloader state.

  • security_patch: security patch level (ro.build.version.security_patch).

  • verified_boot: Verified Boot state (ro.boot.verifiedbootstate): green (locked, verified), yellow (locked, custom key), orange (unlocked) or red (verification failed).

  • bootloader_locked: bootloader lock state (ro.boot.flash.locked, or ro.boot.vbmeta.device_state).

  • debuggable: debuggable build (ro.debuggable), e.g. eng and userdebug builds. Production devices are 0.

  • secure: secure mode (ro.secure), 0 means that adbd runs as root.

  • adb_secure: ADB requires authorization of the computer (ro.adb.secure).

  • build_type, build_tags: build type (user, userdebug, eng) and signing keys (release-keys, or test-keys for builds signed with public test keys).

  • encryption, encryption_type: storage encryption state (ro.crypto.state) and type (ro.crypto.type, file or block).

  • selinux: SELinux mode (see "selinux").

  • adb_enabled, adb_wifi_enabled, developer_options: USB debugging, wireless debugging and developer options (see "settings").

  • adb_tcp_port: port of ADB over TCP/IP (adb tcpip), undef when it is disabled (service.adb.tcp.port, persist.adb.tcp.port).

  • unknown_sources: installation from unknown sources allowed (install_non_market_apps). Only before Android 8.0, undef on later versions, where the permission is per app (see "install_sources").

  • private_dns, private_dns_host: Private DNS (DNS over TLS) mode (off, opportunistic or hostname) and host.

  • always_on_vpn: package of the always-on VPN.

The properties come from the "getprop" cache: use getprop(refresh => 1) to read them again.

privileged_apps

my $apps = $device->privileged_apps;

say for @{$apps->{accessibility_services}};

Return a hash reference with the apps granted powerful special permissions, often abused by malware and stalkerware, as lists of package/class components:

{
    accessibility_services => ['com.example.app/com.example.app.AccessibilityService'],
    notification_listeners => ['com.example.watch/com.example.watch.NotificationListener']
}
  • accessibility_services: enabled accessibility services, which can read the screen and act on behalf of the user.

  • notification_listeners: apps that can read all notifications, including one-time codes.

install_sources

my @packages = $device->install_sources;
my $packages = $device->install_sources;    # array reference

Return the list of packages allowed to install other apps ("Install unknown apps"), or an array reference in scalar context (appops query-op REQUEST_INSTALL_PACKAGES allow).

Requires Android 8.0 or later: on older versions, see unknown_sources in "security".

selinux

say $device->selinux;    # enforcing

Return the SELinux mode of the device in lowercase: enforcing, permissive or disabled (getenforce).

Network

connections

for my $socket ($device->connections) {
    next unless $socket->{state} eq 'ESTABLISHED';
    say "$socket->{local_port} -> $socket->{remote_address}:$socket->{remote_port}";
}

my $sockets = $device->connections;    # array reference

Return the list of TCP and UDP sockets (an array reference in scalar context), one hash reference per socket, from /proc/net/tcp, tcp6, udp and udp6:

{
    protocol       => 'tcp',
    family         => 6,
    state          => 'ESTABLISHED',
    local_address  => '::ffff:192.168.0.10',
    local_port     => 49166,
    remote_address => '::ffff:203.0.113.5',
    remote_port    => 443,
    uid            => 10245,
    inode          => 4670123
}
  • protocol: tcp or udp.

  • family: 4 for IPv4, 6 for IPv6. IPv4 connections of IPv6 sockets have IPv4-mapped addresses (::ffff:192.168.0.10).

  • state: TCP state (LISTEN, ESTABLISHED, TIME_WAIT, CLOSE_WAIT, ...). UDP sockets are ESTABLISHED when connected to a remote address and CLOSE otherwise.

  • local_address, local_port: local address and port. 0.0.0.0 and :: accept connections on every interface, 127.0.0.1 and ::1 only from the device itself.

  • remote_address, remote_port: remote address and port (0.0.0.0 and 0 when not connected).

  • uid: Linux user ID of the owner: match it with the uid of "packages" to find the app (0 is root, 1000 is system). Several packages can share the same UID.

  • inode: socket inode.

listening

for my $socket ($device->listening) {
    say "$socket->{protocol} $socket->{local_address}:$socket->{local_port} uid=$socket->{uid}";
}

my $sockets = $device->listening;    # array reference

Return the sockets waiting for connections (an array reference in scalar context): TCP sockets in the LISTEN state and UDP sockets bound to a port and not connected. The sockets have the same fields as "connections".

my %apps = map { $_->{uid} => $_->{name} } $device->packages;

for my $socket ($device->listening) {
    say "$socket->{local_port} ", $apps{$socket->{uid}} // "uid $socket->{uid}";
}

Packages

packages

for my $package ($device->packages(third_party => 1)) {
    say "$package->{name} $package->{version_code}";
}

my $packages = $device->packages;    # array reference

Return the list of installed packages (an array reference in scalar context), one hash reference per package:

{
    name         => 'com.example.app',
    path         => '/data/app/~~4Xc2bT1fQkZ9GmLwYp3aRA==/com.example.app-Hn5pVz0sJmQ8yXe2LkTr4w==/base.apk',
    version_code => 1020300,
    installer    => 'com.android.vending',
    uid          => 10245
}
  • name: package name.

  • path: path of the APK on the device.

  • version_code: version code, an integer that increases with each release (not the version name shown to users, e.g. 1.2.3).

  • installer: package that installed it (e.g. com.android.vending for Google Play), or undef when the system has not recorded one (e.g. preinstalled packages).

  • uid: Linux user ID of the package.

Requires Android 9.0 or later (pm list packages --show-versioncode): on older versions the list is empty.

Allowed parameters:

  • third_party, only third-party (non-system) packages (pm list packages -3).

  • system, only system packages (pm list packages -s).

package_info

my $info = $device->package_info('com.example.app');

say "$info->{name} $info->{version_name} (target SDK $info->{target_sdk})";

# Dangerous permissions granted by the user
my $permissions = $info->{permissions};

for my $name (sort keys %{$permissions}) {
    my $permission = $permissions->{$name};
    say $name if ($permission->{type} // '') eq 'runtime' && $permission->{granted};
}

Return a hash reference with the details of an installed package, parsed from dumpsys package NAME. Croak if the package is unknown to the device.

{
    name                 => 'com.example.app',
    uid                  => 10245,
    shared_user          => undef,
    version_name         => '1.2.3',
    version_code         => 1020300,
    min_sdk              => 26,
    target_sdk           => 34,
    installer            => 'com.android.vending',
    first_install        => '2024-03-01 10:20:30',
    last_update          => '2024-06-15 18:05:12',
    timestamp            => '2024-06-15 18:04:58',
    path                 => '/data/app/~~4Xc2bT1fQkZ9GmLwYp3aRA==/com.example.app-Hn5pVz0sJmQ8yXe2LkTr4w==',
    data_dir             => '/data/user/0/com.example.app',
    splits               => ['base', 'config.arm64_v8a'],
    primary_abi          => 'arm64-v8a',
    secondary_abi        => undef,
    flags                => ['HAS_CODE', 'ALLOW_CLEAR_USER_DATA', 'ALLOW_BACKUP'],
    private_flags        => ['PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION'],
    system               => 0,
    updated_system       => 0,
    privileged           => 0,
    debuggable           => 0,
    allow_backup         => 1,
    persistent           => 0,
    legacy_storage       => 0,
    installed            => 1,
    hidden               => 0,
    suspended            => 0,
    stopped              => 0,
    not_launched         => 0,
    enabled              => 0,
    enabled_state        => 'default',
    permissions          => {
        'android.permission.INTERNET' => {
            requested  => 1,
            restricted => 0,
            type       => 'install',
            granted    => 1,
            flags      => []
        },
        'android.permission.CAMERA' => {
            requested  => 1,
            restricted => 0,
            type       => 'runtime',
            granted    => 1,
            flags      => ['USER_SET', 'USER_SENSITIVE_WHEN_GRANTED', 'USER_SENSITIVE_WHEN_DENIED']
        },
        'android.permission.READ_CONTACTS' => {
            requested  => 1,
            restricted => 0,
            type       => 'runtime',
            granted    => 0,
            flags      => ['USER_SENSITIVE_WHEN_GRANTED', 'USER_SENSITIVE_WHEN_DENIED']
        },
        'android.permission.WRITE_SECURE_SETTINGS' => {
            requested  => 1,
            restricted => 0,
            type       => undef,
            granted    => 0,
            flags      => []
        },
    },
    declared_permissions => {
        'com.example.app.permission.C2D_MESSAGE' => {
            protection => ['signature'],
            installed  => 1
        },
    },
}

Yes/no values are 1 or 0. Values that are not available on the device are undef. The per-user values are those of user 0.

  • name: package name.

  • uid: Linux user ID of the package (the same as in "connections").

  • shared_user: name of the shared user ID (android:sharedUserId), or undef. Packages with the same shared user run with the same uid and share its permissions.

  • version_name, version_code: version shown to users and version code.

  • min_sdk, target_sdk: minimum and target API level. Apps that target API level 22 or lower get the dangerous permissions at install time, without asking the user.

  • installer: package that installed it (e.g. com.android.vending for Google Play), or undef when the system has not recorded one (e.g. preinstalled packages or adb install).

  • first_install, last_update, timestamp: installation time, last update time and modification time of the APK, as YYYY-MM-DD HH:MM:SS in the device local time zone. Preinstalled packages often report a placeholder date (e.g. 1970-01-01 or 2009-01-01).

  • path, data_dir: directory of the APK files and data directory.

  • splits: names of the installed APK splits (base plus, for app bundles, configuration splits).

  • primary_abi, secondary_abi: ABIs used to load the native libraries of the app, undef for apps without native code.

  • flags, private_flags: application flags as reported by the system.

  • system: system app (SYSTEM flag).

  • updated_system: system app updated after the factory version (UPDATED_SYSTEM_APP).

  • privileged: privileged system app, which can be granted signature|privileged permissions (PRIVILEGED).

  • debuggable: debuggable app (DEBUGGABLE): its data can be accessed with run-as.

  • allow_backup: app data can be backed up (ALLOW_BACKUP).

  • persistent: app kept always running (PERSISTENT).

  • legacy_storage: app opted out of scoped storage (PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE).

  • installed, hidden, suspended, stopped, not_launched: state for the user. installed is 0 for a system app uninstalled for the user, stopped is 1 for an app never started or force-stopped.

  • enabled, enabled_state: enabled setting for the user, as number and name: 0 default, 1 enabled, 2 disabled, 3 disabled_user (disabled by the user), 4 disabled_until_used. default means the state declared in the manifest, which is not always enabled.

  • permissions: permissions requested by the package or granted to its uid, keyed by name:

    • requested: 1 if the package requests it in its manifest. It is 0 for permissions granted to the uid through another package with the same shared_user.

    • restricted: 1 for a hard-restricted permission (e.g. SMS, call log), which needs an exemption by the installer.

    • type: install (normal and signature permissions, granted at install time), runtime (dangerous permissions, granted by the user) or undef (requested but never granted, e.g. a signature permission of another app).

    • granted: 1 if granted.

    • flags: permission flags for runtime permissions (e.g. USER_SET, USER_FIXED, POLICY_FIXED, GRANTED_BY_DEFAULT).

  • declared_permissions: permissions defined by the package, keyed by name, with protection (protection levels, e.g. ['signature', 'privileged']) and installed.

Storage

df

for my $fs ($device->df) {
    say "$fs->{mount} $fs->{available}";
}

my $df = $device->df;    # array reference

Return the list of mounted filesystems with their size (an array reference in scalar context), one hash reference per filesystem (df -k):

{
    filesystem  => '/dev/block/by-name/userdata',
    mount       => '/data',
    size        => 118111600640,
    used        => 53687091200,
    available   => 64424509440,
    use_percent => 46
}

Sizes are in bytes. use_percent is the Use% column of df, rounded up.

Filesystems that df cannot read are skipped. Croaks if none can be read.

Requires Android 6.0 or later (toybox df).

storage

my $storage = $device->storage;

printf "%.1f GB free\n", $storage->{available} / 2**30;

Return a hash reference with the size of the user data partition (/data), in bytes (see "df"):

{
    size      => 118111600640,
    used      => 53687091200,
    available => 64424509440
}

The values are undef if /data is not listed by df.

mounts

my ($data) = grep { $_->{mount} eq '/data' } $device->mounts;

say 'nosuid' if $data->{options}{nosuid};

Return the list of mounted filesystems (an array reference in scalar context), one hash reference per mount point (/proc/mounts):

{
    device  => '/dev/block/by-name/userdata',
    mount   => '/data',
    type    => 'f2fs',
    options => {rw => 1, nosuid => 1, nodev => 1, noatime => 1, background_gc => 'on'}
}

options is a hash reference: options without a value (e.g. ro, nosuid) are 1, the others have their value (e.g. size).

Memory

meminfo

my $meminfo = $device->meminfo;

say $meminfo->{MemTotal};    # 3917221888

Return a hash reference with the content of /proc/meminfo, with the kernel key names (MemTotal, MemAvailable, Cached, SwapTotal, ...). Values in kB are converted to bytes.

memory

my $memory = $device->memory;

Return a hash reference with a summary of the memory, in bytes (see "meminfo"):

{
    total      => 3917221888,
    available  => 1671278592,
    free       => 219500544,
    swap_total => 2147479552,
    swap_free  => 1515728896
}

available (MemAvailable) estimates the memory available to start new applications without swapping. free (MemFree) is the memory not used at all, which is usually low because the kernel uses free memory for caches.

CPU

cpuinfo

my $cpuinfo = $device->cpuinfo;

say scalar @{$cpuinfo->{processors}};    # 8

Return a hash reference with the content of /proc/cpuinfo: processors is the list of per-processor fields, and common holds the fields shared by all processors.

{
    processors => [
        {
            'processor'        => '0',
            'BogoMIPS'         => '26.00',
            'Features'         => 'fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp',
            'CPU implementer'  => '0x41',
            'CPU architecture' => '8',
            'CPU variant'      => '0x2',
            'CPU part'         => '0xd05',
            'CPU revision'     => '0'
        },
        ...
    ],
    common => {}
}

The fields depend on the architecture and on the kernel. common is often empty on 64-bit ARM, and may contain Hardware on 32-bit ARM or, on older kernels, Features and CPU part.

loadavg

my $loadavg = $device->loadavg;

Return a hash reference with the content of /proc/loadavg:

{
    avg1     => 2.15,
    avg5     => 1.87,
    avg15    => 1.62,
    running  => 3,
    total    => 1874,
    last_pid => 18342
}
  • avg1, avg5, avg15: load average over 1, 5 and 15 minutes.

  • running, total: running and total tasks (processes and threads).

  • last_pid: last PID assigned by the kernel.

ERRORS

All methods croak on error: e.g. when the device is no longer available, or when a command or a file read fails.

SEE ALSO

SUPPORT

Bugs / Feature Requests

Please report any bugs or feature requests through the issue tracker at https://github.com/giterlizzi/perl-Net-ADB/issues. You will be notified automatically of any progress on your issue.

Source Code

This is open source software. The code repository is available for public review and contribution under the terms of the license.

https://github.com/giterlizzi/perl-Net-ADB

git clone https://github.com/giterlizzi/perl-Net-ADB.git

AUTHOR

  • Giuseppe Di Terlizzi <gdt@cpan.org>

LICENSE AND COPYRIGHT

This software is copyright (c) 2026 by Giuseppe Di Terlizzi.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)