Android-ElectricSheep-Automator

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

    
        # bottom navigation:
        # the "triangle" back button
        $mother->navigation_menu_back_button() or die;
        # the "circle" home button
        $mother->navigation_menu_home_button() or die;
        # the "square" overview button
        $mother->navigation_menu_overview_button() or die;
    
        # open/close apps
        $mother->open_app({'package'=>qr/calendar$/i}) or die;
        $mother->close_app({'package'=>qr/calendar$/i}) or die;
    
        # push pull files
        $mother->adb->pull($deviceFile, $localFile);
        $mother->adb->push($localFile, $deviceFileOrDir);
    
        # guess what!
        my $xmlstr = $mother->dump_current_screen_ui();
    
        # Pull the apk(s) for an app from device and save locally
        my $res = $mother->pull_app_apk_from_device({
          package => 'com.google.android.calendar'
            # or qr/calendar/i
          'output-dir' => '/tmp/apks-of-calendar-app',
        });
        print $res->{'com.google.android.calendar'}->[0]->['local-path'};
    
        # Install apk(s) for an app onto the device
        $mother->install_app({
          'apk-filename' => ['/tmp/apks/base.apk', '/tmp/apks/config.apk'],
            # or just a string scalar '/tmp/apks/1.apk'
          # optional params to the adb install command
          'install-parameters' => ['-r', '-g']
        });

CONSTRUCTOR

README  view on Meta::CPAN

    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

 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_

 electric-sheep-install-app

    Install an APK file onto the device, passing extra installation
    parameters -r (for re-install) and -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_

 electric-sheep-viber-send-message.pl

README  view on Meta::CPAN

    make this test then pull an APK of an existing app on your connected
    device with electric-sheep-pull-app-apk.pl and point the test file to
    this APK.

    Testing will not send any messages via the device's apps. E.g. the
    plugin Android::ElectricSheep::Automator::Plugins::Apps::Viber will not
    send a message via Viber but it will mock it.

    The live tests will sometimes fail because, so far, something
    unexpected happened in the device. For example, in testing sending
    input text to a text-edit widget, the calendar will be opened and a new
    entry will be added and its text-edit widget will be targeted. Well,
    sometimes the calendar app will give you some notification on startup
    and this messes up with the focus. Other times, the OS will detect that
    some app is taking too long to launch and pops up a notification about
    "something is not responding, shall I close it". This steals the focus
    and sometimes it causes the tests to fail.

PREREQUISITES

 Android Studio

    This is not a prerequisite but it is highly recommended to install it

README.md  view on Meta::CPAN


    # bottom navigation:
    # the "triangle" back button
    $mother->navigation_menu_back_button() or die;
    # the "circle" home button
    $mother->navigation_menu_home_button() or die;
    # the "square" overview button
    $mother->navigation_menu_overview_button() or die;

    # open/close apps
    $mother->open_app({'package'=>qr/calendar$/i}) or die;
    $mother->close_app({'package'=>qr/calendar$/i}) or die;

    # push pull files
    $mother->adb->pull($deviceFile, $localFile);
    $mother->adb->push($localFile, $deviceFileOrDir);

    # guess what!
    my $xmlstr = $mother->dump_current_screen_ui();

    # Pull the apk(s) for an app from device and save locally
    my $res = $mother->pull_app_apk_from_device({
      package => 'com.google.android.calendar'
        # or qr/calendar/i
      'output-dir' => '/tmp/apks-of-calendar-app',
    });
    print $res->{'com.google.android.calendar'}->[0]->['local-path'};

    # Install apk(s) for an app onto the device
    $mother->install_app({
      'apk-filename' => ['/tmp/apks/base.apk', '/tmp/apks/config.apk'],
        # or just a string scalar '/tmp/apks/1.apk'
      # optional params to the adb install command
      'install-parameters' => ['-r', '-g']
    });

# CONSTRUCTOR

README.md  view on Meta::CPAN

## **`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

## **`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_

## **`electric-sheep-install-app`**

Install an APK file onto the device, passing extra installation
parameters `-r` (for re-install) and `-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_

## **`electric-sheep-viber-send-message.pl`**

README.md  view on Meta::CPAN

with [electric-sheep-pull-app-apk.pl](https://metacpan.org/pod/electric-sheep-pull-app-apk.pl) and point the test file
to this APK.

Testing will not send any messages via the device's apps.
E.g. the plugin [Android::ElectricSheep::Automator::Plugins::Apps::Viber](https://metacpan.org/pod/Android%3A%3AElectricSheep%3A%3AAutomator%3A%3APlugins%3A%3AApps%3A%3AViber)
will not send a message via Viber but it will mock it.

The live tests will sometimes fail because, so far,
something unexpected happened in the device. For example,
in testing sending input text to a text-edit widget,
the calendar will be opened and a new entry will be added
and its text-edit widget will be targeted. Well, sometimes
the calendar app will give you some notification
on startup and this messes up with the focus.
Other times, the OS will detect that some app is taking too
long to launch and pops up a notification about
"_something is not responding, shall I close it_".
This steals the focus and sometimes it causes
the tests to fail.

# PREREQUISITES

## Android Studio

lib/Android/ElectricSheep/Automator.pm  view on Meta::CPAN

		# if this is undef, then it means caller did not call connect_device()
		# when caller calls disconnect_device(), this becomes undef again
		# this is a cheap way to not proceed to device-needed subs, e.g. swipe()
		# of course we could make an adb query with e.g. adb get-state
		'device-properties' => undef,

		# object of type Android::ElectricSheep::Automator::ADB::Device
		# which is created when we call connect_device()
		'device-object' => undef,

		# a hash of installed apps by package name (e.g. android.google.calendar)
		# the value will be an AppProperties object if it was enquired or undef
		# if it wasn't. As the addition of apps is done in a lazy way, when
		# needed, unless specified otherwise. In any event open_app() will add an
		# AppProperties object if missing to the specified package.
		'apps' => {},

		# legacy, no worries.
		'apps-roundabout-way' => undef,
	};
	bless $self => $class;

lib/Android/ElectricSheep/Automator.pm  view on Meta::CPAN


    # bottom navigation:
    # the "triangle" back button
    $mother->navigation_menu_back_button() or die;
    # the "circle" home button
    $mother->navigation_menu_home_button() or die;
    # the "square" overview button
    $mother->navigation_menu_overview_button() or die;

    # open/close apps
    $mother->open_app({'package'=>qr/calendar$/i}) or die;
    $mother->close_app({'package'=>qr/calendar$/i}) or die;

    # push pull files
    $mother->adb->pull($deviceFile, $localFile);
    $mother->adb->push($localFile, $deviceFileOrDir);

    # guess what!
    my $xmlstr = $mother->dump_current_screen_ui();

    # Pull the apk(s) for an app from device and save locally
    my $res = $mother->pull_app_apk_from_device({
      package => 'com.google.android.calendar'
	# or qr/calendar/i
      'output-dir' => '/tmp/apks-of-calendar-app',
    });
    print $res->{'com.google.android.calendar'}->[0]->['local-path'};

    # Install apk(s) for an app onto the device
    $mother->install_app({
      'apk-filename' => ['/tmp/apks/base.apk', '/tmp/apks/config.apk'],
        # or just a string scalar '/tmp/apks/1.apk'
      # optional params to the adb install command
      'install-parameters' => ['-r', '-g']
    });

=head1 CONSTRUCTOR

lib/Android/ElectricSheep/Automator.pm  view on Meta::CPAN

=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>>

lib/Android/ElectricSheep/Automator.pm  view on Meta::CPAN

with L<electric-sheep-pull-app-apk.pl> and point the test file
to this APK.

Testing will not send any messages via the device's apps.
E.g. the plugin L<Android::ElectricSheep::Automator::Plugins::Apps::Viber>
will not send a message via Viber but it will mock it.

The live tests will sometimes fail because, so far,
something unexpected happened in the device. For example,
in testing sending input text to a text-edit widget,
the calendar will be opened and a new entry will be added
and its text-edit widget will be targeted. Well, sometimes
the calendar app will give you some notification
on startup and this messes up with the focus.
Other times, the OS will detect that some app is taking too
long to launch and pops up a notification about
"I<something is not responding, shall I close it>".
This steals the focus and sometimes it causes
the tests to fail.

=head1 PREREQUISITES

=head2 Android Studio

lib/Android/ElectricSheep/Automator/AppProperties.pm  view on Meta::CPAN

	my $res = $mother->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:\nsSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }
	my $content = $res->[1];

	my %apps;
	while( $content =~ /^\s{2}Package\s+\[(.+?)\]\s+\(.+?\)\:[\r\n](.+?)(?:[\r\n]\s{2}[^ ]|[\r\n]$|\z)/smg ){
		# if you want the package content:
		#while( $content =~ /^(\s{2}Package\s+\[(.+?)\]\s+\(.+?\)\:[\r\n].+?)(?:[\r\n]\s{2}[^ ]|[\r\n]$|\z)/smg ){
		#my $package_contents = $1; and name $2
		my $package_name = $1; # full package name, e.g. com.google.android.calendar
		# we will be lazy
		my $is_this_lazy = $lazy;
		if( $lazy == 1 ){
			for my $ap (@packages_arr){
				if( ref($ap) eq '' ){
					if( $package_name eq $ap ){ $is_this_lazy = 0; last }
				} else {
					if( $package_name =~ $ap ){ $is_this_lazy = 0; last }
				}
			}

lib/Android/ElectricSheep/Automator/AppProperties.pm  view on Meta::CPAN


	# NOTE
	#   dumpsys package packages
	# and 
	#   dumpsys package com.example.xyz 
	# will yield different things for the package com.example.xyz 
	# we need to enquire twice, 1 for all the package names
	# 2 for the full content of the package info, including activities
	# we will dumpsys specifically for that package
	my $package;
	if( ! exists($params->{'package'}) || ! defined($package=$params->{'package'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' (the package name e.g. com.example.calendar) was not specified. If you want ...

	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : package '${package}' : called ...") }

	# here we could also save to a file on device and then
	# fetch it locally. We will do that if there are problems
	# getting the dump from STDOUT
	my @cmd = ('dumpsys', 'package', $package);
	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:\nsSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }

script/electric-sheep-pull-app-apk.pl  view on Meta::CPAN

	if( $VERBOSITY > 0 ){ print STDOUT "$0 : package '$pname' : saved apk ${Napks} from '".$v->{'device-path'}."' into '".$v->{'local-path'}."'.\n" }
  }
}
print STDOUT "$0 : done, success! Extracted ${Napks} APK(s) from ${Npackages} packages, written to output dir '$OUTDIR'.\n";

sub usage {
	return "Usage $0 --package NAME --output apkdir --configfile CONFIGFILE [--wildcard] [--device DEVICE] [--verbosity v]\n"
		. "\nThis script will pull the APK(s) of the specified package NAME into the local dir 'OUTPUT'. The package NAME can be the full package name, e.g. 'com.android.gallery2' or a part of it, e.g. 'gallery'. Use --wildcard option if you want to match ...
		. "\nExample:\n"
		. "  match all packages with 'gallery2' in their name:\n"
		. "$0 --configfile config/myapp.conf --output apkdir --package 'calendar2' --wildcard\n"
		. "\n  match the package name 'com.android.gallery2' exactly:\n"
		. "$0 --configfile config/myapp.conf --output apkdir --package 'com.android.gallery2'\n"
		. "\nProgram by Andreas Hadjiprocopis (c) 2025 / bliako at cpan.org / andreashad2 at gmail.com\n\n"
	;
}

1;

xt/live/260-find_all_apps-search_app.t  view on Meta::CPAN


my $mother = Android::ElectricSheep::Automator->new({
	'configfile' => $configfile,
	#'verbosity' => $VERBOSITY,
	# we have a device connected and ready to control
	'device-is-connected' => 1,
});
ok(defined($mother), 'Android::ElectricSheep::Automator->new()'." : called and got defined result.") or BAIL_OUT;

##############################################################
# find one app by regex name 'calendar' this yields 2 matches
# we expect to have lots of apps but only 2 to be instantiated
#   com.google.android.calendar
#   com.android.providers.calendar
my $aregex = qr/calendar/i;
my $params = {
	'packages' => $aregex, # this will instantiate all matched apps AppProperties
	# default is lazy=1
};
my $apps = $mother->find_installed_apps($params);
ok(defined($apps), 'find_installed_apps()'." : called and got good result.") or BAIL_OUT;
is(ref($apps), 'HASH', 'find_installed_apps()'." : called and result is a HASHref.") or BAIL_OUT("no it is '".ref($apps)."'.");
ok(scalar(keys %$apps)>1, 'find_installed_apps()'." : called and result contains at least one item.") or BAIL_OUT("no it contains ".scalar(keys %$apps)." items.");
is_deeply($apps, $mother->apps, 'find_installed_apps()'." : set mother's apps to the returned value.") or BAIL_OUT;
# now $mother's apps must contain many items but calendar will have AppProperties.
my $instantiated_apps = 0;
my @instantiated_apps = ();
for my $appname (sort keys %$apps){
	if( defined $apps->{$appname} ){
		$instantiated_apps++;
		push @instantiated_apps, $appname;
	}
	if( $appname =~ $aregex ){
		ok(defined($apps->{$appname}), 'find_installed_apps()'." : app '$appname' has AppProperties.") or BAIL_OUT;
		for my $k (qw/

xt/live/260-find_all_apps-search_app.t  view on Meta::CPAN

ok(defined($searched_apps), 'search_app()'." : called and got good result") or BAIL_OUT;
is(ref($searched_apps), 'HASH', 'search_app()'." : called and result is a HASHref.") or BAIL_OUT(perl2dump($params)."using above params, no it is '".ref($apps)."'.");
ok(scalar(keys %$searched_apps)==1 || scalar(keys %$searched_apps)==2, 'search_app()'." : called and result contains one or two items.") or BAIL_OUT(perl2dump($params)."using above params, no it contains ".scalar(keys %$searched_apps)." items: ".join...

# we must still have the same number of total apps in mother
is(scalar keys %{ $mother->apps }, $num_apps_total, "after these operations the number of total apps remains unchanged ($num_apps_total).") or BAIL_OUT(perl2dump($params)."using above params, no it is now ".(scalar keys %{ $mother->apps })." instead ...

##############################################################
# find same apps as before with an ARRAY
# we expect to have lots of apps but only 2 to be instantiated
#   com.google.android.calendar
#   com.android.providers.calendar
$aregex = qr/^(\Qcom.google.android.calendar\E)|(\Qcom.android.providers.calendar\E)$/i;
$params = {
	'packages' => [
		'com.google.android.calendar',
		'com.android.providers.calendar'
	],
	# default is lazy=1
};
$apps = $mother->find_installed_apps($params);
ok(defined($apps), 'find_installed_apps()'." : called and got good result.") or BAIL_OUT;
is(ref($apps), 'HASH', 'find_installed_apps()'." : called and result is a HASHref.") or BAIL_OUT(perl2dump($params)."using above params, no it is '".ref($apps)."'.");
ok(scalar(keys %$apps)>1, 'find_installed_apps()'." : called and result contains at least one item.") or BAIL_OUT(perl2dump($params)."using above params, no it contains ".scalar(keys %$apps)." items.");
is_deeply($apps, $mother->apps, 'find_installed_apps()'." : set mother's apps to the returned value.") or BAIL_OUT;
# now $mother's apps must contain many items but calendar will have AppProperties.
# The others will be set to undef.
my $instantiated_apps = 0;
my @instantiated_apps = ();
for my $appname (sort keys %$apps){
	if( defined $apps->{$appname} ){
		$instantiated_apps++;
		push @instantiated_apps, $appname;
	}
	if( $appname =~ $aregex ){
		ok(defined($apps->{$appname}), 'find_installed_apps()'." : app '$appname' has AppProperties.") or BAIL_OUT;

xt/live/260-find_all_apps-search_app.t  view on Meta::CPAN

ok(defined($searched_apps), 'search_app()'." : called and got good result") or BAIL_OUT;
is(ref($searched_apps), 'HASH', 'search_app()'." : called and result is a HASHref.") or BAIL_OUT(perl2dump($params)."using above params, no it is '".ref($apps)."'.");
ok(scalar(keys %$searched_apps)==1 || scalar(keys %$searched_apps)==2, 'search_app()'." : called and result contains one or two items.") or BAIL_OUT(perl2dump($params)."using above params, no it contains ".scalar(keys %$searched_apps)." items: ".join...

# we must still have the same number of total apps in mother
is(scalar keys %{ $mother->apps }, $num_apps_total, "after these operations the number of total apps remains unchanged ($num_apps_total).") or BAIL_OUT(perl2dump($params)."using above params, no it is now ".(scalar keys %{ $mother->apps })." instead ...

##############################################################
# find same apps as before with a HASH
# we expect to have lots of apps but only 2 to be instantiated
#   com.google.android.calendar
#   com.android.providers.calendar
$aregex = qr/^(\Qcom.google.android.calendar\E)|(\Qcom.android.providers.calendar\E)$/i;
$params = {
	'packages' => {
		'com.google.android.calendar' => 1,
		'com.android.providers.calendar' => 1,
	},
	# default is lazy=1
};
$apps = $mother->find_installed_apps($params);
ok(defined($apps), 'find_installed_apps()'." : called and got good result.") or BAIL_OUT;
is(ref($apps), 'HASH', 'find_installed_apps()'." : called and result is a HASHref.") or BAIL_OUT(perl2dump($params)."using above params, no it is '".ref($apps)."'.");
ok(scalar(keys %$apps)>1, 'find_installed_apps()'." : called and result contains at least one item.") or BAIL_OUT(perl2dump($params)."using above params, no it contains ".scalar(keys %$apps)." items.");
is_deeply($apps, $mother->apps, 'find_installed_apps()'." : set mother's apps to the returned value.") or BAIL_OUT;
# now $mother's apps must contain many items but calendar will have AppProperties.
my $instantiated_apps = 0;
my @instantiated_apps = ();
for my $appname (sort keys %$apps){
	if( defined $apps->{$appname} ){
		$instantiated_apps++;
		push @instantiated_apps, $appname;
	}
	if( $appname =~ $aregex ){
		ok(defined($apps->{$appname}), 'find_installed_apps()'." : app '$appname' has AppProperties.") or BAIL_OUT;
		for my $k (qw/

xt/live/260-find_all_apps-search_app.t  view on Meta::CPAN

$params = {
	'packages' => $aregex, # this will instantiate all matched apps AppProperties
	# this will instantiate all apps
	'lazy' => 0,
};
$apps = $mother->find_installed_apps($params);
ok(defined($apps), 'find_installed_apps()'." : called and got good result.") or BAIL_OUT;
is(ref($apps), 'HASH', 'find_installed_apps()'." : called and result is a HASHref.") or BAIL_OUT(perl2dump($params)."using above params, no it is '".ref($apps)."'.");
ok(scalar(keys %$apps)>1, 'find_installed_apps()'." : called and result contains at least one item.") or BAIL_OUT(perl2dump($params)."using above params, no it contains ".scalar(keys %$apps)." items.");
is_deeply($apps, $mother->apps, 'find_installed_apps()'." : set mother's apps to the returned value.") or BAIL_OUT;
# now $mother's apps must contain many items but calendar will have AppProperties.
$instantiated_apps = 0;
@instantiated_apps = ();
for my $appname (sort keys %$apps){
	if( defined $apps->{$appname} ){
		$instantiated_apps++;
		push @instantiated_apps, $appname;
	}
	if( $appname =~ $aregex ){
		ok(defined($apps->{$appname}), 'find_installed_apps()'." : app '$appname' has AppProperties.") or BAIL_OUT;
		if(0){



( run in 1.860 second using v1.01-cache-2.11-cpan-39bf76dae61 )