Android-ElectricSheep-Automator
view release on metacpan or search on metacpan
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
for my $ef (@$extrafields){ push @cmd, '-O', $ef }
push @cmd, '-f', '-l', '>', $devicefile;
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
my $res = $self->adb->shell(@cmd);
if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return unde...
if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }
$res = $self->adb->pull($devicefile, $tmpfilename);
if( ! defined $res ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to pull remote file '$devicefile' into local file '$tmpfilename', because undefined was returned, this should not be happening."); return undef }
if( $res->[0] != 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to pull remote file '$devicefile' into local file '$tmpfilename' with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }
@cmd = ("rm", "-f", $devicefile);
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
$res = $self->adb->shell(@cmd);
if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return unde...
if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }
# parse
if( ! open($FH, '<', $tmpfilename) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to open file with dump for reading '$tmpfilename', $!"); return undef }
my $contents;
{ local $/ = undef; $contents = <$FH> } close $FH;
$contents =~ s/\R+//;
my @rows = split /\R+/, $contents;
my @header = split /\s+/, shift @rows;
my %headerh = map { $_ => 1 } @header;
my $id = 'PID';
my $posid = exists($headerh{$id}) ? $headerh{$id} : undef;
if( ! defined $posid ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to find column name '$id' in above 'ps' output, where is it? what is it called?"); return undef }
$id = 'CMD';
my $poscmd = exists($headerh{$id}) ? $headerh{$id} : undef;
my $n = scalar @header;
my %psdata;
while( my $row = shift @rows ){
$row =~ s/^\s*//;
my @rowitems = split /\s+/, $row, $n+1;
my $k = $rowitems[defined($poscmd) ? $poscmd : $posid];
@{ $psdata{$k} }{@header} = splice @rowitems, 0, $n;
if( defined $poscmd ){
$psdata{$k}->{'CMD'} = [ Text::ParseWords::shellwords($psdata{$k}->{'CMD'}) ];
# now CMD is an arrayref
}
}
my $jsonstr = perl2json(\%psdata);
if( ! defined $jsonstr ){ $log->error(perl2dump(\%psdata)."${whoami} (via $parent), line ".__LINE__." : error, failed to convert above perl data hash to JSON string."); return undef }
if( defined $filename ){
if( ! open($FH, '>:raw', $filename) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to open file '$filename' for writing, $!"); return undef }
print $FH $jsonstr;
close $FH;
}
return {
'raw' => $contents,
'json' => $jsonstr,
'perl' => \%psdata
}
}
# ONLY FOR EMULATORS, it fixes the Geolocation to the
# specified coordinates (with 'latitude' and 'longitude').
# returns 1 on failure, 0 on success
# it needs that connect_device() to have been called prior to this call
sub geofix {
my ($self, $params) = @_;
$params //= {};
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
my $log = $self->log();
my $verbosity = $self->verbosity();
if( ! $self->is_device_connected() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, you need to connect a device to the desktop and ALSO explicitly call ".'connect_device()'." before calling this."); return undef }
for ('latitude', 'longitude'){
if( ! exists $params->{$_} ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter '$_' was not specified (as [x,y])."); return 1 }
}
my @cmd = ('emu', 'geo', 'fix', $params->{'longitude'}, $params->{'latitude'});
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
my $res = $self->adb->run(@cmd);
if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return 1 }
if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return 1 }
return 0; # success
}
# Get the current GPS location of the device
# according to ALL the GPS providers as a HASH
# keyed on GPS provider name with the information
# the provider provided including lat/lon
# It returns undef undef on failure or the above hash on success.
# NOTE: some providers may exist but have the Location[...] string as null
# meaning not available (e.g. 'network provider' when no internet exists)
# in this case lat,lon etc. will be '<na>' and the strings will be 'null'.
# it needs that connect_device() to have been called prior to this call
sub dump_current_location {
my ($self, $params) = @_;
$params //= {};
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
my $log = $self->log();
my $verbosity = $self->verbosity();
if( ! $self->is_device_connected() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, you need to connect a device to the desktop and ALSO explicitly call ".'connect_device()'." before calling this."); return undef }
my @cmd = ('dumpsys', 'location');
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
my $res = $self->adb->shell(@cmd);
if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return 1 }
if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return 1 }
my $content = $res->[1];
# these are the GPS providers in order of preference:
my $gps;
for my $prov ('gps provider', 'fused provider', 'passive provider', 'network provider'){
if( $content =~ /Geofences\:\s+Location Providers\:.*?\n\s+\Q${prov}\E\:\s+last location=(.+?)\R\s+last coarse location=(.+?)\R/sm ){
my $last_location = $1;
my $last_coarse_location = $2;
$gps //= {};
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
each item corresponding to one process, keyed on process command and arguments
(as reported by C<ps>, verbatim), as a hash keyed on each field (column)
of the C<ps> output.
=item * B<C<json>> : the above data converted into a JSON string.
=back
=head2 B<C<pidof($params)>>
It returns the PID of the specified command name.
The specified command name must match the app or command
name exactly. B<Use L/pgrep()> if you want to match command
names with a regular expression>.
C<$params> is a HASH_REF which should contain:
=over 4
=item * B<C<name>>
the name of the process. It can be a command name,
e.g. C<audioserver> or an app name e.g. C<android.hardware.vibrator-service.example>.
=back
It returns C<undef> on failure or the PID of the matched command on success.
=head2 B<C<pgrep($params)>>
It returns the PIDs matching the specified command or app
name (which can be an extended regular expression that C<pgrep>
understands). The returned array will contain zero, one or more
hashes with keys C<pid> and C<command>. The former key is the pid of the command
whose full name (as per the process table) will be under the latter key.
Unless parameter C<dont-show-command-name> was set to C<1>.
C<$params> is a HASH_REF which should contain:
=over 4
=item * B<C<name>>
the name of the process. It can be a command name,
e.g. C<audioserver> or an app name e.g. C<android.hardware.vibrator-service.example>
or part of these e.g. C<audio> or C<hardware> or an extended
regular expression that Android's C<pgrep> understands, e.g.
C<^com.+google.+mess>.
=back
It returns C<undef> on failure or an ARRAY_REF containing
a HASH_REF of data for each command matched (under keys C<pid> and C<command>).
The returned ARRAY_REF can contain 0, 1 or more items depending
on what was matched.
=head2 B<C<geofix($params)>>
It fixes the geolocation of the device to the specified coordinates.
After this, app API calls to get current geolocation will result to this
position (unless they use their own, roundabout way).
C<$params> is a HASH_REF which should contain:
=over 4
=item * B<C<latitude>>
the latitude of the position as a floating point number.
=item * B<C<longitude>>
the longitude of the position as a floating point number.
=back
It returns C<1> on failure or a C<0> on success.
=head2 B<C<dump_current_location()>>
It finds the current GPS location of the device
according to ALL the GPS providers available.
It needs that connect_device() to have been called prior to this call
It takes no parameters.
On failure, it returns C<undef>.
On success, it returns a HASH_REF of results.
Each item will be keyed on provider name (e.g. 'C<network provider>')
and will contain the parsed output of
what each GPS provider returned as a HASH_REF with
the following keys:
=over 4
=item * B<C<provider>> : the provider name. This is also the key of the item
in the parent hash.
=item * B<C<latitude>> : the latitude as a floating point number (can be negative too)
or C< E<lt>naE<gt> > if the provider failed to return valid output.
=item * B<C<longitude>> : the longitude as a floating point number (can be negative too)
or C< E<lt>na E<gt> > if the provider failed to return valid output.
=item * B<C<last-location-string>> : the last location string, or
C< E<lt>na E<gt> > if the provider failed to return valid output.
=back
=head2 B<C<pull_app_apk_from_device($params)>>
It pulls the APK file (bytecode) for the
app(s) matched by the specified package
specification,
from the device and writes them
into the specified output directory, locally.
C<$params> is a HASH_REF which should contain:
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
=item * B<C<force-reload-apps-list>>
is a flag to be passed on to L</find_installed_apps($params)>,
if needed, and can be set to 1 to
erase previous packages information and start fresh. Default is C<0>.
=back
It returns a HASH_REF of matched packages names (keys) along
with enquired information (as a L<Android::ElectricSheep::Automator::AppProperties>
object). At the moment, because L</close_app($params)> allows closing only a single app,
this hash will contain only one entry unless we allow closing multiple
apps (e.g. via a regex which it is already supported) in the future.
=head1 SCRIPTS
For convenience, a few simple scripts are provided:
=head2 B<C<electric-sheep-find-installed-apps.pl>>
Find all install packages in the connected device. E.g.
electric-sheep-find-installed-apps.pl --configfile config/myapp.conf --device Pixel_2_API_30_x86_ --output myapps.json
electric-sheep-find-installed-apps.pl --configfile config/myapp.conf --device Pixel_2_API_30_x86_ --output myapps.json --fast
=head2 B<C<electric-sheep-open-app.pl>>
Open an app by its exact name or a keyword matching it (uniquely):
electric-sheep-open-app.pl --configfile config/myapp.conf --name com.android.settings
electric-sheep-open-app.pl --configfile config/myapp.conf --keyword 'clock'
Note that it constructs a regular expression from escaped user input.
=head2 B<C<electric-sheep-close-app.pl>>
Close an app by its exact name or a keyword matching it (uniquely):
electric-sheep-close-app.pl --configfile config/myapp.conf --name com.android.settings
electric-sheep-close-app.pl --configfile config/myapp.conf --keyword 'clock'
Note that it constructs a regular expression from escaped user input.
=head2 B<C<electric-sheep-dump-ui.pl>>
Dump the current screen UI as XML to STDOUT or to a file:
electric-sheep-dump-ui.pl --configfile config/myapp.conf --output ui.xml
Note that it constructs a regular expression from escaped user input.
=head2 B<C<electric-sheep-dump-current-location.pl>>
Dump the GPS / geo-location position for the device from its various providers, if enabled.
electric-sheep-dump-current-location.pl --configfile config/myapp.conf --output geolocation.json
=head2 B<C<electric-sheep-emulator-geofix.pl>>
Set the GPS / geo-location position to the specified coordinates.
electric-sheep-dump-ui.pl --configfile config/myapp.conf --latitude 12.3 --longitude 45.6
=head2 B<C<electric-sheep-dump-screen-shot.pl>>
Take a screenshot of the device (current screen) and save to a PNG file.
electric-sheep-dump-screen-shot.pl --configfile config/myapp.conf --output screenshot.png
=head2 B<C<electric-sheep-dump-screen-video.pl>>
Record a video of the device's current screen and save to an MP4 file.
electric-sheep-dump-screen-video.pl --configfile config/myapp.conf --output video.mp4 --time-limit 30
=head2 B<C<electric-sheep-pull-app-apk.pl>>
Extract the APK file (java bytecode) for an app installed on the device and save locally, perhaps, for disassembly and/or modification and/or re-installation.
electric-sheep-pull-app-apk.pl --package calendar2 --wildcard --output anoutdir --configfile config/myapp.conf --device Pixel_2_API_30_x86_
=head2 B<C<electric-sheep-install-app>>
Install an APK file onto the device, passing extra installation
parameters C<-r> (for re-install) and C<-g> (for granting permissions),
electric-sheep-install-app --apk-filename test.apk -p '-r' -p '-g' --configfile config/myapp.conf --device Pixel_2_API_30_x86_
=head2 B<C<electric-sheep-viber-send-message.pl>>
Send a message using the Viber app.
electric-sheep-viber-send-message.pl --message 'hello%sthere' --recipient 'george' --configfile config/myapp.conf --device Pixel_2_API_30_x86_
This one saves a lot of debugging information to C<debug> which can be used to
deal with special cases or different versions of Viber:
electric-sheep-viber-send-message.pl --outbase debug --verbosity 1 --message 'hello%sthere' --recipient 'george' --configfile config/myapp.conf --device Pixel_2_API_30_x86_
=head1 TESTING
The normal tests under the C<t/> directory, initiated with C<make test> command,
are quite limited in scope because they do not assume
a connected device. That is, they do not check any
functions which require interaction with a connected
device.
The I<live tests> under the C<xt/live> directory, initiated with
C<make livetest> command, require
an Android emulator or real device (the latter B<is not recommended>)
connected to your desktop computer on which you are doing the testing.
Note that testing
with your smartphone is not a good idea, please do not do this,
unless it is some phone which you do not store important data.
( run in 2.472 seconds using v1.01-cache-2.11-cpan-8f98c5d2c55 )