FindNode User Guide · Version 21 · May 28, 2026 · Full reference

Introduction

FindNode finds UI elements on an Android screen with a small query language and then acts on them, so a script never needs screen coordinates. devices.click("OK") replaces devices.click(100, 200): FindNode locates the node with the text "OK" at runtime, and the same script runs on phones with different resolutions and screen sizes.

This page collects the 204 Terminal examples from the FindNode User Guide, version 21, with a short explanation of each. The User Guide remains the full reference.

How to read an example

  • A line that starts with >> is a command typed in the Terminal. The lines under it are the output.
  • Commands are sent with device.sendAai({query:"…", action:"…"}) for one device, or devices.sendAai(…) for several.
  • A query has up to three parts, run in this order: the template (TP), the basic query (BQ) and the expanded query (EQ). The nodes it finds are kept in the Matching List (ML), and actions work on that list.
  • A successful call returns JSON, usually with the value under retval. On an error it returns null, and lastError() gives the message.
  • Copy takes one command without the >>. Screenshots enlarge when clicked.

Chapter 1 of 10

Getting started

How commands are sent to FindNode, and what a query with an action looks like.

sendAai

Every FindNode command goes through sendAai(), called on one device (device) or on several (devices).

1 example, 2 commands

Send several commands in one call

sendaai-ex1

Separate the commands with ; in one string, or pass them as an array in actions. Both return one result per command.

device.sendAai({action:"echo(2*2);echo(3*3);echo(4*4)"})
{count: 3, list: [{retval: 4},{retval: 9},{retval: 16}]}
device.sendAai({actions:["echo(2*2)", "echo(3*3)", "echo(4*4)"]})
{count: 3, list: [{retval: 4},{retval: 9},{retval: 16}]}

Query + Actions

A query finds nodes on the screen, and an action is applied to the nodes found.

1 example, 1 command

Read the Android version

query-actions-ex1

TP:basic is the template, T:Android version is the basic query that finds the label, and OY:1 is the expanded query that moves one node down to the value.

The About phone screen. The value sits under its label.
device.sendAai({query:"TP:basic&&T:Android version&&OY:1", action:"getText"})
{retval: '14'}

Chapter 2 of 10

Query

A query is a string of key:value pairs joined by &&, and a node must meet all of them to match. It has three parts: the template (TP) produces the first nodes, the basic query (BQ) tests each node's own properties, and the expanded query (EQ) works across several nodes.

Overview

Four ways to read values from one Storage Analysis screen.

4 examples, 4 commands

Read the value next to a label

query-ex1

T:Main storage finds the label and OX:1 moves one node to the right, to the free size.

The Storage Analysis screen used in these four examples.
device.sendAai({query:"T:Main storage&&OX:1", action:"getText"})
{retval: '402 GB free'}

The same with an optional query

query-ex2

The query can be written inside the action. This is an optional query (OQ).

device.sendAai({action:"getText(T:Main storage&&OX:1)"})
{retval: '402 GB free'}

Click the back arrow

query-ex3

OX:-1 moves one node to the left of the title, to the left arrow, and click presses it.

device.sendAai({action:"click(T:Storage Analysis&&OX:-1)"})
{retval: true}

Mix all three query parts

query-ex4

The parser splits the query into template (TP:all), basic query (T:Main storage) and expanded query (VG:2&&XT:Audio&&OX:1), and runs them in that order to reach the Audio size.

device.sendAai({query:"TP:all&&T:Main storage&&VG:2&&XT:Audio&&OX:1", action:"getText"})
{retval: '77 MB'}

Matching List (ML)

The nodes a query matches are stored in the Matching List (ML). Since version 16 the search is delayed: it runs only when an action needs nodes.

4 examples, 5 commands

No query means the default template

matching-list-ex1

With no query, the search uses the default template TP:more, so both commands return the same node IDs.

device.sendAai({action:"getIds"})
{count: 11, retval: ['14801','1714c',...]}
device.sendAai({query:"TP:more", action:"getIds"})
{count: 11, retval: ['14801','1714c',...]}

The search runs when getIds needs it

matching-list-ex2

With delayed search (version 16), the search is performed at the moment getIds is called.

device.sendAai({action:"getIds"})
{count: 11, retval: ['14801','1714c',...]}

No search for actions without nodes

matching-list-ex3

openApp does not use the ML, so no search is performed.

device.sendAai({action:"openApp(Skype)"})
{retval: true}

One search where there used to be two

matching-list-ex4

Before version 16 this ran the default TP:more search and then TP:reduced. Now only the TP:reduced search runs.

device.sendAai({action:"newQuery(TP:reduced)"})
{count: 8}

“elements” property

elements takes a list of node IDs and puts those nodes in the ML directly, skipping the query. It is useful for reusing node IDs saved earlier.

1 example, 2 commands

Save node IDs and click each one

elements-property-ex1

The first command saves the IDs of the digit buttons 1 to 9. The second passes them as elements, clicks each one with forEach(nsClick), and reads the result field.

ids = device.sendAai({query:"T:/^[1-9]$/"}).retval
a71e,aadf,aea0,b622,b9e3,bda4,c526,c8e7,cca8
device.sendAai({elements:ids, action:"forEach(nsClick);getText(+R:.editText_result)"})
{count: 2, list: [{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: '789456123'}]}

Chapter 3 of 10

Template in detail

A template (TP) is required in every query. It produces the starting list of nodes, the Matching List (ML), that the rest of the query and every action work on. Only one template is allowed per query. If none is given, the default TP:more is used.

Example: Template Limits the Query Scope

A query can only reach nodes that its template returned. Offsets and all other operations stay inside that list.

2 examples, 3 commands

An offset to a node outside the template fails

template-limits-the-query-scope-ex1

TP:findText,/^[0-9]$/ returns only the digit keys. OX:1 from "3" points at "%", which is not in that list, so the result is null and lastError() gives the reason.

UI Explorer with TP:findText,/^[0-9]$/. Only the digit keys are outlined.
device.sendAai({query:"TP:findText,/^[0-9]$/&&T:3&&OX:1", action:"getText"})
null
lastError()
Node not found on offset

The same offset works with the default template

template-limits-the-query-scope-ex2

No template is given, so the default TP:more is used. It includes the "%" key, and the same offset from "3" now returns it.

device.sendAai({query:"T:3&&OX:1", action:"getText"})
{retval: '%'}

TP:all, more, basic, reduced

These four templates go through every node on the screen and differ in how many they keep. all keeps everything, including the root node. more removes layout nodes and is the default. basic keeps only nodes with no children. reduced keeps the nodes FindNode judges most meaningful.

2 examples, 5 commands

Count the nodes each template returns

tp-all-ex1

getCount counts the current ML: 237 with the default TP:more. +TP:all replaces the ML, so the third count is also 242. The last three counts are for more, basic and reduced.

device.sendAai({actions:[     "getCount",     "getCount(+TP:all)",     "getCount",     "getCount(+TP:more)",     "getCount(+TP:basic)",     "getCount(+TP:reduced)" ]})
{count: 6, list: [{count: 237},{count: 242},{count: 242},{count: 237},{count: 76},{count: 58}]}

Three ways to reduce nodes

tp-all-ex2

TP:reduced (a template), RN (a query filter) and reduceNodes (an action) perform similar node reduction in different places. This screen has 59 nodes, and each of the three brings the list down to 44.

device.sendAai({action:"getCount"})
{retval: 59}
device.sendAai({action:"getCount;reduceNodes"})
{count: 2, list: [{retval: 59},{retval: 44}]}
device.sendAai({query:"RN", action:"getCount"})
{retval: 44}
device.sendAai({query:"TP:reduced", action:"getCount"})
{retval: 44}

TP:anyText[,min[,max]], anyDescription[,min[,max]]

Returns the nodes whose text (anyText) or description (anyDescription) is not empty. An optional minimum and maximum length, both inclusive, narrow the result. Zero is not allowed.

2 examples, 2 commands

Get every text on the screen

tp-anytext-ex1

TP:anyText returns all nodes that have text, and getText reads them. On the calculator this lists the title, the function keys, and every digit and operator.

device.sendAai({query:"TP:anyText", action:"getText"})
{retval: ['Calculator','AC','÷','×','DEL','7','8','9','-' ,'4','5','6','+','1','2','3','%','0','.','±','=']}

Keep only one-character text

tp-anytext-ex2

A minimum and maximum of 1 keep only text that is exactly one character long, so 'Calculator', 'AC' and 'DEL' drop out of the list.

device.sendAai({query:"TP:anyText,1,1", action:"getText"})
{retval: ['÷','×','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}

TP:findText,<text>

Returns the nodes whose text or description matches the given text. The text argument is required and accepts the same forms as T:<text> in a basic query.

1 example, 3 commands

Find text by pattern or wildcard

tp-findtext-ex1

A regular expression matches the calculator's digit keys, which come back top to bottom. The second command matches one to four capital letters, and the wildcard *T returns the entries ending in T.

device.sendAai({query:"TP:findText,/[0-9]/", action:"getText"})
{retval: ['7','8','9','4','5','6','1','2','3','0']}
device.sendAai({query:"TP:findText,/^[A-Z]{1,4}$/", action:"getText"})
{retval: ['TGT','INTC','WMT','T','AAPL','SBUX','TSLA']}
device.sendAai({query:"TP:findText,*T", action:"getText"})
{retval: ['TGT','WMT','T']}

TP:textInput[,<hint text>]

Returns the editable nodes (text fields), optionally only the ones with the given hint text. Hint text needs Android 8.0 or later. BP:editable gives the same result.

1 example, 3 commands

List text fields and fill one by its hint

tp-textinput-ex1

The first command reads the text of all five text fields. The second reads their hint text, which is supported from API 26. The third selects the field whose hint is "Email" and types an address into it.

device.sendAai({query:"TP:textInput", action:"getText"})
{count: 5, retval: ['Text','Number','Email','Text password','Text']}
// Hint Text starts support on API 26
device.sendAai({query:"TP:textInput", action:"getHintText"})
{count: 5, retval: ['Text','Number','Email','Text password','Text']}
// setText based on hint text
device.sendAai({query:"TP:textInput,Email", action:"setText(support@sigma-rt.com)"})
{retval: true}

TP:line,<top|bottom>,<line number>

Groups the fixed elements above or below the scrollable area into lines and returns one line. Line numbers start at 1. Negative numbers count from the end, so -1 is the last line. This is a best-effort feature and mainly applies to apps with a scrollable container.

3 examples, 4 commands

Get the first line at the top

tp-line-ex1

TP:line,top,1 returns the nodes on the first line of the top region. Here that line has six nodes.

The first line of the top region.
device.sendAai({query:"TP:line,top,1"})
{count: 6, retval: ['1902b0','191936','192fbc','1cf0bc','19373e','194a03']}

Click the first item on that line

tp-line-ex2

IX:0 picks the first node of the line, which is the left arrow. Clicking it goes back.

device.sendAai({query:"TP:line,top,1", action:"click(IX:0)"})
{retval: true}

Type into the bottom line

tp-line-ex3

TP:line,bottom,-1 returns the last line of the bottom region, which has five nodes. The second command picks the editable node on that line and sets its text to Hello.

The last line of the bottom region.
device.sendAai({query:"TP:line,bottom,-1"})
{count: 5, retval: ['2be5b8','2c0b42','2c1a46','2c30cc','2c3fd0']}
device.sendAai({query:"TP:line,bottom,-1", action:"setText(BP:editable, Hello)"})
{retval: true}

TP:appNavBar[,<icons|labels>]

Introduced in version 20. Returns the icons and labels of an app's bottom navigation bar: icons first, then labels. Add icons or labels to get only one kind. BP:selected finds the selected tab in most apps.

3 examples, 11 commands

Read and click a navigation bar

tp-appnavbar-ex1

The bar has four tabs, so TP:appNavBar returns eight nodes: four icons, then four labels. A tab can be clicked by its label text or by its icon with IX:3, and BP:selected finds the selected tab.

A navigation bar with four tabs.
device.sendAai({query:"TP:appNavBar", action:"getIds"})
{count: 8, retval: ['141e1','7ead','15fe9','17a30','14d24','150e5','1676b','181b2']}
device.sendAai({query:"TP:appNavBar,labels", action:"getText"})
{count: 4, retval: ['Chats','Updates','Communities','Calls']}
device.sendAai({query:"TP:appNavBar,labels", action:"click(T:Calls)"})
{retval: true}
// Same as above but click the icon
device.sendAai({query:"TP:appNavBar", action:"click(IX:3)"})
{retval: true}
device.sendAai({query:"TP:appNavBar&&BP:selected"})
{count: 2, retval: ['141e1','14d24']}
device.sendAai({query:"TP:appNavBar,labels&&BP:selected", action:"getText"})
{retval: 'Chats'}

A large icon appears in both halves

tp-appnavbar-ex2

The "+" icon covers both the icon row and the label row, so its node (21d62) appears twice. To get only the real labels, keep the nodes that have text, with T:/./ or filter(node.text).

The "+" icon spans the height of both icons and labels.
device.sendAai({query:"TP:appNavBar", action:"getIds"})
{count: 10, retval: ['1d9d0','1fb99','21d62','233e8','255b1','1f056','2121f','21d62','24a6e','26ff8']}
device.sendAai({query:"TP:appNavBar&&T:/./", action:"getText"})
{count: 4, retval: ['Home','Shorts','Subscriptions','You']}
device.sendAai({query:"TP:appNavBar", action:"filter(node.text);getText"})
{count: 2, list: [{retval: true},{count: 4, retval: ['Home','Shorts','Subscriptions','You']}]}
device.sendAai({action:"getText('+TP:appNavBar,labels&&BP:selected')"})
{retval: 'Home'}

Icons and labels that cannot be separated

tp-appnavbar-ex3

In this app each tab is a single node that holds both the icon and the label, so the bar returns three nodes. A tab can still be clicked by position with IX:, not by text with T:.

Three tabs, each one node, as outlined in UI Explorer.
device.sendAai({query:"TP:appNavBar", action:"click(IX:2)"});
{retval: true}

TP:scrollable[,<position>|<query>]

Returns the on-screen items (leaf nodes) of one scrollable area. Choose the area by position, counted from 0 from top to bottom, or by a query that matches it. BP:scrollable, by contrast, matches every scrollable area on the screen.

6 examples, 8 commands

Run TP:scrollable with no argument

tp-scrollable-ex1

With no position or query, the default position 0 is used. Here the result is five node IDs.

device.sendAai({query:"TP:scrollable"})
{count: 5, retval: ['1e434','5bbba','5bf7b','5d9c2','6f70e']}

List every scrollable area with BP:scrollable

tp-scrollable-ex2

BP:scrollable matches all scrollable nodes on the screen. getNodes(B) returns the bounds and ID of each one.

device.sendAai({query:"BP:scrollable", action:"getNodes(B)"})
{count: 5, list: [{bounds: '[0,268][1440,2738]', id: '1e434'},{bounds: '[42,457][1440,1303]', id: '5bbba'},{bounds: '[42,457][1440,1303]', id: '5bf7b'},{bounds: '[0,1303][1440,1742]', id: '5d9c2'},{bounds: '[0,1958][1440,2738]', id: '6f70e'}]}

Read the titles on the Settings screen

tp-scrollable-ex3

Three actions in one call: open Android Settings, query the titles (android:id/title) inside the scrollable area, and read their text. The result has one entry per action.

device.sendAai({actions:["openAndroidSetting(Settings)", "newQuery(TP:scrollable&&R:android:id/title)", "getText"]})
{count: 3, list: [{retval: true},{retval: 6},{count: 6, retval: ['John James','Sign in quickly and safely','Connections','Connected devices','Galaxy AI','Modes and Routines']}]}

Select the Gmail navigation drawer

tp-scrollable-ex4

The drawer opens as a popup among several scrollable areas. It can be selected by position (2) or by a query that matches something inside it (T:Sent). Both return the same 39 nodes.

The menu icon (three lines) opens the navigation drawer.
The open drawer in UI Explorer with TP:more.
The drawer identified with TP:scrollableAsPopup,2.
device.sendAai({query:"TP:scrollable,2", action:"getIds"})
{count: 39, retval: ['639b7','64139','96fd2','97393','97ed6','9d8ee','9dcaf','6d761','72db8','73179','9fab7','75342','75703','75ac4','7750b','7804e','7840f','787d0','7a5d8','7a999','7c7a1','7d2e4','7d6a5','7da66','7f86e','7fc2f','81df8','821b9','8257a','84382','84743','84b04','8690c','86ccd','88e96','89257','8b420','8b7e1','8bba2']}
device.sendAai({query:"TP:scrollable,T:Sent", action:"getIds"})
{count: 39, retval: ['639b7','64139','96fd2','97393','97ed6','9d8ee','9dcaf','6d761','72db8','73179','9fab7','75342','75703','75ac4','7750b','7804e','7840f','787d0','7a5d8','7a999','7c7a1','7d2e4','7d6a5','7da66','7f86e','7fc2f','81df8','821b9','8257a','84382','84743','84b04','8690c','86ccd','88e96','89257','8b420','8b7e1','8bba2']}

Search inside the drawer

tp-scrollable-ex5

With the template limited to the drawer, the query finds "Sent" and reads the node to its right, the count 505.

device.sendAai({query:"TP:scrollable,T:Sent&&T:Sent&&OX:1", action:"getText"})
{retval: '505'}

The same search without the template fails

tp-scrollable-ex6

Without TP:scrollable the query is not limited to the popup, and the offset from "Sent" is not found.

device.sendAai({query:"T:Sent&&OX:1", action:"getText"})
null
lastError()
Query Search Error: Node not found on offset

Chapter 4 of 10

Basic query in detail

The basic query (BQ) tests the nodes from the template one at a time, using each node's own properties, and drops the ones that do not match.

P:<package name>

Selects the app to query. Without P, the active app is used. Use it to reach another app on the screen, such as the system UI.

2 examples, 4 commands

Read the time from the system bar

p-ex1

Three ways to read the clock (R:.clock) of com.android.systemui. An optional query does not support P, so the third form selects the app with newQuery first.

device.sendAai({query:"P:com.android.systemui&&R:.clock", action:"getText"})
{retval:‘3:22’}
device.sendAai({query:"P:com.android.systemui", action:"getText(R:.clock)"})
{retval:‘3:22’}
device.sendAai({action:"newQuery(P:com.android.systemui); getText(R:.clock)"})
{count:2,list:[{retval:29},{retval:‘3:22’}]}

Click the Home button

p-ex2

The Home button belongs to the system UI, so the query selects that package and clicks R:.home.

device.sendAai({query:"P:com.android.systemui", action:"click(R:.home)"})
{retval: true}

T:<text>, D:<text>, TD:<text>, T0:<text>, T1:<text>, TN:<text>

T matches text, D matches the description, and TD matches either. T0 is T with BP:none, T1 is T with BP:!editable, and TN normalizes hyphens and spaces before matching.

1 example, 3 commands

Match text that uses a different hyphen

t-ex1

The text on screen uses another Unicode hyphen, so T:*Wi-Fi* finds nothing. TN converts such hyphens and spaces to the keyboard characters and finds both entries.

device.sendAai({query:"T:*Wi-Fi*", action:"getText"})
null
lastError()
No match found
device.sendAai({query:"TN:*Wi-Fi*", action:"getText"})
{count: 2, retval: ['Enable Wi‑Fi Verbose Logging','Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker']}

Bound In (BI):

BI returns nodes by screen position: those that contain a point, touch a horizontal or vertical line, or lie inside a rectangle. Coordinates do not carry over between devices, so this is mainly for debugging.

1 example, 2 commands

Find nodes by a line or a rectangle

bound-in-ex1

BI:[-1,645] returns the nodes that a horizontal line at y = 645 touches, and IX:-1 picks the last one to click. BI:[0, 0, 1000, 1000] returns the nodes inside that rectangle.

device.sendAai({query:"BI:[-1,645]&&IX:-1", action:"click"})
{retval:true}
device.sendAai({query:"BI:[0, 0, 1000, 1000]", action:"getBounds"})
{bounds:[[0,113,196,309],[252,164,529,257],[70,572,702,695],[702,572,737,576],[70,765,480,888],[480,765,515,769],[515,765,925,888],[925,765,960,769]],count:8,ids:["1b21e","1ae5d","17d90","18151","18c94","19055","19416","197d7"]}

BP:<property>

Matches nodes by their Boolean properties, such as clickable, editable or checked. List several separated by commas, and put ! in front of one to match false.

5 examples, 11 commands

Match one or several properties

bp-ex1

The first query keeps the text fields that are clickable. The next two also require enabled; quotes around the list are optional.

device.sendAai({query:"TP:textInput&&BP:clickable"})
{count: 1, retval: ['27ebc0']}
device.sendAai({query:"TP:textInput&&BP:'clickable,enabled'"})
{count: 1, retval: ['27ebc0']}
device.sendAai({query:"TP:textInput&&BP:clickable,enabled"})
{count: 1, retval: ['27ebc0']}

Show all Boolean properties of a node

bp-ex2

getNodes(BP) returns every Boolean property of the matched node with its value.

device.sendAai({query:"TP:textInput&&BP:clickable,enabled", action:"getNodes(BP)"})
{count: 1, list: [{booleanProperties: {checkable: false, checked: false, clickable: true, editable: true, enabled: true, focusable: true, focused: false, longClickable: true, multiLine: true, scrollable: false, selected: false, visibleToUser: true}, id: '27ebc0'}]}

Match a property that is false

bp-ex3

BP:!enabled matches the next-period arrow when it is disabled and shown in gray. A loop through the periods can stop on this.

The arrows on the right turn gray when there is no further period.
device.sendAai({query:"R:.btn_next_period&&BP:!enabled", action:"getCount"})
{count: 1}

The opposite query finds nothing

bp-ex4

The arrow is disabled, so BP:enabled returns null and lastError() reports no match. The last command reads the labels in the same row as "Day" with TX.

device.sendAai({query:"R:.btn_next_period&&BP:enabled", action:"getCount"})
null
lastError()
No match found
device.sendAai({query:"T:Day&&TX", action:"getText"})
{count: 5, retval: ['Day','Week','Month','Year','Billing']}

Clickable but not checkable

bp-ex5

Checkable nodes are usually clickable too. Of the 26 clickable nodes, 10 are checkable, so BP:clickable,!checkable leaves 16.

device.sendAai({query:"BP:clickable", action:"getCount"})
{count: 26}
device.sendAai({query:"BP:checkable", action:"getCount"})
{count: 10}
device.sendAai({query:"BP:clickable,!checkable", action:"getCount"})
{count: 16}

Basic Query Examples

A few more basic queries, and what happens when nothing matches.

3 examples, 10 commands

The default query, and counting nodes

basic-query-examples-ex1

An empty call is the same as TP:more with getIds. T:/./, D:/./ and TD:/./ count the nodes that have text, a description, or either.

device.sendAai({})
{count: 9, retval: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
device.sendAai({query:"TP:more", action:"getIds"})
{count: 9, retval: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
device.sendAai({query:"T:/./", action:"getCount"})
{count: 25}
device.sendAai({query:"D:/./", action:"getCount"})
{count: 44}
device.sendAai({query:"TD:/./", action:"getCount"})
{count: 69}

Combine several keys

basic-query-examples-ex2

On the calculator, this matches one-character text (T:/^.$/) on buttons (C:.Button) whose resource ID matches .button_*.

device.sendAai({query:"T:/^.$/&&C:.Button&&R:.button_*", action:"getText"})
{retval: ['÷','×','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}

What an error looks like

basic-query-examples-ex3

A query with no match returns null, and lastError() shows the message. An unknown key such as X gives a different error.

device.sendAai({query:"T:X", action:"getText"})
null
lastError()
No match found
device.sendAai({query:"X:X", action:"getText"})
null
lastError()
Error:Cannot find the key for "X"

Chapter 5 of 10

Expanded query in detail

Expanded query (EQ) keys work across several nodes: offsets, groups, intersections and sorting. They run from left to right, and the same key can be used more than once.

Index (IX) M O

IX:<number> picks one node from the ML by position, starting at 0. A negative number counts from the end.

1 example, 3 commands

Pick a node by position

index-ex1

The ML has nine nodes. IX:1 returns the second one and IX:-2 the second from the end.

device.sendAai({})
{count: 9, retval: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
device.sendAai({query:"IX:1"})
{count: 1, retval: ['b779']}
device.sendAai({query:"IX:-2"})
{count: 1, retval: ['ac36']}

Offset (OX & OY) O O

OX and OY move from a reference node to a neighbour. OX goes right (positive) or left (negative); OY goes down (positive) or up (negative).

4 examples, 7 commands

Read the value to the right of a label

offset-ex1

OX:1 moves from the label to the value on its right.

About phone: the values sit to the right of their labels.
device.sendAai({query:"T:Model name&&OX:1", action:"getText"})
{retval: 'Galaxy S22 Ultra'}
device.sendAai({query:"T:Model number&&OX:1", action:"getText"})
{retval: 'SM-S908U1'}

Read the value below a label

offset-ex2

OY:1 moves one node down from the label.

Here the value sits under its label.
device.sendAai({query:"T:Android version&&OY:1", action:"getText"})
{retval: '12'}

Move between calculator keys

offset-ex3

From the 5 key, OX:1 reaches 6 on its right and OY:-1 reaches 8 above it.

The calculator keys in UI Explorer.
device.sendAai({query:"T:5&&OX:1", action:"getText"})
{retval: '6'}
device.sendAai({query:"T:5&&OY:-1", action:"getText"})
{retval: '8'}

The order of OX and OY matters

offset-ex4

Starting from 0, going up and then two to the right lands on ×. Going two to the right and then up lands on −.

// OX & OY orders may get different results
device.sendAai({query:"T:0&&OY:-1&&OX:2", action:"getText"})
{retval: '×'}
device.sendAai({query:"T:0&&OX:2&&OY:-1", action:"getText"})
{retval: '−'}

ViewGroup (VG) O M

VG goes up from a node to the group that encloses it and puts the whole group in the ML, with the group node first. VG:<level> goes up more levels; the default is 1.

7 examples, 7 commands

The first-level group around a button

viewgroup-ex1

VG from "Start Charging" returns only two nodes. The first level does not reach the charging current.

The first-level group (green) holds only the button.
device.sendAai({query:"T:Start Charging&&VG"})
{count: 2, retval: ['53b16','53ed7']}

Scroll to an option and turn it on

viewgroup-ex2

scrollIntoView brings the option's title onto the screen. setChecked then moves from the title to its switch (OY:1&&OX:1) and turns it on.

The option in Developer Options.
device.sendAai({action:"scrollIntoView(T:Disable adb auth*);setChecked(OY:1&&OX:1, true)"})
{count: 2, list: [{retval: true},{retval: true}]}

Scroll the whole option into view with VG

viewgroup-ex3

If the title lands on the last line of the screen, the rest of the option may be out of view. Adding VG scrolls the whole option into view, and the switch is then found with BP:checkable.

With VG, the group covers the title, the description and the switch.
device.sendAai({action:"scrollIntoView(T:Disable adb auth*&&VG);setChecked(BP:checkable, true)"})
{count: 2, list: [{retval: true},{retval: true}]}

The second-level group

viewgroup-ex4

VG:2 returns the whole charging group, 24 nodes. The first node encloses the group, which is useful with showOnScreen.

The level-2 group (green) and the nodes inside it.
device.sendAai({query:"T:Start Charging&&VG:2"})
{count: 24, retval: ['4a8af','4ac70','4b031','4b3f2','4bb74','4bf35','4c2f6','4c6b7','4ca78','4ce39','4d1fa','4d5bb','4d97c','4dd3d','4e0fe','4ec41','4f002','4f3c3','53755','53b16','53ed7','54298','54659','54a1a']}

Read the charging current inside the group

viewgroup-ex5

Inside the group, XT:/^[0-9]+ A$/ finds the text that is a number followed by " A".

device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", action:"getText"})
{retval: '32 A'}

Lower the current by 1 A

viewgroup-ex6

Reads the value, clicks the arrow to its left (OX:-1), refreshes the nodes and reads it again: 32 A becomes 31 A.

device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", actions:"getText;click(OX:-1);refresh;getText"})
{count: 4, list: [{retval: '32 A'},{retval: true},{retval: true},{retval: '31 A'}]}

Lower the current by 5 A

viewgroup-ex7

repeat(5, …) clicks the left arrow five times, and the value goes from 32 A to 27 A.

device.sendAai({query:"T:Start Charging&&VG:2&&XT:/^[0-9]+ A$/", actions:"getText;repeat(5,nsClick(OX:-1));refresh;getText"})
{count: 4, list: [{retval: '32 A'},{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: true},{retval: '27 A'}]}

Intersect (TX & TY) O M

TX and TY take the first node as a reference and return every node that intersects it horizontally (TX) or vertically (TY).

2 examples, 4 commands

Nodes in the same row or column

intersect-ex1

From the 2 key, TX returns the six nodes that intersect it horizontally and TY the six that intersect it vertically.

The row of the 2 key in UI Explorer.
device.sendAai({query:"T:2&&TX"})
{count: 6, retval: ['bb4b','bf0c','c2cd','e0d5','e496','111a2']}
device.sendAai({query:"T:2&&TY"})
{count: 6, retval: ['1d115','1d4d6','a886','b3c9','bf0c','ca4f']}

A wide reference node

intersect-ex2

The result field spans the full width of the screen. TX returns only the field itself, and TY returns 23 nodes.

device.sendAai({query:"R:.result&&TX"})
{count: 1, retval: ['1d4d6']}
device.sendAai({query:"R:.result&&TY"})
{count: 23, retval: ['1d897','1d4d6','d592','f39a','a4c5','a886','ac47','dd14','1029e','b008','b3c9','b78a','e0d5','bb4b','bf0c','c2cd','111a2','e496','c68e','ca4f','ce10','120a6','e857']}

Reduce nodes (RN) M M

RN applies node reduction to the ML. It keeps the most meaningful nodes and prefers small nodes over the containers around them.

1 example, 2 commands

Reduce 242 nodes to 55

reduce-nodes-ex1

The default query returns 242 nodes. With RN the count drops to 55.

device.sendAai({}).count
242
device.sendAai({query:"RN"}).count
55

Sort (ST) M M

ST:X, ST:Y or ST:YX sorts the nodes by their left bounds, their top bounds, or top then left.

1 example, 4 commands

Sort the calculator buttons

sort-ex1

The buttons already come back from top to bottom, so ST:Y and ST:YX change nothing. ST:X orders them by column, from left to right.

device.sendAai({query:"C:.Button", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
device.sendAai({query:"C:.Button&&ST:Y", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
device.sendAai({query:"C:.Button&&ST:YX", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
device.sendAai({query:"C:.Button&&ST:X", action:"getText"})
{retval: ['AC','7','4','1','0','÷','8','5','2','.','×','9','6','3','±','DEL','-','+','%','=']}

Basic query (BQ) M M

A basic-query key written after an EQ key still runs first, as part of the BQ. To run it at that point of the EQ, wrap it in BQ:'…', prefix it with X, or turn on selector:allowBqAfterEq.

2 examples, 4 commands

Match text inside a group

basic-query-2-ex1

After VG selects the CPU group, XT:Cpu Cores or BQ:'T:Cpu Cores' finds the label inside it, and OX:1 reads the value.

The group found by T:Overall Cpu Usage&&VG.
device.sendAai({query:"T:Overall Cpu Usage&&VG&&XT:Cpu Cores&&OX:1", action:"getText"})
{retval: '8'}
device.sendAai({query:"T:Overall Cpu Usage&&VG&&BQ:'T:Cpu Cores'&&OX:1", action:"getText"})
{retval: '8'}

Allow basic keys after expanded keys

basic-query-2-ex2

With selector:allowBqAfterEq set to true (from version 14), a plain T: after VG runs as part of the EQ.

device.sendAai({action:"setConfig(selector:allowBqAfterEq, true)"})
{retval: true}
device.sendAai({query:"T:Overall Cpu Usage&&VG&&T:Cpu Cores&&OX:1", action:"getText"})
{retval: '8'}

Value (V) M M

V:<value> sets the value of the first node in the ML, so a query can find a field and fill it in one step.

2 examples, 4 commands

Fill a title and switch on All day

value-ex1

The query sets the Title field to the text, then moves to the All day switch and sets it to true. With no action given, the default getIds runs.

Before: empty title, All day off.
After: title filled in, All day on.
device.sendAai({query:"T:Title&&V:Online Gaming Tournament&&OY:1&&OX:1&&V:true"})
{count: 1, retval: ['e72e2']}
device.sendAai({action:"newQuery(T:Title&&V:Online Gaming Tournament&&OY:1&&OX:1&&V:true);getIds"})
{count: 2, list: [{count: 1},{count: 1, retval: ['1a2e06']}]}

A value of the wrong type

value-ex2

The switch expects true or false, so V:111 returns null and lastError() explains why.

device.sendAai({query:"T:Title&&V:Online Gaming Tournament&&OY:1&&OX:1&&V:111"})
null
lastError()
Wrong format, expect boolean value

Chapter 6 of 10

Actions

Actions do something with the nodes a query found, or work on their own without nodes. action takes one command, several commands separated by ;, or an array of commands.

Overview

When a call fails, sendAai returns null and lastError() holds the reason.

1 example, 2 commands

An action that finds nothing

actions-ex1

The query has no match, so sendAai returns null and lastError() holds the message.

device.sendAai({query:"T:Not there", action:"getText"})
null
lastError()
No match found

Single Action

One command in action. The result comes back in retval, or null on an error.

1 example, 2 commands

An unknown action name

single-action-ex1

A misspelled action returns null, and lastError() names the invalid action.

device.sendAai({action:"getCurrentPackageNameX"});
null
lastError()
Invalid action: getCurrentPackageNameX

Multiple Actions

Several commands run from left to right, and the result is a list with one entry per command. If one fails, the whole call returns null.

2 examples, 3 commands

Three actions, three results

multiple-actions-ex1

The list holds the FindNode version, the current package name and the node count, in the order the actions ran.

device.sendAai({action:"version;getCurrentPackageName;getCount"})
{count: 3, list: [{retval: 19},{retval: 'com.solaredge.homeowner'},{retval: 59}]}

Find out which action failed

multiple-actions-ex2

lastError() gives the zero-based position of the failed action, here 1, with its error message.

device.sendAai({action:"version;getCurrentPackageNameX;getCount"});
null
lastError()
[1 - getCurrentPackageNameX] Invalid action: getCurrentPackageNameX

Data Type

Action arguments can be strings, integers, doubles, booleans and null. Version 17 added arrays, objects and functions. They follow JavaScript conventions.

1 example, 16 commands

Echo each data type

data-type-ex1

echo returns its argument, which shows how each type is read. Strings may be unquoted or in single or double quotes. Arrays and objects can be indexed with [n] and .key, also when nested.

device.sendAai({action:"echo(this is a string)"})
{retval: 'this is a string'}
device.sendAai({action:"echo('this is a string')"})
{retval: 'this is a string'}
device.sendAai({action:'echo("this is a string")'})
{retval: 'this is a string'}
device.sendAai({action:'echo(1234567)'})
{retval: 1234567}
device.sendAai({action:'echo(1234567.89)'})
{retval: 1234567.89}
device.sendAai({action:"echo(true)"})
{retval: true}
device.sendAai({action:"echo(false)"})
{retval: false}
device.sendAai({action:"echo([101,102,103,104,105])"})
{count: 5, retval: [101,102,103,104,105]}
device.sendAai({action:"echo({a:1, b:2, c:[1,2,3,4,5]})"})
{retval: {a: 1, b: 2, c: [1,2,3,4,5]}}
device.sendAai({action:"echo(null)"})
{retval:null}
device.sendAai({action:"echo({a:100, b:[1,2,3,4]})"})
{retval: {a: 100, b: [1,2,3,4]}}
device.sendAai({action:"echo({a:100, b:[1,2,3,4]}.b[2])"})
{retval: 3}
device.sendAai({action:"echo([1,2,{x:100, y:200, z:300}, 3])"})
{count: 4, retval: [1,2,{x: 100, y: 200, z: 300},3]}
device.sendAai({action:"echo([1,2,{x:100, y:200, z:300}, 3][2])"})
{retval: {x: 100, y: 200, z: 300}}
device.sendAai({action:"echo([1,2,{x:100, y:200, z:300}, 3][2].y)"})
{retval: 200}
device.sendAai({action:"echo([1,2,3,[4,5,6, [7,8,9]]][3][3][1])"})
{retval: 8}

node.X

node.X reads a property of a node. In filter it is checked for every node in the ML; in other commands it reads the first node.

2 examples, 3 commands

Keep only the buttons

node-x-ex1

filter(node.class == '.Button') removes every node whose class is not .Button. The ML goes from 26 nodes to 20.

device.sendAai({action:"getCount;filter(node.class == '.Button');getCount"})
{count: 3, list: [{retval: 26},{retval: true},{retval: 20}]}

Filter by a Boolean property

node-x-ex2

In the calculator only buttons are clickable, so node.isClickable leaves the same 20 nodes. The second command echoes the class and bounds of the first node.

device.sendAai({action:"getCount;filter(node.isClickable);getCount"})
{count: 3, list: [{retval: 26},{retval: true},{retval: 20}]}
device.sendAai({action:"echo(node.class);echo(node.bounds)"})
{count: 2, list: [{retval: 'android.view.ViewGroup'},{retval: 'Rect(0, 113 - 1440, 2952)'}]}

Variables in Actions and Query

When part of a query or action is only known at runtime, build the string in JavaScript: with concatenation, with template literals, or with the aaix and buildAction helpers.

12 examples, 29 commands

Build an action with string concatenation

variables-in-actions-and-query-ex1

With input set to "Hello", these three expressions show the strings that different quoting produces.

var input = "Hello"
"setText('" + input + "')"
setText('Hello')
'setText("' + input + '")'
setText("Hello")
'setText(' + input + ')'
setText(Hello)

Send the concatenated action

variables-in-actions-and-query-ex2

The built setText string is sent as the first action. click(OX:2) then presses the node two places to the right.

device.sendAai({query:"TP:textInput", action:["setText('" + input + "')", "click(OX:2)"]});
{count: 2, list: [{retval: true},{retval: true}]}

The same with a template literal

variables-in-actions-and-query-ex3

Inside backticks, ${input} inserts the variable. Quotes around it keep the text together if it contains a space.

device.sendAai({query:"TP:textInput", action:`setText("${input}");click(OX:2)`})
{count: 2, list: [{retval: true},{retval: true}]}

A variable inside the query

variables-in-actions-and-query-ex4

The regular expression is stored in a variable and inserted into the query, which returns all the digits on the calculator.

var text = "/^[0-9]$/"
device.sendAai({query:`T:${text}`, action:"getText"})
{count: 10, retval: ['7','8','9','4','5','6','1','2','3','0']}

Insert the results of functions

variables-in-actions-and-query-ex5

${c()} and ${d()} call functions that return the class and the description to match. ${x} supplies the fields for getNodes.

var c=()=>".CheckBox"
var x="T,D"
T,D
var d=()=>"Sunday"
device.sendAai({query:`C:${c()}&&D:${d()}`,action:`getNodes("${x}")`})
{count: 1, list: [{description: 'Sunday', id: '509a7', text: 'S'}]}

Build the whole action in a function

variables-in-actions-and-query-ex6

disp() returns the complete getNodes action as a string, which is then inserted as the action.

var disp=()=>`getNodes('${x}')`
disp()
getNodes('T,D')
device.sendAai({query:`C:${c()}&&D:${d()}`,action:`${disp()}`})
{count: 1, list: [{description: 'Sunday', id: '509a7', text: 'S'}]}

Write a function over several lines

variables-in-actions-and-query-ex7

A template literal lets the actions of a function span several lines. enterText sets the text, checks it, clicks the node two places to the right and sends Back.

device.sendAai({action:"function(enterText(Hello))",        enterText:`setText(BP:editable, %1);                   if(getText(BP:editable) != %1, error(Not match));                   click(BP:editable&&OX:2);                   sendKey(Back) `})

aaix builds an action string from a name and its arguments. It was made for versions before 14; template literals now do the same job.

aaix("setText", input)
setText("Hello")
aaix("sendKey", tcConst.keyCodes.KEYCODE_HOME, 0)
sendKey(3, 0)

The strings built by aaix are passed as actions.

device.sendAai({query:"TP:textInput", action:[aaix("setText", input), "click(OX:2)"]})
{count: 2, list: [{retval: true},{retval: true}]}
device.sendAai({action:aaix("sendKey", tcConst.keyCodes.KEYCODE_HOME, 0)})
{retval: true}

buildAction with multi-line text

variables-in-actions-and-query-ex10

buildAction quotes each argument safely, so text with newlines or HTML can be sent without breaking the command.

var action = buildAction('setClipData', 'htmlSend', 'Line 1\nLine 2\nLine 3', 'Line 1<br><b>Line 2</b><br>Line 3')
action
setClipData("htmlSend", "Line 1\nLine 2\nLine 3", "Line 1<br><b>Line 2</b><br>Line 3")
device.sendAai({action:action})
{retval: true}

buildAction with setConfig

variables-in-actions-and-query-ex11

Builds a setConfig action, sends it, and reads the value back with getConfig.

var action = buildAction("setConfig", "restartApp:waitRenderingType", "event")
action
setConfig("restartApp:waitRenderingType", "event")
device.sendAai({action:action})
{retval: true}
device.sendAai({action:"getConfig(restartApp:waitRenderingType)"})
{retval: 'event'}

Insert an argument unquoted with raw()

variables-in-actions-and-query-ex12

raw() inserts its text as is, so the JSON reaches echo as an object and not as a string.

var action = buildAction("echo", raw('{"a":1, "b":[1,2,3]}'))
action
echo({"a":1, "b":[1,2,3]})
device.sendAai({action:action})
{retval: {a: 1, b: [1,2,3]}}

Special Character in Action Parameters

From version 16, quotes, commas and semicolons inside an argument can be escaped with \\.

3 examples, 5 commands

Escape a quote, a comma and a semicolon

special-character-in-action-parameters-ex1

The escaped characters are stored as plain text, as getText shows.

device.sendAai({query:"T:Field 5&&OX:1", action:"setText(I\\'m John\\, she is Mary\\;);getText"})
{count: 2, list: [{retval: true},{retval: 'I'm John, she is Mary;'}]}

Open an app with an apostrophe in its name

special-character-in-action-parameters-ex2

The app name "Domino's" contains a single quote, which is escaped.

device.sendAai({action:"openApp(Domino\\'s)"})
{retval: true}

echo returns the text with the escaped quote, commas and double quotes in place.

device.sendAai({action:"echo(John\\'s ball)"});
{retval: 'John's ball'}
device.sendAai({action:'echo(a\\, b\\, c)'});
{retval: 'a, b, c'}
device.sendAai({action:'echo(he said \\"it was me\\")'});
{retval: 'he said "it was me"'}

Optional Query (OQ)

An action can carry its own query (OQ). With no prefix it searches within the ML and leaves the ML unchanged. + runs a new query and builds a new ML. * searches within the ML and replaces the ML with the result.

4 examples, 9 commands

The three prefixes

optional-query-ex1

All three find "5". With no prefix the ML keeps its seven nodes. With + or * only "5" is left in the ML, as the second getText shows.

device.sendAai({query:"T:/[3-9]/", actions:["getText(T:5)", "getText"]})
{count: 2, retval: [{retval: '5'},{retval: ['7','8','9','4','5','6','3']}]}
device.sendAai({query:"T:/[3-9]/", actions:["getText(+T:5)", "getText"]})
{count: 2, list: [{retval: '5'},{retval: '5'}]}
device.sendAai({query:"T:/[3-9]/", actions:["getText(*T:5)", "getText"]})
{count: 2, list: [{retval: '5'},{retval: '5'}]}

No prefix compared with *

optional-query-ex2

Without a prefix, each OX:1 starts from the same first node (7) and returns 8. With * the ML is replaced each time, so the second step moves on to 9.

device.sendAai({query:"T:/[3-9]/", actions:["getText(OX:1)", "getText(OX:1)", "getText"]})
{count: 3, list: [{retval: '8'},{retval: '8'},{retval: ['7','8','9','4','5','6','3']}]}
device.sendAai({query:"T:/[3-9]/", actions:["getText(*OX:1)", "getText(*OX:1)", "getText"]})
{count: 3, list: [{retval: '8'},{retval: '9'},{retval: '9'}]}

A new query, then a search within it

optional-query-ex3

+TP:anyText&&T:/[^0-9]/ builds a new ML of the 11 texts that are not digits. T:/^.$/ then picks the single-character ones from it.

device.sendAai({action:"getCount(+TP:anyText&&T:/[^0-9]/);getText(T:/^.$/)"})
{count: 2, list: [{retval: 11},{retval: ['÷','×','-','+','%','.','±','=']}]}

+ searches the screen, * only the ML

optional-query-ex4

"1" is not in the ML built by T:/[3-9]/. + finds it on the screen, while * searches only the ML and fails.

device.sendAai({query:"T:/[3-9]/", actions:["getText(+T:1)", "getText"]})
{count: 2, list: [{retval: '1'},{retval: '1'}]}
device.sendAai({query:"T:/[3-9]/", actions:["getText(*T:1)", "getText"]})
null
lastError()
[0 - getText] No match found

Conditional Expression (CondExp)

Conditions for if, assert and filter. A condition can be a Boolean value, a comparison, or any value judged true or false by JavaScript-like rules.

5 examples, 39 commands

Boolean conditions

conditional-expression-ex1

The condition can be a literal or a function such as exists. When it is false the third argument runs; here error stops with a message. The last command uses node.editable as the condition.

device.sendAai({action:"if(true, echo(OK))"});
{retval: 'OK'}
device.sendAai({action:"if(exists(T:/^[0-9]$/), echo(OK), echo(Not))"});
{retval: 'OK'}
var query = "T:Username&&OX:1";
device.sendAai({action:`if(exists(${query}), echo(Found), error(Username not found))`});
{retval: 'Found'}
device.sendAai({action:`if(exists(${query}), echo(Found), error(Username not found))`});
null
lastError()
User Exception: Username not found
device.sendAai({query:query, action:`if(node.editable, setText(${username}), error(Username text field not found))`})
{retval: true}

Which values count as true

conditional-expression-ex2

The isTrue function echoes how its argument is judged. 0, an empty string, false and null are false. Non-zero numbers, non-empty strings, arrays and objects are true.

device.sendAai({action:"function(isTrue)", isTrue:"if(%1, echo(is true), echo(is false))"})
{retval: true}
device.sendAai({action:"isTrue(0)"});
{retval: 'is false'}
device.sendAai({action:"isTrue(1)"});
{retval: 'is true'}
device.sendAai({action:"isTrue(Hello)"});
{retval: 'is true'}
device.sendAai({action:"isTrue('')"});
{retval: 'is false'}
device.sendAai({action:"isTrue(false)"});
{retval: 'is false'}
device.sendAai({action:"isTrue(null)"});
{retval: 'is false'}
device.sendAai({action:"isTrue([])"});
{retval: 'is true'}
device.sendAai({action:"isTrue({})"});
{retval: 'is true'}
device.sendAai({action:"isTrue([true,false][0])"});
{retval: 'is true'}
device.sendAai({action:"isTrue([true,false][1])"});
{retval: 'is false'}
device.sendAai({action:"isTrue(echo(100-100))"});
{retval: 'is false'}
device.sendAai({action:"isTrue(getText(BP:editable&&IX:0))"});
{retval: 'is true'}

echo shows the result of a comparison, which is an easy way to test one. The last three commands use comparisons inside if.

device.sendAai({action:"echo(40/2 == 20)"});
{retval: true}
device.sendAai({action:"echo(40/2 == 2*10)"});
{retval: true}
device.sendAai({action:"echo(40/2 == 2*10+1)"});
{retval: false}
device.sendAai({action:"if(getText(T:1&&TX)[-1] == '%', echo(matched), error(% not matched))"})
{retval: 'matched'}
device.sendAai({action:"if(saveImageInfo().format != 'png', saveImageSettings({format:'png'}))"})
{retval: true}
device.sendAai({action:"function(clickExists)", clickExists:"if(exists(T:%1), click(T:%1), return(false))"})
{retval: true}

Compare arrays and objects

conditional-expression-ex4

Arrays are equal when their elements match in the same order. Objects are equal when they have the same keys and values, in any order.

device.sendAai({action:"if([1,2,3] == [1,2,3],echo(true),echo(false))"})
{retval: true}
device.sendAai({action:"if([1,2,3] == [2,1,3],echo(true),echo(false))"})
{retval: false}
device.sendAai({action:"if({a:1, b:2} == {b:2, a:1},echo(true),echo(false))"})
{retval: true}
device.sendAai({action:"if({a:1, b:2} == {b:2, a:1, c:3},echo(true),echo(false))"})
{retval: false}
device.sendAai({action:"if([1,2,3][1] == 2,echo(true),echo(false))"})
{retval: true}
device.sendAai({action:"if({a:1, b:2}.a == 1,echo(true),echo(false))"})
{retval: true}
device.sendAai({action:"if({a:1, b:2}.b == 1,echo(true),echo(false))"})
{retval: false}
device.sendAai({action:"if([1,2,[4,5]][2] == [4,5],echo(true),echo(false))"})
{retval: true}

Check for Android 14 or above

conditional-expression-ex5

getAndroidVersion opens the settings and reads the version. ensureAndroidVersion compares it with its argument, 14 by default, and returns true or false.

device.sendAai({action:"function(getAndroidVersion)", getAndroidVersion:`     openAndroidSetting(DEVICE_INFO_SETTINGS);     newQuery(T:Software*);     click;     newQuery(T:Android version&&OY:1);     getText; `})
{retval: true}
device.sendAai({action:"getAndroidVersion"});
{retval: '16'}
device.sendAai({action:"function(ensureAndroidVersion(14))", ensureAndroidVersion:"if(getAndroidVersion() >= %1, return(true), return(false))"})
{retval: true}
device.sendAai({action:"ensureAndroidVersion"})
{retval: true}
device.sendAai({action:"ensureAndroidVersion(17)"})
{retval: false}

Chapter 7 of 10

Get commands

Commands that read information about nodes, the device, or the app on the screen.

getActivity

Returns the activity in the foreground, in a form that openActivity accepts.

1 example, 4 commands

Read the current activity and reopen it

getactivity-ex1

The first two commands show the activity names for Gmail and Chrome. The last two save the current activity in a variable and open it again with openActivity.

device.sendAai({action:"getActivity"})
{retval: 'com.google.android.gm/.ui.MailActivityGmail'}
device.sendAai({action:"getActivity"})
{retval: 'com.android.chrome/com.google.android.apps.chrome.Main'}
var activity = device.sendAai({action:"getActivity"}).retval
device.sendAai({action:`openActivity(${activity})`})
{retval: true}

getBounds (M)

Returns the bounds of the nodes in the ML as [left, top, right, bottom], together with their IDs.

1 example, 1 command

Get the bounds of the digit keys

getbounds-ex1

Returns one bounds array for each matching node, along with the node IDs.

device.sendAai({query:"T:/[0-9]/", action:"getBounds"})
{bounds: [[18,1158,370,1521],[370,1158,722,1521],[722,1158,1074,1521],[18,1521,370,1884],[370,1521,722,1884],[722,1521,1074,1884],[18,1884,370,2247],[370,1884,722,2247],[722,1884,1074,2247],[18,2247,370,2610]], count: 10, ids: ['98d1','9c92','a053','a7d5','ab96','af57','b6d9','ba9a','be5b','c5dd']}

getBoolProp and getAllBoolProps (O)

getBoolProp returns which of a node's main Boolean properties apply: editable, progressible, clickable, checkable, scrollable or longClickable. getAllBoolProps returns all of the node's Boolean properties.

1 example, 2 commands

Properties of a checkbox

getboolprop-and-getallboolprops-ex1

For "Checkbox 2", getBoolProp returns checkable and clickable. getAllBoolProps returns seven properties, including checked.

Checkbox 2 is checked.
device.sendAai({action:"getBoolProp(T:Checkbox 2)"})
{count: 2, retval: ['checkable','clickable']}
device.sendAai({action:"getAllBoolProps(T:Checkbox 2)"})
{count: 7, retval: ['checkable','multiLine','clickable','visibleToUser','focusable','enabled','checked']}

getBoolPropInAncestors (O)

Like getBoolProp, but it also walks up through the node's parents to the top of the tree.

1 example, 1 command

Find properties on parent nodes

getboolpropinancestors-ex1

Returns each property together with the ID of the node that has it.

device.sendAai({query:"T:John", action:"getBoolPropInAncestors"})
{count: 2, retval: [['clickable','1b86f'],['scrollable','16d5b']]}

getChecked (M)

Returns true or false for checkable nodes such as switches, checkboxes and radio buttons. Several nodes give an array.

3 examples, 3 commands

Which days are checked

getchecked-ex1

D:Sunday&&TX selects the row of seven day buttons, and getChecked returns the state of each.

Monday to Friday are checked.
device.sendAai({action:"getChecked(D:Sunday&&TX)"})
{count: 7, retval: [false,true,true,true,true,true,false]}

The same row, found by class

getchecked-ex2

No other element here has the class .CheckBox, so the first checkbox can serve as the reference node.

device.sendAai({action:"getChecked(C:.CheckBox&&IX:0&&TX)"})
{count: 7, retval: [false,true,true,true,true,true,false]}

List the checked days by name

getchecked-ex3

XBP:checked keeps only the checked nodes of the row, and getDescription returns their names.

device.sendAai({action:"getDescription(D:Sunday&&R:.day_button_0&&TX&&XBP:checked)"})
{count: 5, retval: ['Monday','Tuesday','Wednesday','Thursday','Friday']}

getCount (M)

Returns the number of nodes in the ML, or the number matched by an optional query.

1 example, 5 commands

Count nodes, with and without a match

getcount-ex1

The first two commands count all nodes and the nodes whose text contains a digit. A query with no match returns null with an error, while the same query given inside getCount returns 0.

device.sendAai({action:"getCount"})
{retval: 26}
device.sendAai({action:"getCount(T:/\\d/)"})
{retval: 10}
device.sendAai({query:"T:Not there", action:"getCount"})
null
lastError()
No match found
device.sendAai({action:"getCount(T:Not there)"})
{retval: 0}

getDeviceName

Returns the device name.

1 example, 1 command

Get the device name

getdevicename-ex1

The name comes back in retval.

device.sendAai({action:"getDeviceName"})
{retval: 'samsung-SM-S938U1'}

getFocus

Returns the node that has the focus, usually a text field or a slider, and makes it the ML.

1 example, 1 command

Fill the focused field, then the one below

getfocus-ex1

getFocus selects the focused field and setText fills it. addQuery(OY:1) moves to the field below for the second setText.

device.sendAai({action:["getFocus", aaix("setText", username), "addQuery(OY:1)", aaix("setText", password)]})
{count: 4, list: [{retval: '67693'},{retval: true},{count: 1},{retval: true}]}

getFuncRetval

Inside a function, returns the outputs of the commands that ran before it: all of them, or only those of the command named in the argument.

3 examples, 8 commands

Return every output of a function

getfuncretval-ex1

getFuncRetval() with no argument is the last command of testEcho, so calling the function returns the output of all five echo commands.

device.sendAai({action:"function(testEcho)", testEcho:`     echo(This is a test to echo in different types);     echo(true);     echo(1000);     echo(3.14159);     echo({a:1, b:{x:1, y:'string'}, c:[100, 200, 300]});     getFuncRetval()`})
{retval: true}
device.sendAai({action:"testEcho"})
{retval: [{retval: 'This is a test to echo in different types'},{retval: true},{retval: 1000},{retval: 3.14159},{retval: {a: 1, b: {x: 1, y: 'string'}, c: [100,200,300]}}]}

Return the output of one command

getfuncretval-ex2

The function passes its argument to getFuncRetval, so each call returns only the output of the command named.

device.sendAai({action:"function(testFR)", testFR:`     echo(10*20);     getDeviceName();     getCount();     getCurrentPackageName();     getFuncRetval(%1)`})
{retval: true}
device.sendAai({action:"testFR(echo)"})
{count: 1, retval: [{retval: 200}]}
device.sendAai({action:"testFR(getDeviceName)"})
{count: 1, retval: [{retval: 'samsung-SM-S938U1'}]}
device.sendAai({action:"testFR(getCount)"})
{count: 1, retval: [{retval: 16}]}

Return a text read on another screen

getfuncretval-ex3

The function opens the location screen, reads the address and goes back. getFuncRetval(getText) makes the address the result of the function.

device.sendAai({action:"function(getAddress)", getAddress:`     click(T:Location);     newQuery(R:.map_header_text, 1000);     getText;     sendKey(Back);     getFuncRetval(getText)`});
{retval: true}
device.sendAai({action:"getAddress"})
{count: 1, retval: [{retval: '9999 Ocean Drive'}]}

getIds (M)

Returns the count and the IDs of the nodes in the ML. This is the default action when none is given.

1 example, 2 commands

List node IDs

getids-ex1

An empty call returns the IDs of all 26 nodes. With an optional query, getIds returns only the four .Button nodes.

device.sendAai({})
{count: 26, retval: ['7708','d8a2','e7a6','ef28','824b','860c','89cd','8d8e','914f','9510','98d1','9c92','a053','a414','a7d5','ab96','af57','b318','b6d9','ba9a','be5b','c21c','c5dd','c99e','cd5f','d120']}
device.sendAai({action:"getIds(C:.Button)"})
{count: 4, retval: ['8dc5','9547','9908','9cc9']}

getInstalledApps

Returns every app installed on the device, including system apps, with its label, package name and version.

2 examples, 5 commands

List the installed apps

getinstalledapps-ex1

The result holds 567 apps. Only the first two are shown here.

var result = device.sendAai({action:"getInstalledApps"})
{
    count: 567,
    retval: [
        {
            label: 'Total Control',
            packageName: 'com.sigma_rt.totalcontrol',
            version: '11.0.30.77777'
        },
        {
            label: 'Maps',
            packageName: 'com.google.android.apps.maps',
            version: '26.17.05.901412876'
        }
// ... omitted
    ]
}

Work with the result in JavaScript

getinstalledapps-ex2

The list is an ordinary array: its length matches count, and an entry can be read by index.

result.count
567
result.retval.length
567
var apps = result.retval;
apps[123]
{label: 'Maps', packageName: 'com.google.android.apps.maps', version: '26.17.05.901412876'}

getLastError, clearLastError

getLastError returns the lastError() message as an action result. clearLastError resets it to an empty string.

1 example, 5 commands

Read and clear the last error

getlasterror-ex1

An invalid query sets the error. getLastError returns the same message, and after clearLastError it is empty.

device.sendAai({query:"Y:123"})
null
lastError()
Query Error: Invalid query type "Y"
device.sendAai({action:"getLastError"})
{retval: 'Query Error: Invalid query type "Y"'}
device.sendAai({action:"clearLastError"})
{retval: true}
device.sendAai({action:"getLastError"})
{retval: ''}

getNodes (M)

Returns information about nodes. The optional fields argument chooses what to return, for example T for text, R for resource ID, BP for Boolean properties, or all.

3 examples, 3 commands

Resource ID and text of the digit keys

getnodes-ex1

The fields 'R,T' return the resource ID and the text of each node. The list is quoted because it contains a comma.

device.sendAai({action:"getNodes(T:/^[0-9]$/,'R,T')"})
{count: 10, retval: [{id: 'a7d3', resourceId: '.button_seven', text: '7'},{id: 'ab94', resourceId: '.button_eight', text: '8'},{id: 'af55', resourceId: '.button_nine', text: '9'},{id: 'b6d7', resourceId: '.button_four', text: '4'},{id: 'ba98', resourceId: '.button_five', text: '5'},{id: 'be59', resourceId: '.button_six', text: '6'},{id: 'c5db', resourceId: '.button_one', text: '1'},{id: 'c99c', resourceId: '.button_two', text: '2'},{id: 'cd5d', resourceId: '.button_three', text: '3'},{id: 'd4df', resourceId: '.button_zero', text: '0'}]}

Range information of sliders

getnodes-ex2

RI returns the type, minimum, maximum and current value of each slider.

device.sendAai({query:"C:.SeekBar", action:"getNodes(RI)"})
{count: 3, retval: [{id: '5541c', rangeInfo: {current: 47, max: 100, min: 0, type: 0, typeString: 'int'}},{id: '566e1', rangeInfo: {current: 81, max: 100, min: 0, type: 0, typeString: 'int'}},{id: '579a6', rangeInfo: {current: 2, max: 10, min: 0, type: 0, typeString: 'int'}}]}

Boolean properties of one slider

getnodes-ex3

IX:-1 picks the last slider, and BP returns all its Boolean properties.

device.sendAai({query:"C:.SeekBar&&IX:-1", action:"getNodes(BP)"})
{count: 1, retval: [{booleanProperties: {checkable: false, checked: false, clickable: false, editable: false, enabled: true, focusable: true, focused: false, longClickable: false, multiLine: false, scrollable: false, selected: false, visibleToUser: true}, id: '579a6'}]}

getPackageName, getCurrentPackageName, getOnScreenPackageNames

getPackageName, the same as getCurrentPackageName, returns the package of the running app. getOnScreenPackageNames returns the packages of all apps on the screen.

1 example, 2 commands

Get the package names

getpackagename-ex1

The first command returns the package of the current app. The second returns the three packages on the screen.

device.sendAai({action:"getPackageName"})
{retval: 'com.google.android.gm'}
device.sendAai({action:"getOnScreenPackageNames"})
{count: 3, retval: ['com.android.systemui','com.sec.android.app.launcher','com.android.chrome']}

getProgress (O)

Returns the range information of a slider or similar element. It is shorthand for getNodes(RI).

1 example, 1 command

Read a slider's value

getprogress-ex1

The slider is at 41 on a scale from 0 to 100.

device.sendAai({action:"getProgress(C:.SeekBar)"})
{retval: {current: 41, max: 100, min: 0, type: 0, typeString: 'int'}}

getQuery/getUniqQuery (O)

Generates a query that matches a given node. getUniqQuery returns a query that matches only that node; the query from getQuery may match several.

1 example, 1 command

A unique query for each node in a row

getquery-ex1

T:Controls&&TX returns the three nodes in the row. For each one, getUniqQuery builds a query that reaches it from the "Controls" text.

The row holds an icon, the text and an arrow.
device.sendAai({query:"T:Controls&&TX"}).retval.forEach(     x => print(device.sendAai({elements:[x],action:"getUniqQuery"}).retval) )
T:Controls&&OX:-1
T:Controls
T:Controls&&OX:1

getSystemProp

Reads Android system properties, like getprop. A plain argument searches names and values. An argument starting with = returns the value of that exact key.

1 example, 2 commands

Search for a property, or read one exactly

getsystemprop-ex1

The search returns the two properties whose names contain ro.product.model. With =, only the value of that exact key comes back.

device.sendAai({action:"getSystemProp(ro.product.model)"})
{count: 2, retval: [{'ro.product.model': 'SM-S938U1'},{'ro.product.model_for_attestation': 'SM-S938U1'}]}
device.sendAai({action:"getSystemProp('=ro.product.model')"})
{retval: 'SM-S938U1'}

getText/getDescription/getHintText/getTextFormats (O|M)

Read the text, description or hint text of nodes. One node gives a value, several give an array. getTextFormats also returns formatted text as HTML.

4 examples, 8 commands

One node or several

gettext-ex1

Four nodes return an array of text. A single node returns just the text.

device.sendAai({query:"TP:line,bottom,-1", action:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']}
// One value will not return an array
device.sendAai({query:"TP:line,bottom,-1&&IX:1", action:"getText"})
{retval: 'Calls'}

Text changes, hint text stays

gettext-ex2

After two fields are filled, getText returns the new content, with the password masked. getHintText still returns the original hints.

device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','Number','Email','text password','Person Name']}
device.sendAai({query:"BP:editable", action:"setText(T:Number, '100');setText(T:text password, secretWord)"})
{count: 2, list: [{retval: true},{retval: true}]}
device.sendAai({query:"BP:editable", action:"getText"})
{retval: ['text','text','100','Email','••••••••••','Person Name']}
// getHintText will maintain the value of the original hints, getText does not
device.sendAai({query:"BP:editable", action:"getHintText"})
{retval: ['text','text','Number','Email','text password','Person Name']}

Formatted text as HTML

gettext-ex3

For text with colors, getTextFormats returns the plain text in retval and the formatting in html.

The formatted text on the screen.
device.sendAai({query:"T:*Red*", action:"getTextFormats"})
{html: '<p dir="ltr" style="white-space:pre-wrap;">Colors: <span style="color:#FF0000;">Red</span> and <span style="color:#FFFFFF;"><span style="background-color:#0000FF;">Blue background</span></span></p>
', retval: 'Colors: Red and Blue background'}

Formatted text from two nodes

gettext-ex4

With two matching nodes, the result is an array with html and retval for each.

Two lines of formatted text.
device.sendAai({query:"T:/Red|mono/", action:"getTextFormats"})
{count: 2, retval: [{html: '<p dir="ltr" style="white-space:pre-wrap;"><tt>monospace (test: iiiWWW)</tt></p>
', retval: 'monospace (test: iiiWWW)'},{html: '<p dir="ltr" style="white-space:pre-wrap;">Colors: <span style="color:#FF0000;">Red</span> and <span style="color:#FFFFFF;"><span style="background-color:#0000FF;">Blue background</span></span></p>
', retval: 'Colors: Red and Blue background'}]}

Chapter 8 of 10

Query commands

Commands that build or change the Matching List (ML) from inside an action.

newQuery/addQuery/waitQuery/refreshQuery exists (M)

newQuery runs a new query and rebuilds the ML. addQuery searches within the current ML. waitQuery waits for a query to match without changing the ML. refreshQuery rereads the screen, and exists returns true or false.

3 examples, 6 commands

Three ways to wait for a node and click it

newquery-ex1

Each waits up to 10 seconds for the text to appear and then clicks it. newQuery and exists(+…) also change the ML; waitQuery does not.

device.sendAai({action:`waitQuery(T:${text},10000);click(T:${text})`})
{count: 2, list: [{retval: true},{retval: true}]}
device.sendAai({action:`newQuery(T:${text},10000);click`})
{count: 2, list: [{retval: 1},{retval: true}]}
device.sendAai({action:`exists(+T:${text},10000);click`})
{count: 2, list: [{retval: 1},{retval: true}]}

A click that opens a new window

newquery-ex2

The click opens a new window, but the ML still holds the nodes of the old one, so getText finds nothing.

device.sendAai({action:`openAndroidSetting(DEVICE_INFO_SETTINGS); click(T:Software information); getText('T:Android version&&OY:1')`})
null
lastError()
[2 - getText] No match found

Reread the new window with refreshQuery

newquery-ex3

refreshQuery after the click reads the new window, and getText then returns the Android version.

device.sendAai({action:`openAndroidSetting(DEVICE_INFO_SETTINGS);     click(T:Software information);     refreshQuery;     getText('T:Android version&&OY:1')`})
{count: 4, list: [{retval: true},{retval: true},{retval: true},{retval: '16'}]}

intersectX/intersectY (O M)

The action forms of TX and TY. They take one node, from a query or the first in the ML, and put the nodes that intersect it in the ML.

5 examples, 5 commands

The row of a key

intersectx-ex1

intersectX(T:3) finds the four nodes in the same row as 3.

device.sendAai({actions:["intersectX(T:3)", "getText"]})
{count: 2, list: [{retval: 4},{retval: ['1','2','3','%']}]}

The column of a key

intersectx-ex2

intersectY also returns nodes with no visible text, such as the result field or a view group. They show here as {}.

device.sendAai({actions:["intersectY(T:3)", "getText"]})
{count: 2, list: [{retval: 8},{retval: [{},{},{},'×','9','6','3','±']}]}

Keep only nodes with text, using addQuery

intersectx-ex3

addQuery(T:/.+/) keeps the nodes that have at least one character.

device.sendAai({actions:["intersectY(T:3)", "addQuery(T:/.+/)", "getText"]})
{count: 3, list: [{retval: 8},{retval: 5},{count: 5, retval: ['×','9','6','3','±']}]}

The same with an optional query

intersectx-ex4

The text filter is given to getText directly.

device.sendAai({actions:["intersectY(T:3)", "getText(T:/.+/)"]})
{count: 2, list: [{retval: 8},{count: 5, retval: ['×','9','6','3','±']}]}

The same with filter

intersectx-ex5

filter(node.text != null) removes the nodes that have no text.

device.sendAai({actions:["intersectY(T:3)", "filter(node.text != null)", "getText"]})
{count: 3, list: [{retval: 8},{retval: true},{count: 5, retval: ['×','9','6','3','±']}]}

push/pop & load/save (M)

These commands store the current ML and restore it later. With a name, an ML saved by save can be loaded in a later sendAai() call.

4 examples, 13 commands

Save an ML and reuse it

push-ex1

The digit keys are saved as calc. Later calls load them and click 2 and 3 without a new query. The result field shows 12, then 123.

device.sendAai({query:"T:/^[0-9]$/", action:"nsClick(T:1);save(calc)"})
{count: 2, list: [{retval: true},{retval: true}]}
device.sendAai({action:"load(calc);nsClick(T:2);getText(R:.editText_result)"})
{count: 3, list: [{retval: true},{retval: true},{retval: '12'}]}
device.sendAai({action:"load(calc);nsClick(T:3);getText(R:.editText_result)"})
{count: 3, list: [{retval: true},{retval: true},{retval: '123'}]}

Loading is faster than searching

push-ex2

With execTime:true the result includes the time spent in FindNode. Each full query takes about 160 ms, and loading the saved ML takes about 13 ms.

device.sendAai({query:"TP:reduced&&T:/./&&IX:-1&&TX", action:"getText", execTime:true})
{count: 5, retval: ['Games','Apps','Search','Books','You'], timeFindNode: 161}
device.sendAai({query:"TP:reduced&&T:/./&&IX:-1&&TX", action:"getText(IX:0)", execTime:true})
{retval: 'Games', timeFindNode: 154}
device.sendAai({query:"TP:reduced&&T:/./&&IX:-1&&TX", action:"getText(IX:1)", execTime:true})
{retval: 'Apps', timeFindNode: 169}
device.sendAai({query:"TP:reduced&&T:/./&&IX:-1&&TX", action:"getText;save(playStore)", execTime:true})
{count: 2, list: [{count: 5, retval: ['Games','Apps','Search','Books','You']},{retval: true}], timeFindNode: 153}
device.sendAai({action:"load(playStore);getText(IX:0)", execTime:true})
{count: 2, list: [{retval: true},{retval: 'Games'}], timeFindNode: 13}
device.sendAai({action:"load(playStore);getText(IX:1)", execTime:true})
{count: 2, list: [{retval: true},{retval: 'Apps'}], timeFindNode: 12}

Loading in a different app fails

push-ex3

The ML was saved in the Play Store, but another app is now in the foreground, so load returns an error.

device.sendAai({action:"load(playStore);getText(IX:0)"})
null
lastError()
[0 - load] Node is saved in com.android.vending, but current package is com.sec.android.app.launcher

Loading an unknown name fails

push-ex4

No ML was saved under this name.

device.sendAai({action:"load(Noname);getText(IX:0)"})
null
lastError()
[0 - load] Cannot find saved nodes for Noname

reduceNodes (M M)

The action form of the RN query. It reduces the current ML and returns the number of nodes left.

1 example, 2 commands

The same count as TP:reduced

reducenodes-ex1

TP:reduced returns 54 nodes, and reduceNodes on the default ML also leaves 54.

device.sendAai({query:"TP:reduced"}).count
54
device.sendAai({action:"reduceNodes"})
{retval: 54}

viewGroup (O M)

The action form of the VG query. It takes an optional query and a level, and changes the ML to the group.

1 example, 2 commands

Read a value inside the Battery group

viewgroup-2-ex1

Both forms select the group around "Battery" and read the text in it that contains a percentage.

The Battery group (green).
device.sendAai({query:"T:Battery&&VG", action:"getText(T:/\\d%/)"})
{retval: '92% available'}
device.sendAai({actions:["viewGroup(T:Battery)","getText(T:/\\d%/)"]})
{count: 2, list: [{retval: 6},{retval: '92% available'}]}

Chapter 9 of 10

Action commands on nodes

Commands that act on nodes: click them, type into them, scroll to them, loop over them, or wait for them to change.

click/nsClick/click2/longClick/ clickForNewWindow /clickDown/clickUp (O)

Clicks on a node. click waits for the screen to start changing. nsClick clicks without waiting and also accepts x and y coordinates. clickDown and clickUp press and release separately.

3 examples, 9 commands

Click a node

click-ex1

Both commands click the 5 key. nsClick does not wait for the screen to change.

device.sendAai({query:"T:5", action:"click"})
{retval: true}
device.sendAai({action:"nsClick(T:5)"})
{retval: true}

Click at a calculated position

click-ex2

The bounds of the 4 key are used to work out the centre of the key to its right, and nsClick(x, y) clicks there.

device.sendAai({action:"getBounds(T:4)"})
{bounds: [[18,1585,370,1981]], count: 1, retval: ['a641']}
var dim = device.sendAai({action:"getBounds(T:4)"}).bounds[0]
var x = (dim[2]-dim[0])/2 + dim[2]
var y = (dim[3]-dim[1])/2 + dim[1]
print(x + "," + y)
546,1783
device.sendAai({action:`nsClick(${x}, ${y})`})
{retval: true}

Hold a click for 3 seconds

click-ex3

clickDown presses, sleep(3000) waits, and clickUp releases.

// Use clickDown and clickUp to simulate 3 seconds long click
device.sendAai({query:"T:Hold 3 seconds", action:"clickDown;sleep(3000);clickUp"})
{retval: true}

echo, log (O)

echo returns its argument in retval; log writes it to logcat. The argument can be a value, a function call, a comparison or an arithmetic expression.

3 examples, 6 commands

Echo different values

echo-ex1

Six values in one call, each returned in its own retval.

device.sendAai({action:"echo(string); echo(100); echo(3.14159); echo(true); echo(false); echo(null)"})
{count: 6, list: [{retval: 'string'},{retval: 100},{retval: 3.14159},{retval: true},{retval: false},{retval: 'null'}]}

Echo part of a function result

echo-ex2

listAndroidSettings() returns a list, and [0] and [-1] pick its first and last entries.

device.sendAai({action:"echo(listAndroidSettings()[0])"});
{retval: 'ACCESSIBILITY_DETAILS_SETTINGS'}
device.sendAai({action:"echo(listAndroidSettings()[-1])"});
{retval: 'ZEN_MODE_PRIORITY_SETTINGS'}

Arithmetic, and log in place of echo

echo-ex3

The text set in the field is an arithmetic expression, and echo returns its result, 192807. log returns true and writes the value to logcat.

var s = "(123 + 456) * (789-456)"
device.sendAai({action:`setText(BP:editable,${s}); echo(getText(BP:editable))`})
{count: 2, list: [{retval: true},{retval: 192807}]}
device.sendAai({action:`setText(BP:editable,${s});log(getText(BP:editable))`})
{count: 2, list: [{retval: true},{retval: true}]}

filter (M)

Goes through every node in the ML and removes the ones that do not meet the condition, which is usually written with node.X.

2 examples, 3 commands

Keep the one editable node

filter-ex1

push stores the ML of 227 nodes, filter(node.isEditable) reduces it to the single input field for setText, and pop restores the 227. The second command keeps only the nodes that have text.

device.sendAai({action:`getCount;push;filter(node.isEditable);getCount; setText(${input});pop;getCount`})
{count: 7, list: [{retval: 227},{retval: true},{retval: true},{retval: 1},{retval: true},{retval: true},{retval: 227}]}
device.sendAai({action:"filter(node.text!=null); getText"})
{count: 2, list: [{retval: true},{count: 21, retval: ['Calculator','AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%', '0','.','±','=']}]}

Chain two filters

filter-ex2

node.text is short for node.text != null. The second filter then keeps only the numbers.

device.sendAai({action:"filter(node.text);filter(node.isNumber);getText"})
{count: 3, list: [{retval: true},{retval: true},{count: 10, retval: ['7','8','9','4','5','6','1','2','3','0']}]}

forEach (M)

Runs an action once for each node in the ML and returns one result per node.

1 example, 4 commands

Run an action for each node

foreach-ex1

getText runs once on four nodes and returns an array. forEach(getText) runs four times and returns four results. In the last command, setChecked(true) runs on each day button, and true marks the ones that changed.

device.sendAai({query:"T:1&&TX", action:"getText"})
{count: 4, retval: ['1','2','3','%']}
device.sendAai({query:"T:1&&TX", action:"forEach(getText)"})
{count: 4, retval: [{retval: '1'},{retval: '2'},{retval: '3'},{retval: '%'}]}
device.sendAai({query:"R:/.day_button_[0-6]/", action:"getText"})
{count: 7, retval: ['S','M','T','W','T','F','S']}
device.sendAai({query:"R:/.day_button_[0-6]/", action:"forEach(setChecked(true))"})
{count: 7, retval: [{retval: true},{retval: false},{retval: false},{retval: false},{retval: false},{retval: false},{retval: true}]}

If, assert (O)

if(<condition>, <then>, <else>) runs one of two actions. assert(<condition>) returns true or raises an AssertionError.

7 examples, 17 commands

Change the display mode if needed, then read

if-ex1

The battery shows either miles or a percentage. If the miles text exists, the function clicks it to change the mode, and then reads the percentage.

device.sendAai({action:"function(getPctg)", getPctg:"if(exists(+T:/^\\d+ mi/), nsClick);getText(+T:/^\\d+%/)"})
{retval: true}
device.sendAai({action:"getPctg"})
{retval: '79%'}

A function that returns a threshold

if-ex2

getLowPctg returns 20, the level at which charging is needed.

device.sendAai({action:"function(getLowPctg)", getLowPctg:"echo(20)"})
{retval: true}

Compare two function results

if-ex3

needCharge returns true when the battery percentage is at or below the threshold. At 79% it returns false.

device.sendAai({action:"function(needCharge)", needCharge:"if(getPctg() <= getLowPctg(), return(true), return(false))"})
{retval: true}
device.sendAai({action:"needCharge"})
{retval: false}

The same with return

if-ex4

return can return the comparison directly. After the threshold is redefined as 100, needCharge returns true.

device.sendAai({action:"function(needCharge)", needCharge:"return(getPctg() <= getLowPctg())"})
{retval: true}
device.sendAai({action:"needCharge"})
{retval: false}
device.sendAai({action:"function(getLowPctg)", getLowPctg:"echo(100)"})
{retval: true}
device.sendAai({action:"needCharge"})
{retval: true}

A function with two arguments

if-ex5

add clicks the two digits with + and = on the calculator and reads the result.

device.sendAai({action:"function(add)", add:"click(T:%1);click(T:+);click(T:%2);click(T:=);getText(+R:.formula)"})
{retval: true}
device.sendAai({action:"add(8,9)"})
{retval: '17'}

Check the calculator against echo

if-ex6

equal compares the calculator's result with the arithmetic done by echo.

device.sendAai({action:"function(equal)", equal:"if(add(%1,%2) == echo(%1+%2), return(true), return(false))"})
{retval: true}
device.sendAai({action:"equal(8,9)"})
{retval: true}

assert

if-ex7

A false condition raises an AssertionError, with your own message if one is given. A true condition returns true.

device.sendAai({action:"assert(add(8,9)==10)"})
[AssertionError 'Assertion error on "add(8,9)==10"']
device.sendAai({action:"assert(echo(true))"})
{retval: true}
device.sendAai({action:"assert(echo(false))"})
[AssertionError 'Assertion error on "echo(false)"']
device.sendAai({action:"assert(echo(false), 'This should not happen')"})
[AssertionError 'This should not happen']

refresh (M)

Node information is cached. refresh rereads it after the screen has changed.

1 example, 1 command

Read a value that changes

refresh-ex1

The CPU usage is read, and after two seconds and a refresh it is read again: 29 becomes 78.

device.sendAai({query:"T:Overall Cpu Usage&&OX:1", actions:["getText", "sleep(2000)", "refresh", "getText"]})
{count: 4, list: [{retval: '29'},{retval: true},{retval: true},{retval: '78'}]}

repeat (M)

Runs an action a given number of times and returns one result per run. The maximum is 20 unless raised with setConfig.

4 examples, 4 commands

Click a key five times

repeat-ex1

Clicks the 1 key on the calculator five times.

device.sendAai({action:"repeat(5,click(T:1))"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}

Lower the charging current by 1 A

repeat-ex2

Clicks the arrow to the left of the current value.

The charging current with its left and right arrows.
device.sendAai({action:"click(T:/[0-9]+ A/&&OX:-1)"})
{retval: true}

Lower it by 5 A

repeat-ex3

The same click, repeated five times.

device.sendAai({action:"repeat(5, click(T:/[0-9]+ A/&&OX:-1))"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}

Lower it and check the result

repeat-ex4

Reads the value, clicks five times, refreshes and reads again: 27 A becomes 22 A.

device.sendAai({query:"T:/[0-9]+ A/", action:"getText;repeat(5,click(OX:-1));refresh;getText"})
{count: 4, list: [{retval: '27 A'},{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]},{retval: true},{retval: '22 A'}]}

saveImage, saveImageInfo, saveImageSettings (M)

saveImage saves a picture of the nodes in the ML on the device and returns the file path. saveImageSettings changes the format, folder, quality and resolution, and saveImageInfo returns the current settings.

4 examples, 8 commands

Save an image of the digit keys

saveimage-ex1

The first image is saved as a JPEG in the shared Pictures folder. After the settings change, the same command saves a PNG in Total Control's private folder.

The saved image covers the keys 0 to 9.
device.sendAai({query:"T:/^[0-9]$/", action:"saveImage"})
{retval: '/storage/emulated/0/Pictures/Samsung-SM-S908-2024-12-01-03-51-48.jpg'}
device.sendAai({action:"saveImageSettings({format:'png',folder:'private', resolution:'720p'})"})
{retval: true}
device.sendAai({query:"T:/^[0-9]$/", action:"saveImage"})
{retval: '/storage/emulated/0/Android/data/com.sigma_rt.totalcontrol/files/Pictures/Samsung-SM-S908-2024-12-01-03-56-18.png'}

Save only one node

saveimage-ex2

IX:0 limits the image to the first node, the 7 key. The settings are changed first and checked with saveImageInfo.

The first node is 7.
device.sendAai({action:"saveImageSettings({format:'jpg',quality:50, folder:'private',resolution:'1080p'})"})
{retval: true}
device.sendAai({action:"saveImageInfo"})
{retval: {folder: 'private', format: 'jpeg', quality: 50, resolution: '1080p'}}
device.sendAai({query:"T:/^[0-9]$/", action:"saveImage(IX:0)"})
{retval: '/storage/emulated/0/Android/data/com.sigma_rt.totalcontrol/files/Pictures/Samsung-SM-S908-2024-12-01-04-04-45.jpg'}

Save a navigation bar

saveimage-ex3

VG selects the group around "Calls", so the image covers the whole bar.

The saved image.
device.sendAai({query:"T:Calls&&VG", action:"saveImage"})
{retval: '/storage/emulated/0/Android/data/com.sigma_rt.totalcontrol/files/Pictures/Samsung-SM-S908-2024-12-01-04-14-47.jpg'}

Save the checkboxes

saveimage-ex4

The query matches the checkboxes, and the image covers all of them.

The saved image.
device.sendAai({query:"C:.CheckBox", action:"saveImage"})
{retval: '/storage/emulated/0/Android/data/com.sigma_rt.totalcontrol/files/Pictures/Samsung-SM-S908-2024-12-01-04-30-37.jpg'}

scrollIntoView (M)

Scrolls until a node matching the query is found and fully visible, then puts it in the ML. The direction and the maximum number of pages are optional.

2 examples, 2 commands

Scroll a long list to a name and click it

scrollintoview-ex1

Scrolls up to 50 pages until "John" is found, then clicks it.

device.sendAai({actions:["scrollIntoView(T:John,50)", "click"]})
{count: 2, list: [{retval: true},{retval: true}]}

Scroll to a value and read it

scrollintoview-ex2

Finds the text that is a number followed by "miles" and reads it.

device.sendAai({action:"scrollIntoView(T:/[0-9]+ miles/, topDown, 2);getText"})
{count: 2, list: [{retval: true},{retval: '9,999 miles'}]}

openActivity

Starts an activity through Android's am start. A plain activity name is passed with -n; an argument that starts with - is passed as it is.

1 example, 8 commands

Open activities and intents

openactivity-ex1

The first commands reopen the current activity by name. The others pass am start options: open a web page, launch YouTube, open the Wi-Fi settings and start an email.

var activity = device.sendAai({action:"getActivity"}).retval;
activity
com.google.android.gm/.ComposeActivityGmailExternal
device.sendAai({action:`openActivity(${activity})`})
{retval: true}
device.sendAai({action:'openActivity(-a android.intent.action.VIEW -d https://www.sigma-rt.com)'})
{retval: true}
device.sendAai({action:"openActivity(-d https://www.sigma-rt.com)"})
device.sendAai({action:"openActivity(-a android.intent.action.MAIN -c android.intent.category.LAUNCHER -p com.google.android.youtube)"})
{retval: true}
device.sendAai({action:"openActivity(-a android.settings. -a android.settings.WIFI_SETTINGS)"})
{retval: true}
device.sendAai({action:"openActivity(-a android.intent.action.SENDTO -d mailto:support@sigma-rt.com)"})
{retval: true}

setProgress (O)

Sets the value of a slider. The range comes from getProgress or getNodes('RI').

2 examples, 4 commands

Set the brightness to 50%

setprogress-ex1

Reads the slider's maximum and sets the value to half of it.

The Brightness slider in the Display settings.
device.sendAai({query:"T:Brightness&&VG&&XC:.SeekBar", action:"getProgress"})
{retval: {current: 85983232, max: 267386880, min: 0, type: 0, typeString: 'int'}}
var max = device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:"getProgress"}).retval.max
267386880
device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:aaix("setProgress", max/2)})
{retval: true}

Set every slider to 50%

setprogress-ex2

Loops over all the sliders in the sound settings, reads each maximum and sets half of it.

device.sendAai({query:"C:.SeekBar"}).retval.forEach(     x => {         var max = device.sendAai({elements:[x], action:"getProgress"}).retval.max;         device.sendAai({elements:[x], action:`setProgress(${max/2})`});     } )

setText (O)

Enters text into a text field directly, without an input method. By default it replaces the existing text; a leading + appends.

7 examples, 15 commands

Read the fields, fill by hint, and append

settext-ex1

The fields are read with getText and getHintText, then chosen by their hint text. The last command appends " World" with a leading +.

The text fields found by TP:textInput.
device.sendAai({query:"TP:textInput", action:"getText"})
{count: 5, retval: ['Text','Number','Email','Text password','Text']}
// Hint Text starts support on API 26
device.sendAai({query:"TP:textInput", action:"getHintText"})
{count: 5, retval: ['Text','Number','Email','Text password','Text']}
// setText based on hint text
device.sendAai({query:"TP:textInput,Email", action:"setText(support@sigma-rt.com)"})
{retval: true}
device.sendAai({query:"BP:editable", action:"setText(HT:Number, 100);setText(HT:Text password, secretWord)"})
// Use "+" for concatenation
device.sendAai({query:"TP:textInput&&IX:-1", action:"setText('Hello');setText('+ World'); getText"})
{count: 3, list: [{retval: true},{retval: true},{retval: 'Hello World'}]}

Send with Enter

settext-ex2

A \n at the end of the text triggers Send in apps where Enter sends the message.

// For Enter as Send, add "\n" at the end of text
device.sendAai({query:"TP:textInput", action: "setText('Hello\n')"})

Insert emoji

settext-ex3

A template literal builds the emoji from code points and places them in the middle of the text.

// Add Emoji at the middle of string
device.sendAai({query:"TP:textInput&&IX:-1", action:`setText("hello ${String.fromCodePoint(0x1f643, 0x1f644)} world");getText`})

Five ways to reach the Number field

settext-ex4

By its text, by the template's hint argument, by HT, by its label, or by position with IX:1.

// Below are 5 ways to enter on "Number" field:
device.sendAai({query:"BP:editable&&T:Number", action:"setText(100)"})
{retval: true}
device.sendAai({query:"TP:textInput,Number", action:"setText(200)"})
{retval: true}
device.sendAai({query:"BP:editable&&HT:Number", action:"setText(300)"})
{retval: true}
device.sendAai({query:"T:Field 2", action:"setText(400)"})
{retval: true}
device.sendAai({query:"TP:textInput", action:"setText(IX:1,500)"})
{retval: true}

A function that fills the next field

settext-ex5

setNextText moves one field down (*OY:1) and sets its text.

device.sendAai({query:"TP:textInput", action:"function(setNextText)", setNextText:"setText(*OY:1, %1)"})
{retval: true}

Fill five fields in one call

settext-ex6

The first field is set by position, and setNextText fills each field below it in turn.

device.sendAai({query:"TP:textInput", action:`   setText(IX:0, Hello);   setNextText(100);   setNextText(support@sigma-rt.com);   setNextText(password);   setNextText(long long long word)   `})
{count: 5, list: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: true}]}

Check the result

settext-ex7

getText shows the five values, with the password masked.

device.sendAai({query:"TP:textInput", action:"getText"})
{count: 5, retval: ['Hello','100','support@sigma-rt.com','••••••••','long long long word']}

showOnScreen (O)

Scrolls until the first node is fully visible on the screen.

1 example, 1 command

Bring a node fully into view

showonscreen-ex1

Returns true because the screen was scrolled. False would mean the node was already fully visible.

device.sendAai({action:"showOnScreen(T:Ace)"})
{retval: true}

swipeAcross (O)

Swipes across the first node from end to end in the given direction. Useful for dismissing a message.

1 example, 1 command

Swipe a message away

swipeacross-ex1

Swipes the "Alarm deleted" message to the right.

device.sendAai({action:"swipeAcross(T:Alarm deleted, right)"})
{retval: true}

until (M)

Waits until a node disappears (gone) or its text or description changes (changed), up to a timeout.

2 examples, 4 commands

Wait until a button is gone

until-ex1

Both forms wait up to 20 seconds for the reset button to disappear.

device.sendAai({query:"R:.btn_img_reset_time", action:"until(gone, 20000)"})
{retval: true}
device.sendAai({action:"until(R:.btn_img_reset_time, gone, 20000)"})
{retval: true}

Wait until a value changes

until-ex2

Waits up to 30 seconds for the description under "Nasdaq" to change, then reads the new value.

device.sendAai({query:"T:Nasdaq*&&OY:1", actions:["getDescription", "until(changed,D,30000)","getDescription"]})
{count: 3, list: [{retval: '10,989.25'},{retval: true},{retval: '10,988.50'}]}
device.sendAai({actions:["until(T:Nasdaq*&&OY:1, changed, D, 30000)","getDescription"]})
{count: 2, list: [{retval: true},{retval: '10,988.50'}]}

Chapter 10 of 10

Action commands

Commands that do not involve nodes: opening apps, sending keys, the clipboard, configuration, and building your own functions.

error

Stops execution. sendAai() returns null, and lastError() holds "User Exception:" followed by your message.

1 example, 2 commands

Stop with your own error message

error-ex1

If the battery percentage read from the system UI is below 10, error stops the call and lastError() shows the message.

device.sendAai({query:"P:com.android.systemui&&R:.battery_percentage_view",     action:`if(getText() < 10,                 error(battery too low),                 echo(battery level is adequate)) `})
null
lastError()
User Exception: battery too low

exec

Runs a shell command on the device with sh and returns its output. Not every command is allowed, and the command must not contain >.

1 example, 4 commands

Run shell commands

exec-ex1

The first command finds the resumed activity. The second is refused with "Permission denied". The third chains several commands and returns their combined output.

device.sendAai({action:"exec(dumpsys activity activities | grep ResumedActivity | tail -1)"})
{retval: '  ResumedActivity: ActivityRecord{57c186 u0 com.veronicaapps.veronica.simplecalculator/.MainActivity t1027}'}
device.sendAai({action:"exec(ls /data/local)"})
null
lastError()
ls: /data/local: Permission denied
device.sendAai({action:"exec((cd /etc;ls|head -5)&&echo ----&&ls |head -5)"})
{retval: 'ADP.xml
ASKSB.xml
ASKSC.xml
ASKSHB.xml
ASKSP.xml
----
acct
apex
audit_filter_table
bin
bootstrap-apex'}

function/funcExists/funcDelete/funcReset/funcList

function creates a new action from a list of actions. %1, %2 and so on stand for its arguments, and the values given at creation are the defaults. funcExists, funcDelete, funcList and funcReset manage the functions.

10 examples, 21 commands

Define a function with default arguments

function-ex1

add clicks two digits with + and = on the calculator and reads the result. %1 and %2 default to 1 and 2.

device.sendAai({action:"function(add(1,2))", add:"click(T:%1);click(T:+);click(T:%2);click(T:=);getText(R:.editText_result)"})
{retval: true}

Call it with arguments

function-ex2

3 and 4 replace %1 and %2. The function returns the value of its last command, getText.

device.sendAai({action:"add(3,4)"})
{retval: '=7'}

Return the result of every command

function-ex3

With getFuncRetval() as the last command, the function returns the output of each command, as it did before version 17.

device.sendAai({action:"function(add(1,2))", add:"click(T:%1);click(T:+);click(T:%2);click(T:=);getText(R:.editText_result);getFuncRetval()"})
{retval: true}
device.sendAai({action:"add(3,4)"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=7'}]}

Call it with no arguments

function-ex4

Both defaults are used, so it clicks 1 + 2 =.

device.sendAai({action:"add"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=3'}]}

Call it with one argument

function-ex5

%1 becomes 9 and %2 keeps its default of 2.

device.sendAai({action:"add(9)"})
{retval: [{retval: true},{retval: true},{retval: true},{retval: true},{retval: '=11'}]}

Check for a function and delete it

function-ex6

After funcDelete, funcExists returns false and calling add is an invalid action.

device.sendAai({action:"funcExists(add)"})
{retval: true}
device.sendAai({action:"funcDelete(add)"})
{retval: true}
device.sendAai({action:"funcExists(add)"})
{retval: false}
device.sendAai({action:"add(9)"})
null
lastError()
Invalid action: add

A function defined as an array

function-ex7

sendText finds the text field, sets the text and clicks the send button two nodes to the right. It can be called with literal text or with a variable.

device.sendAai({action:"function(sendText(Hello))", sendText:[     "newQuery(TP:textInput)",     "setText(%1)", "click(OX:2)", "getFuncRetval" ]})
{retval: true}
device.sendAai({action:"sendText(How are you?)"})
{sendText: [{count: 1},{retval: true},{retval: true}]}
var input = "Good morning"
device.sendAai({action:`sendText(${input})`})
{sendText: [{count: 1},{retval: true},{retval: true}]}

A function that calls another function

function-ex8

findAndSend scrolls to a name, opens the chat, calls sendText and goes back.

device.sendAai({action:"function(findAndSend)", findAndSend:"scrollIntoView(T:%1);click;sendText(%2);sendKey(Back);getFuncRetval"})
{retval: true}
device.sendAai({action:"findAndSend(John Doe, Nice to meet you)"})
{findAndSend: [{retval: true},{retval: true},{sendText: [{count: 1},{retval: true},{retval: true}]},{retval: true}]}

Read a value from another app

function-ex9

getProduction opens mySolarEdge, waits for the screen, reads the current power and goes back twice. getFuncRetval(getText) returns the text.

device.sendAai({actions:"function(getProduction)", getProduction:[     "openApp(mySolarEdge)",     "waitQuery(T:Lifetime,20000)",     "newQuery(R:.pv_power_now)",     "getText",     "sendKey(Back)",     "sendKey(Back)",     "getFuncRetval(getText)"]});
{retval: true}
var info = device.sendAai({action:"getProduction"})
info
{retval: [{retval: '5.89 kW
Solar Power Now'}]}

Wrap it in a JavaScript function

function-ex10

The User Guide defines a JavaScript function getProduction() that calls the action and converts the text to watts. Here it returns 5890.

getProduction()
5890

installApp and uninstallApp

Install an app from an APK file on the Windows PC, or uninstall one by package name. Total Control runs adb to do it.

1 example, 6 commands

Install and uninstall an app

installapp-and-uninstallapp-ex1

A successful install returns the package name, and an uninstall returns true. A missing file or an unknown package ends in a timeout error.

device.sendAai({action:"installApp(/tmp/APK Files/SigmaTestApp.apk)"})
{retval: 'com.sigma_rt.sigmatestapp'}
device.sendAai({action:"uninstallApp(com.sigma_rt.sigmatestapp)"})
{retval: true}
device.sendAai({action:"installApp(/tmp/APK Files/NoSuchFile.apk)"})
null
lastError()
Timeout waiting for any package change
device.sendAai({action:"uninstallApp(com.sigma_rt.notthere)"})
null
lastError()
Timeout waiting for uninstall: com.sigma_rt.notthere

listAndroidSettings

Returns the names of the Android settings screens that openAndroidSetting can open.

1 example, 2 commands

List the available settings

listandroidsettings-ex1

The list has 70 names.

device.sendAai({action:"listAndroidSettings"})
{list: ['SETTINGS','ACCESSIBILITY_SETTINGS','ACTION_CONDITION_PROVIDER_SETTINGS', ..., 'ZEN_MODE_PRIORITY_SETTINGS']}
device.sendAai({action:"listAndroidSettings"}).list.length
70

openApp/restartApp /closeApp

openApp starts an app or brings it to the front, closeApp force-closes it, and restartApp does both. The app is named by its package name or its display name.

2 examples, 4 commands

Open and restart an app

openapp-ex1

With time measurement on, the first open takes about 3 seconds, opening it again when it is already in memory takes about 0.7 seconds, and a restart takes about 2.9 seconds.

device.sendAai({action:"openApp(Skype)"})
{retval: true, timeFindNode: 3040}
device.sendAai({action:"openApp(Skype)"})
{retval: true, timeFindNode: 672}
device.sendAai({action:"restartApp(Skype)"})
{retval: true, timeFindNode: 2859}

Wait for a slow app

openapp-ex2

A query and a timeout make openApp wait until "Lifetime" appears, here after about 13 seconds.

device.sendAai({action:"openApp(mySolarEdge,T:Lifetime,15000)"})
{retval: true, timeFindNode: 13098}

openAndroidSetting

Opens an Android settings screen by name. The name is converted to upper case, spaces become _, and _SETTINGS is added if it is missing.

1 example, 1 command

Open the Apps settings

openandroidsetting-ex1

"manage applications" is converted to MANAGE_APPLICATIONS_SETTINGS.

device.sendAai({action:"openAndroidSetting(manage applications)"})
{retval: true}

return

Inside a function, return stops the function and returns a value. Outside a function it behaves like echo.

3 examples, 4 commands

Return a value or null

return-ex1

If an editable field exists, its text is returned; otherwise null.

device.sendAai({action:"if(exists(+BP:editable), return(getText), return(null))"})
{retval: 'Hello'}

return outside a function

return-ex2

It does not stop the main action: echo(3) still runs, and return only adds true to the results.

device.sendAai({action:"echo(1);echo(2);return;echo(3)"})
{count: 4, list: [{retval: 1},{retval: 2},{retval: true},{retval: 3}]}

Stop in the middle of a function

return-ex3

The first return ends the function after two echo commands, so echo(3) never runs.

device.sendAai({action:"function(testecho)", testecho:"echo(1);echo(2);return(getFuncRetval());echo(3);return(getFuncRetval())"})
{retval: true}
device.sendAai({action:"testecho"})
{retval: [{retval: 1},{retval: 2}]}

sendKey

Sends a key to the screen: a key code with an optional meta state, or a shortcut name such as back or home.

1 example, 1 command

Type a capital A

sendkey-ex1

The key code for A is sent with the Shift meta state.

device.sendAai({action:aaix("sendKey", tcConst.keyCodes.A, tcConst.keyCodes.META_SHIFT_ON)})
{retval: true}

setClipData/getClipData/getClipText/clearClipData/sendClipData

Get, set, clear and paste the Android clipboard. The data can be text, HTML, a URI or an intent. A type ending in Send also pastes the data.

4 examples, 9 commands

Set HTML and paste it

setclipdata-ex1

htmlSend puts both the plain text and the HTML on the clipboard and pastes them into the text field.

device.sendAai({action:"setClipData(htmlSend, 'Line 1\nLine 2\nLine 3', 'Line 1<br><b>Line 2</b><br>Line 3')"})
{retval: true}

Read formatted text from the clipboard

setclipdata-ex2

After this text is copied on the device, getClipData returns the HTML in retval and the plain text in text.

The formatted text that was copied.
device.sendAai({action:"getClipData"})
{retval: '<p><b>Copying formatted text between Android and Windows.</b></p><p>&#8226;&#9;Node(<i>getTextFormats</i>) or clipboard(<i>getClipData</i>)<br>
&#8226;&#9;plain text or formatted HTML<br>
&#8226;&#9;Copy from here, paste on box below</p>', text: 'Copying formatted text between Android and Windows.
•Node(getTextFormats) or clipboard(getClipData)
•plain text or formatted HTML
•Copy from here, paste on box below', type: 'html'}

URI, text and intent

setclipdata-ex3

getClipData returns the data with its type. getClipText returns only the text.

device.sendAai({action:"setClipData(uri, http://www.sigma-rt.com)"})
{retval: true}
device.sendAai({action:"setClipData(uri, http://www.sigma-rt.com);getClipData"});
{count: 2, list: [{retval: true},{retval: 'http://www.sigma-rt.com', type: 'uri'}]}
device.sendAai({action:"setClipData(This is a test);getClipData"});
{count: 2, list: [{retval: true},{retval: 'This is a test', type: 'text'}]}
device.sendAai({action:"setClipData(This is another test);getClipText"});
{count: 2, list: [{retval: true},{retval: 'This is another test'}]}
device.sendAai({action:"setClipData(intent, 'android.intent.action.VIEW|https://www.sigma-rt.com/');getClipData"});
{count: 2, list: [{retval: true},{retval: 'Intent { act=android.intent.action.VIEW dat=android.intent.action.VIEW|https://www.sigma-rt.com/... }', type: 'intent'}]}

Long HTML with buildAction

setclipdata-ex4

buildAction quotes the text and the HTML safely before the action is sent.

action = buildAction("setClipData", "htmlSend", text, html)
device.sendAai({action:action})
{retval: true}

setConfig, getConfig, unsetConfig

setConfig changes a FindNode setting by name, getConfig reads it, and unsetConfig restores the original value.

1 example, 5 commands

Change the default template and restore it

setconfig-ex1

With the default template set to reduced, getCount drops from 58 to 46. After unsetConfig it is 58 again.

device.sendAai({action:"getCount"})
{retval: 58}
device.sendAai({action:"setConfig(selector:defaultTemplate, reduced)"})
{retval: true}
device.sendAai({action:"getCount"})
{retval: 46}
device.sendAai({action:"unsetConfig(selector:defaultTemplate)"})
{retval: true}
device.sendAai({action:"getCount"})
{retval: 58}

version/getVersion

Returns the FindNode API version number.

1 example, 1 command

Get the version

version-ex1

Both names return the same number, 21.

device.sendAai({action:"version;getVersion"})
{count: 2, list: [{retval: 21},{retval: 21}]}

TCHelp