android-automate

1.0.1 • Public • Published

node-android-automate

Visit my Blog to get in touch or to see demos of this and much more.

Contents

Overview

This node module allows you to automate tasks on your Android device using simple, flexible function chains.

If you use this module, please do get in touch and let me know what you built and how you got on.

The module is available as an official node package manager repo:

https://www.npmjs.com/package/android-automate

Here is an example of a function chain which opens the default browser, visits a website, scroll down a bit, takes a screen shot, crops and greyscales it and then reads any text therein.

device.browse('https://en.wikipedia.org')
      .wait(2500)
      .scroll(50)
      .wait(1000)
      .screen('example')
      .shoot()
      .crop({ x: 50, y: 500, w: 1300, h: 150 })
      .greyscale()
      .read();

This module offers an extensive range of such functions allowing for the manipulation and control of any connected Android device. All the available methods are fully documented here.

It should be noted that many of the available methods are asynchronous in nature, in that their execution is normally independent of the main program flow. Obviously this is at odds with the process of placing them into a function chain. The core engine within android-automate handles this conflict by strictly sequencing all operations in the order in which they were specified. This is true even when those operations appear on separate lines within the housing script.

Demonstration

You will find lots of small examples of using android-automate within in this document. In addition to these, you can also examine one large demonstration application - a program which automatically plays the popular mobile puzzle game FlowFree.

https://github.com/pete-rai/flowfree-player

License

This project is available under the MIT license. Please respect the terms of the license.

Karmaware

This software is released with the karmaware tag

Disclaimer

I've done best efforts testing on a range of devices. If you find any problems, please do let me know by raising an issue here. Better still, create a fix for the problem too and drop in the changes; that way everyone can benefit from it.

Before You Start

Before you get going with your own scripts, there are some steps you must complete to ensure that your device is correctly installed, configured and connected.

Dependencies

This module has a dependency on presence of Android Platform Tools. There are extensive notes on how to install these tools on a range of host machines at the Android Developer website:

https://developer.android.com/studio/releases/platform-tools

Specifically, the module assumes the presence of the Android Debug Bridge adb. This tool should either be on the execution path of the hosting machine, or the path to it should be specified to android-automate (see the configuration section below).

IF you want to use android-automate to read text from the screen of your Android device using the Screen.read method, THEN this module also has a dependency on presence of the Tesseract OCR engine. There are extensive notes on how to install this engine on a range of host machines at the Tesseract User Manual website:

https://tesseract-ocr.github.io/tessdoc/Home.html

The Tesseract OCR command line tool should either be on the execution path of the hosting machine, or the path to it should be specified to android-automate (see the configuration section below).

Note: That both adb and tesseract are external dependencies. That is, that they are outside of the node package management area. These applications are invoked directly, rather than via the node_modules mechanism. Please do test that these applications are installed and operational before using android-automate.

Connecting Devices

Any Android compatible device can be connected to android-automate to allow access and control. This can be achieved in either a wired or wireless mode. The steps to accomplish a connection are outlined over dozens of articles across the web. Perhaps the clearest is from the Android team itself:

https://developer.android.com/studio/command-line/adb

It is important that you test the connection to your device before you start using android-automate. This can be done by executing the command adb devices as detailed in the linked article above. Only move on to using android-automate after this step is complete and the output of adb devices includes your target Android device.

Configuration

There is no reason to configure anything to use android-automate; you can use if out-of-the-box and it will deliver good service. However, there are a few parameters which you can tweak to slightly modify its behaviour. If you want to set any of this configuration you should utilise the services of the dotenv node package. This will read your preferred values from an .env file and load them into an environment space from where android-automate will have access to them. Note: Make sure that you load dotenv in your code before you require android-automate.

Option Default Description
ADB_PATH The path to your adb executable. If not present, then adb should be on your execution path.
TESSERACT_PATH The path to your tesseract executable. If not present, then tesseract should be on your execution path.
TESSERACT_OPTIONS -l eng --oem 1 --psm 11 The tesseract options used to read on-screen text. See the Tesseract documentation for more detail. Note the default options are optimised to read English language text.

If you decide to modify any of these parameters, be sure to use the exact same name as specified in the table above.

Using the Module

In this section, we outline all the objects and functions which you can use as part of android-automate. We also demonstrate these via a series of small examples, to help you get started with your own scripts. This section assumes that you have read and completed the previously outlined steps of installing and testing the dependencies, setting your preferred configuration and connecting your target device.

System Objects

There are two system objects which can be used part of android-automate function chains: device and screen. The device object is the main player and has chainable functions to control the device itself: tap, press, browse, scroll, etc. The screen object represents a given screenshot taken from the device and has chainable functions to manipulate this image: shoot, crop, greyscale, colors, read, etc.

You can create a device instance and then you can chain functions on from there:

let device = new AndroidAutomate.Device();
device.browse(url).wait(2500).scroll(50).wait(1000);

You create screen instances by chaining-on a screen function onto an existing device object:

device.browse(url).wait(2500).scroll(50).wait(1000).screen('screen_0');

The most common first thing to do to a new screen instance is to take a screenshot of the current screen on the connected device:

device.browse(url).wait(2500).scroll(50).wait(1000).screen('screen_0').shoot();

It should be noted that, once you create a screen object, the context of the chain transfers to screen methods. This means you can only chain-on screen methods from that point on:

// the next line won't work, because scroll is a device method
device.browse(url).wait(2500).scroll(50).wait(1000).screen('screen_0').shoot().scroll();

// but this line will work, since greyscale is a screen method
device.browse(url).wait(2500).scroll(50).wait(1000).screen('screen_0').shoot().greyscale();

However you can, at any point, switch a function chain back to the device context and then chain-on device methods again:

// now this will work, as the device context has been switched back in
device.browse(url).wait(2500).scroll(50).wait(1000).screen('screen_0').shoot().device().scroll();

When you create a screen object and take a screenshot from the device into it, a PNG image file is created on the hosting machine. This will be located in the directory which you passed-in as the first parameter to the newly create device object (or the current directory if you did not specify an alternative location). For example, in the code above, a local file will be created in the current directory called screen_0.png. Subsequent screen operations will then be performed in context of this file.

If you later in a chain want to return to a previously created image, you can simply refer to it by its name. Then subsequent operations will be performed on this earlier image.

// this will take two screenshots and greyscale the first of these
device.screen('screen_0').shoot().screen('screen_1').shoot().screen('screen_0').greyscale();

Some methods on both the device and screen objects return values. For example, Device.size returns an object which describes the size of the device's screen; Screen.read return text read from the specified area of the screen image. Functions such as these, which return values, cannot be used to chain-on further operations. They represent the end of their hosting chain.

// this next line ends in a value function, which terminates the chain of operations
let size = await device.browse(url).wait(2500).size();

// but you can always just carry on with a new chain on later lines
device.scroll(50).wait(1000);

Once you get going with devices and screens, you will quickly see the value that function chaining brings. You should also examine the small examples shown later in this document to learn more about how to exploit the features of android-automate.

Complete Function List

Here is a complete list of all the functions which are available within android-automate. You will find an example of each of these in the later section of this document. Use these examples to understand on object formats for the function inputs and outputs.

Device

These are all the functions of the Device class. Please note the default values of some parameters, if you omit them.

Constructor

This is how you create a new device instance:

Function Description
new Device(temp='.', tracing=false) Creates a new device instance. You can optionally specify a location for temporary files (including screen images) and whether or not tracing is switched on from the start.
Chainable

These are the chainable functions:

Function Description
browse(url) Starts the default browser and navigates to the given URL.
camera(video=false) Opens the device camera - by default in stills mode, but use the parameter to open it in video mode.
input(text) Inputs the given text into whatever field has the current input focus.
press(keycode) Presses a key using an Android keycode. See below for more details of these.
scroll(percent=50, down=true, speed=300) Scrolls the screen using a swipe - by default it swipes 50% of the screen, in 300 milliseconds in a upward direction, leading to a downward scroll. Note: It is a good idea to chain a wait after a scroll, in order to allow for the visual scroll to complete on the device.
start(app) Starts the given application, using its internal Android application name.
swipe(from, to, speed=300) Swipes the screen from the given point, to the given point, at the given speed in milliseconds.
swipes(points, pause=300, speed=300) swipes the screen in a path connected by all the given points, at given speed in milliseconds, with the given pauses in milliseconds.
tap(point, duration=0) Taps the screen at the given point, for the given duration.
trace(text) Outputs the given text into the trace stream.
tracing(mode) Switches tracing on or off.
wait(millisecs) Pauses the the functions chain for the given number of milliseconds.

The press function takes an Android keycode as its parameter. There is a complete list of such keycodes included within the Keys enumeration, for example Keys.CAMERA or Keys.HOME. You can find a complete set of Android keycodes using the link below:

https://github.com/pete-rai/node-android-automate/blob/main/lib/keys.js

Note that the function swipes is a simple wrapper over calling swipe multiple times. You need to send at least two points to this method for it to do anything useful.

Information

These are the information delivering functions, which hence are not chainable:

Function Description
battery() Returns the current battery level as an integer percentage.
connected() Returns a boolean to indicate if the device is connected.
shell(command) Executes an adb shell command and returns the output. See below for more details of these.
size() Returns an object describing the size of the device screen.
version() Returns the current major Android version of the device as an integer.

This module uses adb shell commands to achieve the functionality which it makes available. However, if there is something else that you want to do which is not represented here, you can call the adb shell infrastructure directly using the shell function. You can find more information about these shell comments using the link below:

See https://developer.android.com/studio/command-line/adb#shellcommands

If you do use this, perhaps also raise an issue on android-automate if you believe that this is because there is something missing in our function set.

Context

These are the functions, which change the context of the chain over to the Screen class:

Function Description
screen(name) Switches to a screen context with the given name.

Screen

These are all the functions of the Screen class. Please note the default values of some parameters, if you omit them:

Constructor

You cannot create a new screen instance directly; this can only be done using the Device.screen function, documented above.

Chainable

These are the chainable functions:

Function Description
crop(section) Crops the screen image using the given rectangle.
greyscale() Greyscales the screen image. Often useful ahead of OCR text reading.
save(file) Saves the screen image to the given file. Note you can use a variety of image formats here.
sharpen() Sharpens the screen image. Often useful ahead of OCR text reading.
shoot() Takes a screenshot of the current device screen.
threshold(value=128) Makes a stark, two color image based on the threshold value between 0 and 255. Often useful ahead of OCR text reading.
trace(text) Outputs the given text into the trace stream.
tracing(mode) Switches tracing on or off.
wait(millisecs) Pauses the the functions chain for the given number of milliseconds.
Information

These are the information delivering functions, which hence are not chainable:

Function Description
colors(points) Returns the RGB color values at each of the specified points inside the current screen image. If you send in RBG values it also checks that your expectations match what is on the screen. See the notes below on this.
contains(other, max=1) Returns the number of times the given other screen appears anywhere within the current screen image - optionally once or many times. See the notes below on this.
file() Returns the name of the underlying image filename.
read(clean=true) Returns OCR'd text from within the given screen, optionally cleaning its white space.
sharp() Returns the name of the underlying sharp class instance. See below for explanation.

You can see that andriod-automate offers a range of functions to manipulate the screen image. These are most often useful to prepare a screenshot so that it can be better read by the OCR engine. Reading text from within an image can be tricky and error prone. It is often useful to manipulate the image to make it as easy as possible for the OCR engine to read the text. Techniques that are often applied here include:

  • crop - to isolate the area of the screen where the text to be read can be found.
  • greyscale - to remove colors and make it easier to discern text from other elements.
  • sharpen - to improve the edges of the text, so it can be more easily identified.
  • threshold - to make a stark separation between text and background.

The two methods colors and contains are very good for identifying screens and for finding the location of on-screen elements, such as buttons or sprites. These are very powerful functions and you are encouraged to explore the example use of them in the later section of this document.

Whilst andriod-automate offers some image preparing methods, you may well find that you want to do something else to manipulate the screen image. This is facilitated by two methods on the screen class: sharp and file. Internally andriod-automate uses the node Sharp module to perform image actions. You can ask for the raw sharp image, so that you can then perform your own operations using it.

// get the raw sharp object
let sharp = await device.screen('example_0').shoot().sharp();

// perform some sharp operations directly
await sharp.greyscale().modulate({ brightness: 2 }).toFile('example_1.png');

// load this new image within a screen class instance
let text = await device.screen('example_1').read();

If you don't want to use the sharp library, you can use the Screen.file method to get the name of the raw image file, which can then process in your image library of choice.

Context

These are the functions, which change the context of the chain to either a new Screen or back to the Device class:

Function Description
clone(name) Makes a clone of the current screen and switches to its context.
delete() Deletes the current screen and switches the context back to the device context.
device() Switches the context back to the device.
screen(name) Switches the context to a new or existing other screen instance.

Be sure to await the result of the Screen.delete function. This will be chained into the sequence, but the switch of context will happen too early with you call it without awaiting (or outside of a promise chain).

Example Usage

One great way to learn how to use android-automate is though a series of simple examples. Here we will show all the functions in actions in small bite-size code chunks - which you can use as templates for your own scripts.

All the examples below assume that you have declared the module android-automate in file scope, that you have installed it using the node package manager and that you have followed the Before You Start steps outlined above.

const AndroidAutomate = require('android-automate');

This basic example, simply tests whether the device is correctly connected, such that it is reachable by the android-automate module.

async function example_0 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    let connected = await device.connected();

    console.log(`The device is ${ connected ? '' : 'not ' }connected`);
}

Here is the output for this function when the device is connected and tracing is switched off:

The device is connected

And the same when tracing is switched on:

2021-10-02 08:28:47: querying version...
2021-10-02 08:28:47: queried as v10

The device is connected

In all further examples, we will show the output with tracing switched on. Here is what you will see if the device is not correctly connected:

2021-10-02 08:37:19: querying version...

The device is not connected

Whenever the device is not connected, the module functions will return empty results - the screen size will be 0x0, the battery level will be 0, the version will be 0, chainable methods will do nothing, etc. The module will not generate exceptions, but it will log these issues if you have tracing switched on. We will not show any further device not connected scenarios in these examples.

The example function shown above is using the async / await syntax. This representation arguably leads to the clearest code. However, if you prefer, you can easily convert any of the examples over to Promise syntax:

function example_00 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    device.connected()
          .then(connected => console.log(`The device is ${ connected ? '' : 'not ' }connected`));
}

In all further examples we show here, we will be using async / await syntax.

async function example_1 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    let size = await device.size();
    let batt = await device.battery();
    let vern = await device.version();

    console.log(`Screen: ${ size.w } x ${ size.h }, Battery: ${ batt }%, Running: Android v${ vern }`);
}

This example, gets lots of information from the device. Here is what the output looks like:

2021-10-02 09:42:58: sizing...
2021-10-02 09:42:59: sized at { w: 1440, h: 2960 }
2021-10-02 09:42:59: querying battery...
2021-10-02 09:42:59: queried at 87%
2021-10-02 09:42:59: querying version...
2021-10-02 09:42:59: queried as v10

Screen: 1440 x 2960, Battery: 87%, Running: Android v10

Here is a more complex example:

async function example_2 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    let url = 'https://en.wikipedia.org/wiki/Star_Trek:_The_Original_Series';

    await device.browse(url).wait(2500);
    await device.scroll().wait(1000).scroll().wait(1000).scroll(25).wait(1000).scroll(75, false);
}

It opens a page in the default browser, waits for it to loads and the scrolls back and forth a few times. Here is the output of this function:

2021-10-02 10:02:18: browsing to 'https://en.wikipedia.org/wiki/Star_Trek:_The_Original_Series'...
2021-10-02 10:02:19: browsed
2021-10-02 10:02:19: waiting for 2500ms...
2021-10-02 10:02:21: waited
2021-10-02 10:02:21: scrolling down by 50% for 300ms...
2021-10-02 10:02:21: sizing...
2021-10-02 10:02:21: sized at { w: 1440, h: 2960 }
2021-10-02 10:02:21: swiping from { x: 720, y: 2220 } to { x: 720, y: 740 } for 300ms...
2021-10-02 10:02:22: swiped
2021-10-02 10:02:22: scrolled
2021-10-02 10:02:22: waiting for 1000ms...
2021-10-02 10:02:23: waited
2021-10-02 10:02:23: scrolling down by 50% for 300ms...
2021-10-02 10:02:23: sizing...
2021-10-02 10:02:23: sized at { w: 1440, h: 2960 }
2021-10-02 10:02:23: swiping from { x: 720, y: 2220 } to { x: 720, y: 740 } for 300ms...
2021-10-02 10:02:24: swiped
2021-10-02 10:02:24: scrolled
2021-10-02 10:02:24: waiting for 1000ms...
2021-10-02 10:02:25: waited
2021-10-02 10:02:25: scrolling down by 25% for 300ms...
2021-10-02 10:02:25: sizing...
2021-10-02 10:02:25: sized at { w: 1440, h: 2960 }
2021-10-02 10:02:25: swiping from { x: 720, y: 1850 } to { x: 720, y: 1110 } for 300ms...
2021-10-02 10:02:25: swiped
2021-10-02 10:02:25: scrolled
2021-10-02 10:02:25: waiting for 1000ms...
2021-10-02 10:02:26: waited
2021-10-02 10:02:26: scrolling up by 75% for 300ms...
2021-10-02 10:02:26: sizing...
2021-10-02 10:02:26: sized at { w: 1440, h: 2960 }
2021-10-02 10:02:26: swiping from { x: 720, y: 370 } to { x: 720, y: 2590 } for 300ms...
2021-10-02 10:02:27: swiped
2021-10-02 10:02:27: scrolled

As you can see, the scroll method results in a swipe being sent to the device. Now lets try and read some text from the screen:

async function example_3 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    let url = 'https://en.wikipedia.org/wiki/Star_Trek:_The_Original_Series';
    let text = await device.browse(url).wait(2500).screen('example_3').shoot().crop({ x: 40, y: 410, w: 1350, h: 150 }).greyscale().read();

    console.log(`Read from page: '${ text }'`);
}

The cropping rectangle here is where Wikipedia places the page title - which I found by taking an earlier screenshot. Here is the output of this function:

2021-10-02 11:08:53: browsing to 'https://en.wikipedia.org/wiki/Star_Trek:_The_Original_Series'...
2021-10-02 11:08:53: browsed
2021-10-02 11:08:53: waiting for 2500ms...
2021-10-02 11:08:55: waited
2021-10-02 11:08:55: shooting 'example_3'...
2021-10-02 11:08:56: shot
2021-10-02 11:08:56: cropping 'example_3' at { x: 40, y: 410, w: 1350, h: 150 }...
2021-10-02 11:08:56: cropped
2021-10-02 11:08:56: greyscaling 'example_3'...
2021-10-02 11:08:56: greyscaled
2021-10-02 11:08:56: clean reading 'example_3'...
2021-10-02 11:08:56: read

Read from page: 'Star Trek: The Original Series'

So here, you can see the text has been successfully read. In practice reading text from inside an image can sometimes be challenging. I used crop and greyscale here to prepare the image for reading. Other operations you might want to use for this are sharpen and threshold. Or you can go your own way and prepare the image yourself by using the file or sharp functions (as described in an earlier section of this document).

Lets take a look at some of the advanced screen functions:

async function example_4 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);

    // this line makes two images, where the second one is a small section of the first
    await device.screen('example').shoot().clone('other').crop({ x: 10, y: 10, w: 100, h: 100 });

    // we now look for the smaller image inside the larger one
    let position = await device.screen('example').contains('other');

    // obviously in this small example, this should always return one result
    console.log(position);
}

The contains method looks for instances of one image, inside another. This is useful when you are trying to find the location of an on-screen element like a button or a sprite. The function returns an array of the position of each which is found. Here is the output:

2021-10-02 12:46:47: shooting 'example'...
2021-10-02 12:46:49: shot
2021-10-02 12:46:49: cloning 'example' to 'other'...
2021-10-02 12:46:49: cloned
2021-10-02 12:46:49: cropping 'other' at { x: 10, y: 10, w: 100, h: 100 }...
2021-10-02 12:46:49: cropped
2021-10-02 12:46:49: checking if 'example' contains at most 1 instance of 'other'...
2021-10-02 12:46:49: checked contained

[ { x: 10, y: 10, w: 100, h: 100 } ]

You can search for multiple instances of the smaller image, but this method can be relatively slow if you do that. By default it stops when it finds the first match.

async function example_5 (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);

    let points = [
       { x: 100, y: 100 },
       { x: 100, y: 200 },
       { x: 200, y: 100 }
    ]

    // extracts colors, but there is no match as we didn't send expected colors in
    let result = await device.screen('example').shoot().colors(points);
    console.log (util.inspect(result, { depth: null }));

    // extracts colors, and these should all match if the screen is static
    result = await device.screen('example').shoot().colors(result.colors);
    console.log (util.inspect(result, { depth: null }));
}

The colors function extracts the RGB values of the pixel points which you send in. If you also send in color values, it will also check that all your expected colors match what is on the screen.

2021-10-02 13:57:43: shooting 'example'...
2021-10-02 13:57:45: shot
2021-10-02 13:57:45: getting colors from 'example' at 3 points...
2021-10-02 13:57:45: got colors

{
  colors: [
    { x: 100, y: 100, c: { r: 1, g: 1, b: 1 } },
    { x: 100, y: 200, c: { r: 151, g: 131, b: 98 } },
    { x: 200, y: 100, c: { r: 93, g: 93, b: 93 } }
  ],
  matched: false
}

2021-10-02 13:57:45: shooting 'example'...
2021-10-02 13:57:47: shot
2021-10-02 13:57:47: getting colors from 'example' at 3 points...
2021-10-02 13:57:48: got colors

{
  colors: [
    { x: 100, y: 100, c: { r: 1, g: 1, b: 1 } },
    { x: 100, y: 200, c: { r: 151, g: 131, b: 98 } },
    { x: 200, y: 100, c: { r: 93, g: 93, b: 93 } }
  ],
  matched: true
}

This can be a useful to identify a screen by quickly inspecting only a small number of pixels within it.

Here is a more fun script:

async function tiktok (count = 10, tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);

    let DELAY_START = 2500; // ms
    let DELAY_SWIPE = 5000; // ms

    let sz = await device.size();
    let hi = { x: sz.w / 2, y: sz.h / 2 - (sz.h / 4) };
    let lo = { x: sz.w / 2, y: sz.h / 2 + (sz.h / 4) };

    await device.start('com.zhiliaoapp.musically').wait(DELAY_START);

    for (let i = 0; i < count; i++) {
        await device.trace(`watching video ${i+1} of ${count}`).wait(DELAY_SWIPE).screen(`tiktok_${i+1}`).shoot().device().swipe(lo, hi);
    }
}

This function starts TikTok and swipes through a number of videos every 5 secs and takes a screenshot of each as it does.

2021-10-02 14:22:42: sizing...
2021-10-02 14:22:42: sized at { w: 1440, h: 2960 }
2021-10-02 14:22:42: starting 'com.zhiliaoapp.musically'...
2021-10-02 14:22:43: started
2021-10-02 14:22:43: waiting for 2500ms...
2021-10-02 14:22:45: waited
2021-10-02 14:22:45: watching video 1 of 5
2021-10-02 14:22:45: waiting for 5000ms...
2021-10-02 14:22:50: waited
2021-10-02 14:22:50: shooting 'tiktok_1'...
2021-10-02 14:22:52: shot
2021-10-02 14:22:52: swiping from { x: 720, y: 2220 } to { x: 720, y: 740 } for 300ms...
2021-10-02 14:22:53: swiped
2021-10-02 14:22:53: watching video 2 of 5
2021-10-02 14:22:53: waiting for 5000ms...
2021-10-02 14:22:58: waited
2021-10-02 14:22:58: shooting 'tiktok_2'...
2021-10-02 14:23:00: shot
2021-10-02 14:23:00: swiping from { x: 720, y: 2220 } to { x: 720, y: 740 } for 300ms...
2021-10-02 14:23:01: swiped

... etc

Here are two example functions which take a picture and take a video respectively:

async function take_pic (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    await device.camera();
    await device.wait(1000).press(AndroidAutomate.Keys.CAMERA)
    await device.wait(500).press(AndroidAutomate.Keys.HOME);
}

async function take_vid (secs = 5, tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    await device.camera(true);
    await device.wait(1000).press(AndroidAutomate.Keys.BUTTON_START);
    await device.wait(1000 * secs).press(AndroidAutomate.Keys.BUTTON_START)
    await device.wait(500).press(AndroidAutomate.Keys.BUTTON_SELECT)
    await device.wait(500).press(AndroidAutomate.Keys.HOME);
}

These functions record media on the local storage of the device. This media may then be auto-mirrored to a cloud service such as Google Photos. We have not (yet) implemented the protocols to retrieve local files from the device. This is possible, but does move somewhat beyond the remit of simple automation. You could always open the camera and then take a screenshot. Then the image will be auto-transferred to your script hosting machine:

async function take_pic (tracing = false) {
    let device = new AndroidAutomate.Device().tracing(tracing);
    await device.camera().wait(1000).screen('picture').shoot().wait(500).device().press(AndroidAutomate.Keys.HOME);
}

Here are a couple of other quick examples. This first one outputs the names of all the people on the first page of your Whatsapp conversations:

async function whatsapp (tracing = false) {
    const NAME_X = 255;
    const NAME_Y = 525;
    const NAME_W = 900;
    const NAME_H =  75;
    const NAME_D = 265;

    let device = new AndroidAutomate.Device().tracing(tracing);
    let size = await device.size();

    await device.start('com.whatsapp').wait(2000).screen('whatsapp').shoot();

    let names = [];
    let section = { x: NAME_X, y: NAME_Y, w: NAME_W, h: NAME_H }

    for ( ; section.y + NAME_H < size.h ; section.y += NAME_D) {
        names.push(await device.screen('whatsapp').clone('name').crop(section).read());
    }

    await device.screen('whatsapp').delete().screen('name').delete();

    console.log(names);
}

This implementation is both fragile (Whatsapp opens in its last state, not always on the conversation list) and specific (the measurements listed are specifically for my device). A more robust implementation is possible however, but somewhat beyond the scope of these simple demos.

Finally, here is the same function modified to send a message to a given person over Whatsapp:

async function whatsapp_msg (who, msg, tracing = false) {
    const NAME_X =  255;
    const NAME_Y =  525;
    const NAME_W =  900;
    const NAME_H =   75;
    const NAME_D =  265;
    const TAP_X  =   25;
    const TAP_Y  =   25;
    const SEND_X = 1335;
    const SEND_Y = 1680;

    let device = new AndroidAutomate.Device().tracing(tracing);
    let size = await device.size();

    await device.start('com.whatsapp').wait(2000).screen('whatsapp').shoot();

    let section = { x: NAME_X, y: NAME_Y, w: NAME_W, h: NAME_H }

    for ( ; section.y + NAME_H < size.h ; section.y += NAME_D) {
        let name = await device.screen('whatsapp').clone('name').crop(section).read();

        if (name === who) {
            await device.tap({ x: section.x + TAP_X, y: section.y + TAP_Y }).wait(250).input(msg).wait(1000).tap({ x: SEND_X, y: SEND_Y });
            break;
        }
    }

    await device.screen('whatsapp').delete().screen('name').delete();
}

That's all for these small example functions. You can find more inspiration by examining the large demonstration application - a program which automatically plays the popular mobile puzzle game FlowFree. There is more information on this in the Demonstration section.

Have fun!

Pete Rai

Package Sidebar

Install

npm i android-automate

Weekly Downloads

1

Version

1.0.1

License

MIT

Unpacked Size

68.5 kB

Total Files

8

Last publish

Collaborators

  • peterai