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 inro.product.*,ro.product.vendor.*,ro.product.system.*andro.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:1ifro.boot.qemuorro.kernel.qemuis set (Android emulator),0otherwise.
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 thanuptime).
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 byuname -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) orred(verification failed).bootloader_locked: bootloader lock state (ro.boot.flash.locked, orro.boot.vbmeta.device_state).debuggable: debuggable build (ro.debuggable), e.g.enganduserdebugbuilds. Production devices are0.secure: secure mode (ro.secure),0means thatadbdruns 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, ortest-keysfor builds signed with public test keys).encryption,encryption_type: storage encryption state (ro.crypto.state) and type (ro.crypto.type,fileorblock).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),undefwhen 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,undefon later versions, where the permission is per app (see "install_sources").private_dns,private_dns_host: Private DNS (DNS over TLS) mode (off,opportunisticorhostname) 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:tcporudp.family:4for IPv4,6for 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 areESTABLISHEDwhen connected to a remote address andCLOSEotherwise.local_address,local_port: local address and port.0.0.0.0and::accept connections on every interface,127.0.0.1and::1only from the device itself.remote_address,remote_port: remote address and port (0.0.0.0and0when not connected).uid: Linux user ID of the owner: match it with theuidof "packages" to find the app (0isroot,1000issystem). 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.vendingfor Google Play), orundefwhen 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), orundef. Packages with the same shared user run with the sameuidand 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.vendingfor Google Play), orundefwhen the system has not recorded one (e.g. preinstalled packages oradb install).first_install,last_update,timestamp: installation time, last update time and modification time of the APK, asYYYY-MM-DD HH:MM:SSin the device local time zone. Preinstalled packages often report a placeholder date (e.g.1970-01-01or2009-01-01).path,data_dir: directory of the APK files and data directory.splits: names of the installed APK splits (baseplus, for app bundles, configuration splits).primary_abi,secondary_abi: ABIs used to load the native libraries of the app,undeffor apps without native code.flags,private_flags: application flags as reported by the system.system: system app (SYSTEMflag).updated_system: system app updated after the factory version (UPDATED_SYSTEM_APP).privileged: privileged system app, which can be grantedsignature|privilegedpermissions (PRIVILEGED).debuggable: debuggable app (DEBUGGABLE): its data can be accessed withrun-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.installedis0for a system app uninstalled for the user,stoppedis1for an app never started or force-stopped.enabled,enabled_state: enabled setting for the user, as number and name:0default,1enabled,2disabled,3disabled_user(disabled by the user),4disabled_until_used.defaultmeans the state declared in the manifest, which is not always enabled.permissions: permissions requested by the package or granted to itsuid, keyed by name:requested:1if the package requests it in its manifest. It is0for permissions granted to theuidthrough another package with the sameshared_user.restricted:1for 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) orundef(requested but never granted, e.g. a signature permission of another app).granted:1if 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, withprotection(protection levels, e.g.['signature', 'privileged']) andinstalled.
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)