Android-ElectricSheep-Automator

 view release on metacpan or  search on metacpan

META.json  view on Meta::CPAN

{
   "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.70, CPAN::Meta::Converter version 2.150010",
   "license" : [
      "artistic_2"
   ],
   "meta-spec" : {
      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",

META.yml  view on Meta::CPAN

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

README  view on Meta::CPAN

NAME

    Android::ElectricSheep::Automator - Do Androids Dream of Electric
    Sheep? Smartphone control from your desktop.

VERSION

    Version 0.06

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 needs to be specified if just one:
        $mother->connect_device() if scalar(@devices)==0;
    
        # Go Home
        $mother->home_screen() or die;
    
        # swipe up/down/left/right

README  view on Meta::CPAN

      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

README  view on Meta::CPAN

    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 info(), warn(), error(). For
      example Mojo::Log.

    logfile

README  view on Meta::CPAN


    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:

README  view on Meta::CPAN

      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(), open_app(), 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()", "open_app()", 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()" which is called
      via "connect_device($params)" and will persist for the duration of
      the connection. However, after a call to "disconnect_device()" this

README  view on Meta::CPAN

      --configfile config/myapp.conf --device Pixel_2_API_30_x86_>>

TESTING

    The normal tests under t/, initiated with make test, 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 xt/live, initiated with make livetest, require an
    Android device connected to your desktop on which you installed this
    package and on which you are doing the testing. This suffices to be an
    emulator. It can also be a real Android phone but 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.

    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.

README  view on Meta::CPAN

    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
    (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.

    Additionally, Android Studio offers possibly the easiest way to create
    Android Virtual Devices (AVD) which emulate an Android phone of various
    specifications. 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. This is great for
    experimenting without pluggin 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.

README  view on Meta::CPAN

    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

README.md  view on Meta::CPAN

# NAME

Android::ElectricSheep::Automator - Do Androids Dream of Electric Sheep? Smartphone control from your desktop.

# VERSION

Version 0.06

# 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 needs to be specified if just one:
    $mother->connect_device() if scalar(@devices)==0;

    # Go Home
    $mother->home_screen() or die;

    # swipe up/down/left/right

README.md  view on Meta::CPAN


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

README.md  view on Meta::CPAN

    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 `info()`, `warn()`, `error()`. For
    example [Mojo::Log](https://metacpan.org/pod/Mojo%3A%3ALog).

README.md  view on Meta::CPAN

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:

README.md  view on Meta::CPAN

    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()`, `open_app()`, 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()"](#swipe), ["open\_app()"](#open_app), 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()"](#find_current_device_properties)
    which is called via ["connect\_device($params)"](#connect_device-params) and will persist

README.md  view on Meta::CPAN

# TESTING

The normal tests under `t/`, initiated with `make test`,
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 `xt/live`, initiated with
`make livetest`, require
an Android device connected to your desktop on which
you installed this package and on which you are doing the testing.
This suffices to be an emulator. It can also be a real Android
phone but 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.

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

README.md  view on Meta::CPAN

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
(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.

Additionally, Android Studio offers possibly the
easiest way to create Android Virtual Devices (AVD) which emulate
an Android phone of various specifications.
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. This is great for
experimenting without pluggin 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.

README.md  view on Meta::CPAN

`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 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 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];
	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.06

=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 needs to be specified if just one:
    $mother->connect_device() if scalar(@devices)==0;

    # Go Home
    $mother->home_screen() or die;

    # swipe up/down/left/right

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 C<info()>, C<warn()>, C<error()>. For
example L<Mojo::Log>.

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

=over 1

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

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

=item 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 C<swipe()>, C<open_app()>, 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.

=item 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()>, L</open_app()>, etc.

=item 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()>
which is called via L</connect_device($params)> and will persist

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

=head1 TESTING

The normal tests under C<t/>, initiated with C<make test>,
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 C<xt/live>, initiated with
C<make livetest>, require
an Android device connected to your desktop on which
you installed this package and on which you are doing the testing.
This suffices to be an emulator. It can also be a real Android
phone but 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.

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

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
(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.

Additionally, Android Studio offers possibly the
easiest way to create Android Virtual Devices (AVD) which emulate
an Android phone of various specifications.
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. This is great for
experimenting without pluggin 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.06

=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,
    });

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;



( run in 0.830 second using v1.01-cache-2.11-cpan-299005ec8e3 )