view release on metacpan or search on metacpan
{
"abstract" : "Do Androids Dream of Electric Sheep? Smartphone control from your desktop.",
"author" : [
"Andreas Hadjiprocopis <bliako@cpan.org>"
],
"dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 7.76, CPAN::Meta::Converter version 2.150010",
"license" : [
"artistic_2"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
---
abstract: 'Do Androids Dream of Electric Sheep? Smartphone control from your desktop.'
author:
- 'Andreas Hadjiprocopis <bliako@cpan.org>'
build_requires:
Data::Roundtrip: '0.30'
ExtUtils::MakeMaker: '0'
FindBin: '0'
Mojo::Log: '0'
Test::More: '0'
Test::More::UTF8: '0'
Test::TempDir::Tiny: '0'
NAME
Android::ElectricSheep::Automator - Do Androids Dream of Electric
Sheep? Smartphone control from your desktop.
VERSION
Version 0.09
WARNING
Current distribution is extremely alpha. API may change.
SYNOPSIS
The present package fascilitates the control of a USB-debugging-enabled
Android device, e.g. a real smartphone, or an emulated (virtual)
Android device, from your desktop computer using Perl. It's basically a
thickishly-thin wrapper to the omnipotent Android Debug Bridge (adb)
program.
Note that absolutely nothing is installed on the connected device,
neither any of its settings will be modified by this package. See "WILL
ANYTHING BE INSTALLED ON THE DEVICE?".
use Android::ElectricSheep::Automator;
my $mother = Android::ElectricSheep::Automator->new({
# optional as there is a default, but you may have
# problems with the location of the adb executable
'configfile' => $configfile,
'verbosity' => 1,
# we already have a device connected and ready to control
'device-is-connected' => 1,
});
# find the devices connected to desktop and set one.
my @devices = $mother->adb->devices;
$mother->connect_device({'serial' => $devices->[0]->serial})
or die;
# no device identification is required for the method call
# if there is only one connected device:
$mother->connect_device() if scalar(@devices)==0;
# Go Home
$mother->home_screen() or die;
and can be supplied to the constructor instead of the configuration
file.
If no configuration is specified, then a default configuration will
be used. In this case please specify adb-path-to-executable to point
to the location of adb. Most likely the default path will not work
for you.
* adb-path-to-executable
optionally specify the path to the adb executable in your desktop
system. This will override the setting 'adb'->'path-to-executable'
in the configuration, if it was provided. Use this option if you are
not providing any configuration and so the default configuration will
be used. But it will most likely fail because of this path not being
correct for your system. So, if you are going to omit providing a
configuration and the default configuration will be used do specify
the adb path via this option (but you don't have to and your mileage
may vary).
* device-serial or device-object
* device-is-connected
optionally set it to 1 in order to communicate with the device and
get some information about it like screen size, resolution,
orientation, etc. And also allow use of functionality which needs
communicating with a device like "swipe($params)",
"home_screen($params)", "open_app($params)", etc. After
instantiation, you can use the method "connect_device($params)" and
"disconnect_device()" for conveying this information to the module.
Also note that if there are more than one devices connected to the
desktop, make sure you specify which one with the device parameter.
Default value is 0.
* logger
optionally specify a logger object to be used (instead of creating a
fresh one). This object must implement these methods: info(), warn(),
error(). Mojo::Log fits perfectly.
* logfile
* ARRAY_REF : my $ar = [1,2,3]; my $ar = \@ahash; my @anarray = @$ar;
* HASH_REF : my $hr = {1=1, 2=>2}; my $hr = \%ahash; my %ahash =
%$hr;>
* In this module parameters to functions are passed as a HASH_REF.
Functions return back objects, ARRAY_REF or HASH_REF.
devices()
Lists all Android devices connected to your desktop and returns these
as an ARRAY_REF which can be empty.
It returns undef on failure.
connect_device($params)
Specifies the current Android device to control. Its use is required
only if you have more than one devices connected. $params is a HASH_REF
which should contain exactly one of the following:
Android::ElectricSheep::Automator::DeviceProperties object containing
this information, for example screen size, resolution, serial number,
etc.
It returns Android::ElectricSheep::Automator::DeviceProperties object
on success or undef on failure.
connect_device()
It signals to our object that there is now a device connected to the
desktop and its enquiry and subsequent control can commence. If this is
not called and neither device-is-connected => 1 is specified as a
parameter to the constructor, then the functionality will be limited
and access to functions like "swipe($params)", "open_app($params)",
etc. will be blocked until the caller signals that a device is now
connected to the desktop.
Using "connect_device($params)" to specify which device to target in
the case of multiple devices connected to the desktop will also call
this method.
This method will try to enquire the connected device about some of its
properties, like screen size, resolution, orientation, serial number
etc. This information will subsequently be available via
$self->device_properties()>.
It returns 0 on success, 1 on failure.
disconnect_device()
Signals to our object that it should consider that there is currently
no device connected to the desktop (irrespective of that is true or
not) which will block access to "swipe($params)", "open_app($params)",
etc.
device_properties()
It returns the currently connected device properties as a
Android::ElectricSheep::Automator::DeviceProperties object or undef if
there is no connected device. The returned object is constructed during
a call to "find_current_device_properties($params)" which is called via
"connect_device($params)" and will persist for the duration of the
TESTING
The normal tests under the t/ directory, initiated with 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 live tests under the xt/live directory, initiated with make
livetest command, require an Android emulator or real device (the
latter 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. It is very easy to get an emulated
Android device running on any OS.
So, prior to make livetest make sure you have an android emulator up
and running with, for example, emulator -avd Pixel_2_API_30_x86_ . See
section "Android Emulators" for how to install, list and run them
buggers.
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
(from https://developer.android.com/studio) on your desktop computer
because it contains all the executables you will need, saved in a well
documented file system hierarchy, which can then be accessed from the
command line. You will not be using the IDE or anything, just the
accompaniying binaries and libraries it comes with.
Additionally, Android Studio offers possibly the easiest way to create
Android Virtual Devices (AVD) which emulate an Android phone of various
specifications, phone models and sizes, API levels, etc. I mention this
because one can install apps on an AVD and control them from your
desktop as long as you are able to receive sms verification codes from
a real phone. Perhaps you will need an Android emulator image which
comes with Google Play Services, if you are installing apps from their
store. This is great for experimenting without plugging in your real
smartphone on your desktop.
The bottom line is that by installing Android Studio, you have all the
executables you need for running things from the command line and,
additionally, you have the easiest way for creating Android Virtual
Devices, which emulate Android devices: phones, tablets, automotive
displays. Once you have this set up, you will not need to open Android
Studio ever again unless you want to update your kit. All the
functionality will be accessible from the command line.
ADB
Android Debug Bridge (ADB) is the program which communicates with your
smartphone or an Android Virtual Device from your desktop (Linux, osx
and the unnamed 0$).
If you do not want to install Android Studio, the adb executable is
included in the package called "Android SDK Platform Tools" available
from the Android official site, here:
https://developer.android.com/tools/releases/platform-tools#downloads
You will need the adb executable to be on your path or specify its
fullpath in the configuration file supplied to
Android::ElectricSheep::Automator's constructor.
Enter your phone pin and you are in developer mode.
You can exit Developer Mode by going to Settings->System->Developer and
turn it off. It is highly advised to turn off Developer Mode for
everyday use of your phone. Do not connect your smartphone to public
WIFI networks with Developer Mode ON.
Do not leave home with Developer Mode ON.
Once you have enabled "USB Debugging", you have two options for making
your device visible to your desktop and, consequently, to ADB and to
this module:
* connect your android device via a USB cable to your desktop
computer. I am not sure if you also need to tap on the USB charging
options and allow "Transfer Files".
* connect your device to the same WIFI network as your desktop
computer. Then follow instructions from, e.g., here
https://developer.android.com. This requires a newer Android version.
Android Emulators
It is possible to do most things your smartphone does with an Android
Virtual Device. You can install apps on the the virtual device which
you can register by supplying your real smartphone number.
List all virtual devices currently available in your desktop computer,
with emulator -list-avds which outputs something like:
Pixel_2_API_27_x86_
Pixel_2_API_30_x86_
Start a virtual device with emulator -avd Pixel_2_API_30_x86_
And hey, you have an android phone running on your desktop in its own
space, able to access the network but not the telephone network (no SIM
card).
It is possible to create a virtual device from the command line. But
perhaps it is easier if you download Android Studio from:
https://developer.android.com/studio and follow the setup there using
the GUI. You will need to do this just once for creating the device,
you can then uninstall Android Studio.
Android Studio will download all the required files and will create
# NAME
Android::ElectricSheep::Automator - Do Androids Dream of Electric Sheep? Smartphone control from your desktop.
# VERSION
Version 0.09
# WARNING
Current distribution is extremely alpha. API may change.
# SYNOPSIS
The present package fascilitates the control
of a USB-debugging-enabled
Android device, e.g. a real smartphone,
or an emulated (virtual) Android device,
from your desktop computer using Perl.
It's basically a thickishly-thin wrapper
to the omnipotent Android Debug Bridge (adb)
program.
**Note that absolutely nothing is
installed on the connected device,
neither any of its settings will be modified by this package**.
See ["WILL ANYTHING BE INSTALLED ON THE DEVICE?"](#will-anything-be-installed-on-the-device).
use Android::ElectricSheep::Automator;
my $mother = Android::ElectricSheep::Automator->new({
# optional as there is a default, but you may have
# problems with the location of the adb executable
'configfile' => $configfile,
'verbosity' => 1,
# we already have a device connected and ready to control
'device-is-connected' => 1,
});
# find the devices connected to desktop and set one.
my @devices = $mother->adb->devices;
$mother->connect_device({'serial' => $devices->[0]->serial})
or die;
# no device identification is required for the method call
# if there is only one connected device:
$mother->connect_device() if scalar(@devices)==0;
# Go Home
$mother->home_screen() or die;
If no configuration is specified, then a default
configuration will be used. In this case please
specify **`adb-path-to-executable`** to point
to the location of `adb`. Most likely
the default path will not work for you.
- **`adb-path-to-executable`**
optionally specify the path to the `adb` executable in
your desktop system. This will override the setting
` 'adb'->'path-to-executable' ` in the configuration,
if it was provided. Use this option if you are not
providing any configuration and so the default configuration
will be used. But it will most likely fail because of this
path not being correct for your system. So, if you are going
to omit providing a configuration and the default configuration
will be used do specify the `adb` path via this option (but you
don't have to and your mileage may vary).
- **`device-serial`** or **`device-object`**
screen size, resolution, orientation, etc.
And also allow use of
functionality which needs communicating with a device
like ["swipe($params)"](#swipe-params), ["home\_screen($params)"](#home_screen-params),
["open\_app($params)"](#open_app-params), etc.
After instantiation, you can use the
method ["connect\_device($params)"](#connect_device-params) and
["disconnect\_device()"](#disconnect_device) for conveying
this information to the module.
Also note that if there are
more than one devices connected to the desktop, make sure
you specify which one with the `device` parameter.
Default value is 0.
- **`logger`**
optionally specify a logger object
to be used (instead of creating a fresh one). This object
must implement these methods: `info()`, `warn()`, `error()`.
[Mojo::Log](https://metacpan.org/pod/Mojo%3A%3ALog) fits perfectly.
Note:
- **`ARRAY_REF`** : `my $ar = [1,2,3]; my $ar = \@ahash; my @anarray = @$ar;`
- **`HASH_REF`** : `my $hr = {1=`1, 2=>2}; my $hr = \\%ahash; my %ahash = %$hr;>
- In this module parameters to functions are passed as a HASH\_REF.
Functions return back objects, ARRAY\_REF or HASH\_REF.
## **`devices()`**
Lists all Android devices connected to your
desktop and returns these as an ARRAY\_REF which can be empty.
It returns `undef` on failure.
## **`connect_device($params)`**
Specifies the current Android device to control. Its use is
required only if you have more than one devices connected.
`$params` is a HASH\_REF which should contain exactly
one of the following:
and returns back an [Android::ElectricSheep::Automator::DeviceProperties](https://metacpan.org/pod/Android%3A%3AElectricSheep%3A%3AAutomator%3A%3ADeviceProperties)
object containing this information, for example screen size,
resolution, serial number, etc.
It returns [Android::ElectricSheep::Automator::DeviceProperties](https://metacpan.org/pod/Android%3A%3AElectricSheep%3A%3AAutomator%3A%3ADeviceProperties)
object on success or `undef` on failure.
## **`connect_device()`**
It signals to our object that there is now
a device connected to the desktop and its
enquiry and subsequent control can commence.
If this is not called and neither `device-is-connected => 1`
is specified as a parameter to the constructor, then
the functionality will be limited and access
to functions like ["swipe($params)"](#swipe-params), ["open\_app($params)"](#open_app-params), etc.
will be blocked until the caller signals that
a device is now connected to the desktop.
Using ["connect\_device($params)"](#connect_device-params) to specify which device
to target in the case of multiple devices
connected to the desktop will also call this
method.
This method will try to enquire the connected device
about some of its properties, like screen size,
resolution, orientation, serial number etc.
This information will subsequently be available
via `$self->`device\_properties()>.
It returns `0` on success, `1` on failure.
## **`disconnect_device()`**
Signals to our object that it should consider
that there is currently no device connected to
the desktop (irrespective of that is true or not)
which will block access to ["swipe($params)"](#swipe-params), ["open\_app($params)"](#open_app-params), etc.
## **`device_properties()`**
It returns the currently connected device properties
as a [Android::ElectricSheep::Automator::DeviceProperties](https://metacpan.org/pod/Android%3A%3AElectricSheep%3A%3AAutomator%3A%3ADeviceProperties)
object or `undef` if there is no connected device.
The returned object is constructed during a call
to ["find\_current\_device\_properties($params)"](#find_current_device_properties-params)
which is called via ["connect\_device($params)"](#connect_device-params) and will persist
The normal tests under the `t/` directory, initiated with `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 _live tests_ under the `xt/live` directory, initiated with
`make livetest` command, require
an Android emulator or real device (the latter **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.
It is very easy to get an emulated Android device running on any OS.
So, prior to `make livetest` make sure you have an android
emulator up and running with, for example,
`emulator -avd Pixel_2_API_30_x86_` . See section
["Android Emulators"](#android-emulators) for how to install, list and run them
buggers.
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
(from [https://developer.android.com/studio](https://developer.android.com/studio))
on your desktop computer because it contains
all the executables you will need,
saved in a well documented file system hierarchy,
which can then be accessed from the command line.
You will not be using the IDE or anything, just
the accompaniying binaries and libraries it comes with.
Additionally, Android Studio offers possibly the
easiest way to create Android Virtual Devices (AVD) which emulate
an Android phone of various specifications, phone models and sizes,
API levels, etc.
I mention this because one can install apps
on an AVD and control them from your desktop
as long as you are able to receive sms verification
codes from a real phone. Perhaps you will need an Android
emulator image which comes with Google Play Services,
if you are installing apps from their store.
This is great for
experimenting without plugging in your real
smartphone on your desktop.
The bottom line is that by installing Android Studio,
you have all the executables you need for running things
from the command line and, additionally, you have
the easiest way for creating Android
Virtual Devices, which emulate Android devices: phones,
tablets, automotive displays. Once you have this set up, you
will not need to open Android Studio ever again unless you
want to update your kit. All the functionality
will be accessible from the command line.
## ADB
Android Debug Bridge (ADB) is the program
which communicates with your smartphone or
an Android Virtual Device from
your desktop (Linux, osx and the unnamed `0$`).
If you do not want to install Android Studio, the `adb` executable
is included in the package called
"Android SDK Platform Tools" available from
the Android official site, here:
[https://developer.android.com/tools/releases/platform-tools#downloads](https://developer.android.com/tools/releases/platform-tools#downloads)
You will need the `adb` executable to be on your path
or specify its fullpath in the configuration file
supplied to [Android::ElectricSheep::Automator](https://metacpan.org/pod/Android%3A%3AElectricSheep%3A%3AAutomator)'s constructor.
`Settings->System->Developer` and turn it off.
It is highly advised to turn off Developer Mode
for everyday use of your phone.
**Do not connect your smartphone
to public WIFI networks with Developer Mode ON**.
**Do not leave home with Developer Mode ON**.
Once you have enabled "USB Debugging", you have
two options for making your device visible to
your desktop and, consequently, to ADB and to this module:
- connect your android device via a USB cable
to your desktop computer. I am not sure if you also
need to tap on the USB charging options and allow
"Transfer Files".
- connect your device to the same WIFI network
as your desktop computer. Then follow instructions
from, e.g., here [https://developer.android.com](https://developer.android.com).
This requires a newer Android version.
## Android Emulators
It is possible to do most things your
smartphone does with an Android Virtual Device.
You can install apps on the the virtual device which
you can register by supplying your real smartphone
number.
List all virtual devices currently available
in your desktop computer, with `emulator -list-avds`
which outputs something like:
Pixel_2_API_27_x86_
Pixel_2_API_30_x86_
Start a virtual device with `emulator -avd Pixel_2_API_30_x86_`
And hey, you have an android phone running on your
desktop in its own space, able to access the network
but not the telephone network (no SIM card).
It is possible to create a virtual device
from the command line.
But perhaps it is easier if you download Android Studio
from: [https://developer.android.com/studio](https://developer.android.com/studio) and follow
the setup there using the GUI. You will need to do this just
once for creating the device, you can then uninstall Android Studio.
Android Studio will download all the
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
</* log to file if you uncomment this */>
</* "filename" : "..." */>
}
</* config for our plugins (each can go to separate file also) */>
}
EODC
# NOTE: by default, it assumes that no device is connected
# and so it does not enquire about screen size etc on startup
# In order to tell it that a device (just one)
# is connected to the desktop and that we should connect to
# it,
# use param 'device-is-connected' => 1
# if there are more than one devices and you want to connect to
# one of them, then
# use param 'device-serial' => <serial-of-device-to-connect>
# or
# use param 'device-object' => <device object>
# (of type Android::ElectricSheep::Automator::ADB::Device)
# or after instantiation with $obj->connect_device(...);
# and similarly for disconnect_device()
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
else { $self->cleanup($self->confighash->{'debug'}->{'cleanup'}) }
my $verbosity = $self->verbosity;
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : done, success (verbosity is set to ".$self->verbosity." and cleanup to ".$self->cleanup.").") }
return $self;
}
# This signals our object that there is at least one device connected
# to the desktop which ADB can access and so can we.
# set the device by specifying one of
# 'serial' : the device's serial
# 'device-object' : a Android::ADB::Device object
# as returned by any item of $self->adb->devices()
# However, if there is ONLY ONE device connected to the desktop, then
# you do not need to specify a device, use this method without arguments
#
# It returns the device object (Android::ADB::Device) on success
# or undef on failure
sub connect_device {
my ($self, $params) = @_;
$params //= {};
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
for (@$devs){
if( $_->serial eq $m ){ $what_device = $_; last }
}
if( ! defined $what_device ){ $log->error(devices_toString($devs)."\n${whoami} (via $parent), line ".__LINE__." : error, there is no device with specified serial '$m', above are all the connected devices."); return undef }
} elsif( exists($params->{'device-object'}) && defined($m=$params->{'device-object'})
&& (ref($params->{'device-object'})eq'Android::ElectricSheep::Automator::ADB::Device')
){
$what_device = $m
} else {
# no params means we assume there is exactly 1 device connected to the desktop
my $devs = $self->devices();
if( scalar(@$devs) == 1 ){
$what_device = $devs->[0];
} else { $log->error("${whoami} (via $parent), line ".__LINE__." : error, expecting exactly one device connected to the desktop but found ".scalar(@$devs)." instead. In the case of more than one devices connected to the desktop then specify which o...
}
# this can die
my $res = eval { $self->adb->set_device($what_device) };
if( $@ || ! defined $res ){ $log->error(device_toString($what_device)."\n${whoami} (via $parent), line ".__LINE__." : error, call to ".'adb->set_device()'." has failed for above device."); return undef }
# and get the device properties of the set device
# that method will also set $self->{'device-properties'} to the returned object
my $device_properties = $self->find_current_device_properties();
if( ! defined $device_properties ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'find_current_device_properties()'." has failed."); return 1; }
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub dump_current_screen_ui {
my ($self, $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 ".'device_connected()'." before calling this."); return undef }
my $filename = exists($params->{'filename'}) && defined($params->{'filename'}) ? $params->{'filename'} : undef;
my $FH;
if( ! defined $filename ){
($FH, $filename) = tempfile(CLEANUP=>$self->cleanup);
close $FH;
}
# WARNING, you need to wake up the phone before dumping !!!!
my $devicefile = File::Spec->catfile('/', 'data', 'local', 'tmp', $$.'.xml');
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# It returns 1 on failure, 0 on success.
sub install_app {
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 1 }
my $apkfilename = exists($params->{'apk-filename'}) ? $params->{'apk-filename'} : undef;
if( ! defined $apkfilename ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'apk-filename' is missing."); return 1 }
my $installparams = exists($params->{'install-parameters'}) ? $params->{'install-parameters'} : [];
my @cmd = ('later...');
for (@$installparams){ push @cmd, $_ }
if( ref($apkfilename) eq '' ){
# just one apk file
push @cmd, $apkfilename;
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# (the apk saved locally) and 'device-name' (the path of the apk on the device)
sub pull_app_apk_from_device {
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 $outdir = exists($params->{'output-dir'}) ? $params->{'output-dir'} : undef;
if( ! defined $outdir ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'output-dir' is missing."); return undef }
# this can be regex or absolute string, like what search_apps() takes
my $package = exists($params->{'package'}) ? $params->{'package'} : undef;
if( ! defined $package ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' is missing."); return undef }
my $sparams = {
'package' => $package,
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub swipe {
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 1 }
my $w = $self->device_properties->get('w');
my $h = $self->device_properties->get('h');
my @fullspec;
for ('x1', 'y1', 'x2', 'y2', 'dt'){
if( ! exists($params->{$_}) || ! defined($params->{$_}) ){ last }
push @fullspec, $params->{$_};
}
my @cmd;
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# This will append to apps() the result.
sub find_installed_apps {
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 }
# optionally caller can specify a list of app names to enquire
# either as exact package name (string), a Regexp object (qr//)
# an ARRAY of package names or a HASH of package names:
my $packages = exists($params->{'packages'}) && defined($params->{'packages'}) ? $params->{'packages'} : undef;
my $rr = ref $packages;
if( ($rr ne '')&&($rr ne 'Regexp')&&($rr ne 'ARRAY')&&($rr ne 'HASH') ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, the type of input parameter 'packages' must be one of scalar string, Regexp, ARRAY or HASH and not '$rr'."); re...
# NOTE: that all those 'packages', if any,
# will be non-lazily even if 'lazy' is 1
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub open_app {
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 ($package);
if( ! exists($params->{'package'}) || ! defined($package=$params->{'package'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' is required."); return undef }
if( (ref($package)ne'') && (ref($package)ne'Regexp') ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, the type of input parameter 'package' must be a scalar string (the package name) or a Regexp object (compiled regex via ".'qr//'...
# optional activity, else we will see if we find one
my $activity = (exists($params->{'activity'}) && defined($params->{'activity'})) ? $params->{'activity'} : undef;
my $force_reload = (exists($params->{'force-reload-apps-list'}) && defined($params->{'force-reload-apps-list'})) ? $params->{'force-reload-apps-list'} : 0;
my $lazy = (exists($params->{'lazy'}) && defined($params->{'lazy'})) ? $params->{'lazy'} : 1;
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub close_app {
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 ($package);
if( ! exists($params->{'package'}) || ! defined($package=$params->{'package'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' is required."); return undef }
if( (ref($package)ne'') && (ref($package)ne'Regexp') ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, the type of input parameter 'package' must be a scalar string (the package name) or a Regexp object (compiled regex via ".'qr//'...
# optional activity, else we will see if we find one
my $activity = (exists($params->{'activity'}) && defined($params->{'activity'})) ? $params->{'activity'} : undef;
my $force_reload = (exists($params->{'force-reload-apps-list'}) && defined($params->{'force-reload-apps-list'})) ? $params->{'force-reload-apps-list'} : 0;
my $lazy = (exists($params->{'lazy'}) && defined($params->{'lazy'})) ? $params->{'lazy'} : 1;
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# use pgrep()
sub pidof {
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 }
if( ! exists($params->{'name'}) || ! defined($params->{'name'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, missing parameter 'name' is required, it must be the exact app name, if you do not have the exact name then use 'pgre...
my @cmd = ('pidof', $params->{'name'});
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 not found it exits with 1
if( $res->[0] == 1 ){ return -1 } # nothing matched
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# on error it returns undef
sub pgrep {
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 }
if( ! exists($params->{'name'}) || ! defined($params->{'name'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, missing parameter 'name' is required, it must be the exact app name, if you do not have the exact name then use 'pgre...
my $dont_show_command_name = (exists($params->{'dont-show-command-name'}) && defined($params->{'dont-show-command-name'}) && ($params->{'dont-show-command-name'}>0)) ? 1 : 0;
# -f will search the full command name
# -l will include the command name which is the default
my @cmdparams = ('-f');
push(@cmdparams, '-l') unless $dont_show_command_name;
my @cmd = ('pgrep', @cmdparams, $params->{'name'});
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub tap {
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 (@position, $m);
if( exists($params->{'position'}) && defined($m=$params->{'position'}) ){
@position = ($m->[0], $m->[1]);
} elsif( exists($params->{'bounds'}) && defined($m=$params->{'bounds'}) ){
@position = ( int(($m->[1]->[0] + $m->[0]->[0])/2), int(($m->[1]->[1] + $m->[0]->[1])/2) );
} else { $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'position' (as ['x','y']) or 'bounds' (as [lefttopX,lefttopY],[bottomrightX,bottomrighY]) was not specified."); return 1 }
my @cmd = ('input', 'tap', @position);
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub input_text {
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 (@position, $m);
if( exists($params->{'position'}) && defined($m=$params->{'position'}) ){
@position = ($m->[0], $m->[1]);
} elsif( exists($params->{'bounds'}) && defined($m=$params->{'bounds'}) ){
@position = ( int(($m->[1]->[0] + $m->[0]->[0])/2), int(($m->[1]->[1] + $m->[0]->[1])/2) );
} else { $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'position' (as ['x','y']) or 'bounds' (as [lefttopX,lefttopY],[bottomrightX,bottomrighY]) was not specified."); return 1 }
# optional text, else we send just '' (but we clicked on it)
my $text = (exists($params->{'text'}) && defined($params->{'text'})) ? $params->{'text'} : '';
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub clear_input_field {
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 (@position, $m);
if( exists($params->{'position'}) && defined($m=$params->{'position'}) ){
@position = ($m->[0], $m->[1]);
} elsif( exists($params->{'bounds'}) && defined($m=$params->{'bounds'}) ){
@position = ( int(($m->[1]->[0] + $m->[0]->[0])/2), int(($m->[1]->[1] + $m->[0]->[1])/2) );
} else { $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'position' (as ['x','y']) or 'bounds' (as [lefttopX,lefttopY],[bottomrightX,bottomrighY]) was not specified."); return 1 }
# first tap on the text edit widget at the specified coordinates to get focus
if( $self->tap($params) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to tap on the position of the recipient of the text input"); return 1 }
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# I wish they would load ps info from a string rather than running their own `ps`
sub list_running_processes {
my ($self, $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 ".'device_connected()'." before calling this."); return undef }
my $filename = exists($params->{'filename'}) && defined($params->{'filename'}) ? $params->{'filename'} : undef;
my $extrafields = exists($params->{'extra-fields'}) && defined($params->{'extra-fields'}) ? $params->{'extra-fields'} : [];
my ($FH, $tmpfilename) = tempfile(CLEANUP=>$self->cleanup);
close $FH;
# WARNING, you need to wake up the phone before dumping !!!!
my $devicefile = File::Spec->catfile('/', 'data', 'local', 'tmp', $$.'.csv');
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# 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 }
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# 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'){
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub list_physical_displays {
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', 'SurfaceFlinger', '--display-id');
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 }
my $content = $res->[1];
my %ids;
while( $content =~ /^(Display\s+(.+?)\s+.+?)$/gsm ){
$ids{$2} = $1
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub dump_current_screen_shot {
my ($self, $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 ".'device_connected()'." before calling this."); return undef }
my $filename = exists($params->{'filename'}) && defined($params->{'filename'}) ? $params->{'filename'} : undef;
my $FH;
if( ! defined $filename ){
($FH, $filename) = tempfile(CLEANUP=>$self->cleanup);
close $FH;
}
# optional display-id (TODO: confirm that this display id is valid with
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub dump_current_screen_video {
my ($self, $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 ".'device_connected()'." before calling this."); return 1 }
my $filename = exists($params->{'filename'}) && defined($params->{'filename'}) ? $params->{'filename'} : undef;
if( ! defined $filename ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'filename' is not specified, an output filename must be specified."); return 1 }
my @options;
# optional duration or default of 10 seconds. (Android default is 180 which is stupidly huge for us)
if( exists($params->{'time-limit'}) && defined($params->{'time-limit'}) ){
push @options, '--time-limit', $params->{'time-limit'}
} else { push @options, '--time-limit', '10' }
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub wake_up {
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 = qw/input keyevent KEYCODE_WAKEUP/;
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 }
return 0; # success
}
# goes to the home screen
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub home_screen {
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 }
$self->wake_up();
my @cmd = qw/am start -a android.intent.action.MAIN -c android.intent.category.HOME/;
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 }
return 0; # success
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub next_screen {
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 }
if( $self->swipe({'direction' => 'right', dt => 100}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'swipe()'." has failed."); return undef }
return 0; # success
}
# It swipes left basically
# it returns 0 on success, 1 on failure
# it needs that connect_device() to have been called prior to this call
sub previous_screen {
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 }
if( $self->swipe({'direction' => 'left', dt => 100}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'swipe()'." has failed."); return undef }
return 0; # success
}
# it returns 0 on success, 1 on failure
# it needs that connect_device() to have been called prior to this call
# the left-triangle button (see http://developer.android.com/reference/android/view/KeyEvent.html)
sub navigation_menu_back_button {
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 = ('input', 'keyevent', 'KEYCODE_BACK');
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 }
return 0; # success
}
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# the round button, it goes to home (see http://developer.android.com/reference/android/view/KeyEvent.html)
sub navigation_menu_home_button {
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 = ('input', 'keyevent', 'KEYCODE_HOME');
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 }
return 0; # success
}
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# (see http://developer.android.com/reference/android/view/KeyEvent.html)
sub navigation_menu_overview_button {
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 = ('input', 'keyevent', 'KEYCODE_APP_SWITCH');
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 }
return 0; # success
}
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub find_all_apps_roundabout_way {
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 }
# go to home, swipe up and all apps will be revealed
# then dump the UI
# then swipe down
if( $self->home_screen() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'home_screen()'." has failed."); return undef }
usleep(300);
if( $self->swipe({'direction'=>'up'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'swipe()'." has failed."); return undef }
usleep(300);
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
# it needs that connect_device() to have been called prior to this call
sub open_app_roundabout_way {
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 $apps = $self->apps_roundabout_way();
if( (! defined($apps))
|| (exists($params->{'force-reload-apps-list'}) && defined($params->{'force-reload-apps-list'}) && ($params->{'force-reload-apps-list'}>0))
){
if( ! defined $self->find_all_apps_roundabout_way() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to load list of installed apps, call to ".'find_all_apps_roundabout_way()'." has failed."); return undef }
$apps = $self->apps_roundabout_way();
}
my ($position, $appname, $bounds, $name);
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
? $params->{'adb-path-to-executable'}
: $self->confighash->{'adb'}->{'path-to-executable'}
;
if( ! -x $pathtoadb ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, specified adb executable '${pathtoadb}' is not an executable or does not exist."); return 1; }
if( ! defined ($self->{'_private'}->{'Android::ADB'}=Android::ElectricSheep::Automator::ADB->new(
path => $pathtoadb,
verbosity => $self->verbosity
)) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'Android::ElectricSheep::Automator::ADB->new()'." has failed (path to executable was specified as '$pathtoadb')."); return 1; }
}
# does caller have a device connected to the desktop and wants us to
# target it?
# if just one device, we don't need serial etc:
my $device_params;
if( exists($params->{'device-is-connected'}) && defined($params->{'device-is-connected'}) && ($params->{'device-is-connected'}>0) ){
# just one device, we don't need serial of the device
$device_params = {};
} elsif( exists($params->{'device-serial'}) && defined($params->{'device-serial'}) ){
$device_params = {'serial' => $params->{'device-serial'}};
} elsif( exists($params->{'device-object'}) && defined($params->{'device-object'}) ){
$device_params = {'device-object' => $params->{'device-object'}};
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
return $sl;
}
# only pod below
=pod
=encoding utf8
=head1 NAME
Android::ElectricSheep::Automator - Do Androids Dream of Electric Sheep? Smartphone control from your desktop.
=head1 VERSION
Version 0.09
=head1 WARNING
Current distribution is extremely alpha. API may change.
=head1 SYNOPSIS
The present package fascilitates the control
of a USB-debugging-enabled
Android device, e.g. a real smartphone,
or an emulated (virtual) Android device,
from your desktop computer using Perl.
It's basically a thickishly-thin wrapper
to the omnipotent Android Debug Bridge (adb)
program.
B<Note that absolutely nothing is
installed on the connected device,
neither any of its settings will be modified by this package>.
See L</WILL ANYTHING BE INSTALLED ON THE DEVICE?>.
use Android::ElectricSheep::Automator;
my $mother = Android::ElectricSheep::Automator->new({
# optional as there is a default, but you may have
# problems with the location of the adb executable
'configfile' => $configfile,
'verbosity' => 1,
# we already have a device connected and ready to control
'device-is-connected' => 1,
});
# find the devices connected to desktop and set one.
my @devices = $mother->adb->devices;
$mother->connect_device({'serial' => $devices->[0]->serial})
or die;
# no device identification is required for the method call
# if there is only one connected device:
$mother->connect_device() if scalar(@devices)==0;
# Go Home
$mother->home_screen() or die;
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
If no configuration is specified, then a default
configuration will be used. In this case please
specify B<C<adb-path-to-executable>> to point
to the location of C<adb>. Most likely
the default path will not work for you.
=item * B<C<adb-path-to-executable>>
optionally specify the path to the C<adb> executable in
your desktop system. This will override the setting
C< 'adb'-E<gt>'path-to-executable' > in the configuration,
if it was provided. Use this option if you are not
providing any configuration and so the default configuration
will be used. But it will most likely fail because of this
path not being correct for your system. So, if you are going
to omit providing a configuration and the default configuration
will be used do specify the C<adb> path via this option (but you
don't have to and your mileage may vary).
=item * B<C<device-serial>> or B<C<device-object>>
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
screen size, resolution, orientation, etc.
And also allow use of
functionality which needs communicating with a device
like L</swipe($params)>, L</home_screen($params)>,
L</open_app($params)>, etc.
After instantiation, you can use the
method L</connect_device($params)> and
L</disconnect_device()> for conveying
this information to the module.
Also note that if there are
more than one devices connected to the desktop, make sure
you specify which one with the C<device> parameter.
Default value is 0.
=item * B<C<logger>>
optionally specify a logger object
to be used (instead of creating a fresh one). This object
must implement these methods: C<info()>, C<warn()>, C<error()>.
L<Mojo::Log> fits perfectly.
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
=item * In this module parameters to functions are passed as a HASH_REF.
Functions return back objects, ARRAY_REF or HASH_REF.
=back
=head2 B<C<devices()>>
Lists all Android devices connected to your
desktop and returns these as an ARRAY_REF which can be empty.
It returns C<undef> on failure.
=head2 B<C<connect_device($params)>>
Specifies the current Android device to control. Its use is
required only if you have more than one devices connected.
C<$params> is a HASH_REF which should contain exactly
one of the following:
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
and returns back an L<Android::ElectricSheep::Automator::DeviceProperties>
object containing this information, for example screen size,
resolution, serial number, etc.
It returns L<Android::ElectricSheep::Automator::DeviceProperties>
object on success or C<undef> on failure.
=head2 B<C<connect_device()>>
It signals to our object that there is now
a device connected to the desktop and its
enquiry and subsequent control can commence.
If this is not called and neither C<device-is-connected =E<gt> 1>
is specified as a parameter to the constructor, then
the functionality will be limited and access
to functions like L</swipe($params)>, L</open_app($params)>, etc.
will be blocked until the caller signals that
a device is now connected to the desktop.
Using L</connect_device($params)> to specify which device
to target in the case of multiple devices
connected to the desktop will also call this
method.
This method will try to enquire the connected device
about some of its properties, like screen size,
resolution, orientation, serial number etc.
This information will subsequently be available
via C<$self-E<gt>>device_properties()>.
It returns C<0> on success, C<1> on failure.
=head2 B<C<disconnect_device()>>
Signals to our object that it should consider
that there is currently no device connected to
the desktop (irrespective of that is true or not)
which will block access to L</swipe($params)>, L</open_app($params)>, etc.
=head2 B<C<device_properties()>>
It returns the currently connected device properties
as a L<Android::ElectricSheep::Automator::DeviceProperties>
object or C<undef> if there is no connected device.
The returned object is constructed during a call
to L</find_current_device_properties($params)>
which is called via L</connect_device($params)> and will persist
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
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.
It is very easy to get an emulated Android device running on any OS.
So, prior to C<make livetest> make sure you have an android
emulator up and running with, for example,
C<emulator -avd Pixel_2_API_30_x86_> . See section
L<Android Emulators> for how to install, list and run them
buggers.
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
This steals the focus and sometimes it causes
the tests to fail.
=head1 PREREQUISITES
=head2 Android Studio
This is not a prerequisite but it is
highly recommended to install it
(from L<https://developer.android.com/studio>)
on your desktop computer because it contains
all the executables you will need,
saved in a well documented file system hierarchy,
which can then be accessed from the command line.
You will not be using the IDE or anything, just
the accompaniying binaries and libraries it comes with.
Additionally, Android Studio offers possibly the
easiest way to create Android Virtual Devices (AVD) which emulate
an Android phone of various specifications, phone models and sizes,
API levels, etc.
I mention this because one can install apps
on an AVD and control them from your desktop
as long as you are able to receive sms verification
codes from a real phone. Perhaps you will need an Android
emulator image which comes with Google Play Services,
if you are installing apps from their store.
This is great for
experimenting without plugging in your real
smartphone on your desktop.
The bottom line is that by installing Android Studio,
you have all the executables you need for running things
from the command line and, additionally, you have
the easiest way for creating Android
Virtual Devices, which emulate Android devices: phones,
tablets, automotive displays. Once you have this set up, you
will not need to open Android Studio ever again unless you
want to update your kit. All the functionality
will be accessible from the command line.
=head2 ADB
Android Debug Bridge (ADB) is the program
which communicates with your smartphone or
an Android Virtual Device from
your desktop (Linux, osx and the unnamed C<0$>).
If you do not want to install Android Studio, the C<adb> executable
is included in the package called
"Android SDK Platform Tools" available from
the Android official site, here:
L<https://developer.android.com/tools/releases/platform-tools#downloads>
You will need the C<adb> executable to be on your path
or specify its fullpath in the configuration file
supplied to L<Android::ElectricSheep::Automator>'s constructor.
lib/Android/ElectricSheep/Automator.pm view on Meta::CPAN
C<Settings-E<gt>System-E<gt>Developer> and turn it off.
It is highly advised to turn off Developer Mode
for everyday use of your phone.
B<Do not connect your smartphone
to public WIFI networks with Developer Mode ON>.
B<Do not leave home with Developer Mode ON>.
Once you have enabled "USB Debugging", you have
two options for making your device visible to
your desktop and, consequently, to ADB and to this module:
=over 4
=item * connect your android device via a USB cable
to your desktop computer. I am not sure if you also
need to tap on the USB charging options and allow
"Transfer Files".
=item * connect your device to the same WIFI network
as your desktop computer. Then follow instructions
from, e.g., here L<https://developer.android.com>.
This requires a newer Android version.
=back
=head2 Android Emulators
It is possible to do most things your
smartphone does with an Android Virtual Device.
You can install apps on the the virtual device which
you can register by supplying your real smartphone
number.
List all virtual devices currently available
in your desktop computer, with C<emulator -list-avds>
which outputs something like:
Pixel_2_API_27_x86_
Pixel_2_API_30_x86_
Start a virtual device with C<emulator -avd Pixel_2_API_30_x86_>
And hey, you have an android phone running on your
desktop in its own space, able to access the network
but not the telephone network (no SIM card).
It is possible to create a virtual device
from the command line.
But perhaps it is easier if you download Android Studio
from: L<https://developer.android.com/studio> and follow
the setup there using the GUI. You will need to do this just
once for creating the device, you can then uninstall Android Studio.
Android Studio will download all the
lib/Android/ElectricSheep/Automator/Plugins/Apps/Viber.pm view on Meta::CPAN
return {};
}
# only pod below
=pod
=encoding utf8
=head1 NAME
Android::ElectricSheep::Automator::Plugins::Apps::Viber - Control the Viber app from your desktop via the ElectricSheep Automator
=head1 VERSION
Version 0.09
=head1 WARNING
Current distribution is extremely alpha. API may change.
=head1 SYNOPSIS
An L<Android::ElectricSheep::Automator> plugin which
interacts with the Viber app from the desktop.
use Android::ElectricSheep::Automator::Plugins::Apps::Viber;
my $viber = Android::ElectricSheep::Automator::Plugins::Apps::Viber->new({
'configfile' => $configfile,
'verbosity' => 1,
# we already have a device connected and ready to control
'device-is-connected' => 1,
});
lib/Android/ElectricSheep/Automator/Plugins/Apps/Viber.pm view on Meta::CPAN
=head1 MOTIVATION
I wanted to send viber messages from my beloved Linux Desktop
using the command line. I did not want to install Viber app on
my smartphone. I do not use a smartphone except for
reading the news.
=head1 INSTALLING VIBER
Firstly, I installed an Android emulator
in my desktop computer, as described here
L<https://metacpan.org/pod/Android::ElectricSheep::Automator#Android-Emulators>
Note that some Android emulators do not allow access to
Google App Store (whatever is called). You can only install apps
via third party which is not very safe (even for your emulator).
See this for how to enable the App Store:
L<https://stackoverflow.com/questions/71815181/how-can-i-get-google-play-to-work-on-android-emulator-in-android-studio-bumblebe>
Fortunately, Viber offers a version of its app from its website,
xt/live/110-adb-connect-device.t view on Meta::CPAN
ok(-f $configfile, "config file exists ($configfile).") or BAIL_OUT;
my $mother = Android::ElectricSheep::Automator->new({
'configfile' => $configfile,
'verbosity' => $VERBOSITY,
});
ok(defined($mother), 'Android::ElectricSheep::Automator->new()'." : called and got defined result.") or BAIL_OUT;
# this mother should not be connected
is($mother->is_device_connected(), 0, 'Android::ElectricSheep::Automator->new()'." : called and device is not connected as expected.") or BAIL_OUT;
# get all devices connected to the desktop
my @devices = $mother->adb->devices;
# and make sure we have at least one device connected to the desktop
ok(scalar(@devices)>0, "there are ".@devices." connected devices on the desktop") or BAIL_OUT("At least one device must be connected to the desktop.");
diag "Found connected android device(s) : ".Android::ElectricSheep::Automator::devices_toString(\@devices);
if( 1 == scalar @devices ){
# now create a new mother with just the one connected device
$mother = Android::ElectricSheep::Automator->new({
'configfile' => $configfile,
'verbosity' => $VERBOSITY,
'device-is-connected' => 1,
});
ok(defined($mother), 'Android::ElectricSheep::Automator->new()'." : called and got defined result.") or BAIL_OUT;