FindNode 用户指南 · 第 21 版 · 2026 年 5 月 28 日 · 完整参考文档

简介

FindNode 使用简洁的查询语言查找 Android 屏幕上的界面元素并执行操作,因此脚本无需依赖屏幕坐标。用 devices.click("OK") 替代 devices.click(100, 200):FindNode 在运行时定位文字为“OK”的节点,同一脚本即可在不同分辨率和屏幕尺寸的手机上运行。

本页汇集了《FindNode 用户指南》第 21 版中的 204 个终端示例,并为每个示例提供简要说明。完整参考内容请查看用户指南。

如何阅读示例

  • 以 >> 开头的行是在终端中输入的命令,下方各行是输出结果。
  • 单台设备通过 device.sendAai({query:"…", action:"…"}) 发送命令,多台设备通过 devices.sendAai(…) 发送。
  • 查询最多包含三部分,依次执行:模板(TP)、基本查询(BQ)和扩展查询(EQ)。找到的节点保存在匹配列表(ML)中,操作作用于该列表。
  • 调用成功时返回 JSON,结果通常位于 retval 字段中。出错时返回 null,可通过 lastError() 获取错误信息。
  • “复制”按钮仅复制命令,不包含 >> 提示符。点击截图可放大查看。

第 1 章,共 10 章

快速入门

了解如何向 FindNode 发送命令,以及查询与操作的组合形式。

sendAai

所有 FindNode 命令都通过 sendAai() 发送,可对单台设备(device)或多台设备(devices)调用。

1 个示例,2 条命令

在一次调用中发送多条命令

sendaai-ex1

可在一个字符串中用 ; 分隔命令,也可通过 actions 传入命令数组。两种方式都会为每条命令返回一个结果。

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}]}

查询与操作

查询用于查找屏幕上的节点,操作则作用于找到的节点。

1 个示例,1 条命令

读取 Android 版本

query-actions-ex1

TP:basic 是模板,T:Android version 是查找标签的基本查询,OY:1 是向下偏移一个节点以获取对应值的扩展查询。

“关于手机”界面。对应值位于标签下方。
device.sendAai({query:"TP:basic&&T:Android version&&OY:1", action:"getText"})
{retval: '14'}

第 2 章,共 10 章

查询

查询是由 && 连接的一组 key:value 键值对,节点必须满足全部条件才能匹配。查询分为三部分:模板(TP)生成初始节点列表,基本查询(BQ)检查各节点自身的属性,扩展查询(EQ)对多个节点进行操作。

概述

从同一个存储分析界面读取数值的四种方式。

4 个示例,4 条命令

读取标签旁边的值

query-ex1

T:Main storage 查找标签,OX:1 向右偏移一个节点,获取剩余空间大小。

这四个示例使用的存储分析界面。
device.sendAai({query:"T:Main storage&&OX:1", action:"getText"})
{retval: '402 GB free'}

使用可选查询实现相同操作

query-ex2

查询也可以写在操作内部,这称为可选查询(OQ)。

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

点击返回箭头

query-ex3

OX:-1 从标题向左偏移一个节点,定位左箭头,再由 click 点击它。

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

组合查询的三个部分

query-ex4

解析器将查询拆分为模板(TP:all)、基本查询(T:Main storage)和扩展查询(VG:2&&XT:Audio&&OX:1),并按此顺序执行,获取音频占用空间。

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

匹配列表(ML)

查询匹配到的节点保存在匹配列表(ML)中。从第 16 版开始,搜索采用延迟执行机制:只有操作需要节点时才执行搜索。

4 个示例,5 条命令

未指定查询时使用默认模板

matching-list-ex1

未指定查询时,搜索使用默认模板 TP:more,因此两条命令返回相同的节点 ID。

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

在 getIds 需要节点时执行搜索

matching-list-ex2

使用延迟搜索机制(第 16 版)时,搜索在调用 getIds 的时刻执行。

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

不需要节点的操作不会触发搜索

matching-list-ex3

openApp 不使用 ML,因此不会执行搜索。

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

将两次搜索减少为一次

matching-list-ex4

第 16 版之前会先执行默认的 TP:more 搜索,再执行 TP:reduced。现在只执行 TP:reduced 搜索。

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

“elements”属性

elements 接收节点 ID 列表,将这些节点直接放入 ML,跳过查询。这适合复用此前保存的节点 ID。

1 个示例,2 条命令

保存节点 ID 并逐个点击

elements-property-ex1

第一条命令保存数字按钮 1 到 9 的 ID。第二条命令通过 elements 传入这些 ID,用 forEach(nsClick) 逐个点击,然后读取结果框。

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

第 3 章,共 10 章

模板详解

每个查询都需要一个模板(TP)。模板生成初始节点列表,即匹配列表(ML),供后续查询和操作使用。每个查询只能使用一个模板;未指定时使用默认模板 TP:more。

示例:模板限制查询范围

查询只能访问模板返回的节点。偏移和其他操作都限定在该列表内。

2 个示例,3 条命令

偏移到模板范围外的节点会失败

template-limits-the-query-scope-ex1

TP:findText,/^[0-9]$/ 仅返回数字按键。从“3”执行 OX:1 会指向“%”,而该节点不在列表中,因此返回 null,lastError() 给出原因。

UI Explorer 中使用 TP:findText,/^[0-9]$/。只有数字按键被框选。
device.sendAai({query:"TP:findText,/^[0-9]$/&&T:3&&OX:1", action:"getText"})
null
lastError()
Node not found on offset

使用默认模板时相同偏移可成功

template-limits-the-query-scope-ex2

未指定模板,因此使用默认的 TP:more。其中包含“%”按键,从“3”执行相同偏移即可返回该节点。

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

TP:all, more, basic, reduced

这四种模板都会遍历屏幕上的所有节点,区别在于保留哪些节点。all 保留全部节点,包括根节点;more 移除布局节点,是默认模板;basic 仅保留没有子节点的节点;reduced 保留 FindNode 判断为最有意义的节点。

2 个示例,5 条命令

统计各模板返回的节点数

tp-all-ex1

getCount 统计当前 ML 的节点数:默认 TP:more 返回 237 个。+TP:all 替换 ML,因此第三次统计也为 242。最后三次分别统计 more、basic 和 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}]}

精简节点的三种方式

tp-all-ex2

TP:reduced(模板)、RN(查询过滤器)和 reduceNodes(操作)在不同阶段执行类似的节点精简。该界面有 59 个节点,三种方式都将列表精简为 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]]

返回文字(anyText)或描述(anyDescription)非空的节点。可指定最小和最大长度(均包含端点)来缩小结果范围,长度不能为零。

2 个示例,2 条命令

获取屏幕上的全部文字

tp-anytext-ex1

TP:anyText 返回所有包含文字的节点,getText 读取其文字。在计算器中,这会列出标题、功能键、所有数字和运算符。

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

仅保留单字符文字

tp-anytext-ex2

将最小和最大长度都设为 1,仅保留一个字符的文字,因此“Calculator”“AC”和“DEL”会被排除。

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

TP:findText,<text>

返回文字或描述与指定文字匹配的节点。文字参数必填,支持与基本查询中 T:<text> 相同的匹配形式。

1 个示例,3 条命令

使用正则表达式或通配符查找文字

tp-findtext-ex1

正则表达式匹配计算器的数字按键,结果按从上到下的顺序返回。第二条命令匹配一到四个大写字母,通配符 *T 返回以 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>]

返回可编辑节点(文本框),也可仅返回提示文字匹配的节点。提示文字需要 Android 8.0 或更高版本。BP:editable 可获得相同结果。

1 个示例,3 条命令

列出文本框并按提示文字填写

tp-textinput-ex1

第一条命令读取全部五个文本框的文字。第二条读取提示文字,该功能从 API 26 开始支持。第三条选中提示文字为“Email”的文本框并输入地址。

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>

将可滚动区域上方或下方的固定元素按行分组,并返回其中一行。行号从 1 开始,负数表示从末尾计数,-1 为最后一行。此功能尽力识别,主要适用于包含可滚动容器的应用。

3 个示例,4 条命令

获取顶部第一行

tp-line-ex1

TP:line,top,1 返回顶部区域第一行的节点。本例中该行有六个节点。

顶部区域的第一行。
device.sendAai({query:"TP:line,top,1"})
{count: 6, retval: ['1902b0','191936','192fbc','1cf0bc','19373e','194a03']}

点击该行的第一项

tp-line-ex2

IX:0 选取该行的第一个节点,即左箭头。点击后返回上一页。

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

在底部行输入文字

tp-line-ex3

TP:line,bottom,-1 返回底部区域的最后一行,其中有五个节点。第二条命令选取该行的可编辑节点,将文字设为 Hello。

底部区域的最后一行。
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>]

第 20 版新增。返回应用底部导航栏的图标和标签,先返回图标,再返回标签。添加 icons 或 labels 可仅获取其中一种。BP:selected 可在多数应用中查找已选中的标签。

3 个示例,11 条命令

读取并点击导航栏

tp-appnavbar-ex1

导航栏有四个标签,因此 TP:appNavBar 返回八个节点:四个图标和四个标签。可通过标签文字或使用 IX:3 点击图标,BP:selected 用于查找已选中的标签。

包含四个标签的导航栏。
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'}

大图标同时出现在两个区域

tp-appnavbar-ex2

“+”图标跨越图标行和标签行,因此其节点(21d62)出现两次。要仅获取实际标签,可用 T:/./ 或 filter(node.text) 保留包含文字的节点。

“+”图标跨越图标和标签的整个高度。
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'}

无法分离的图标与标签

tp-appnavbar-ex3

此应用中每个标签由一个同时包含图标和文字的节点组成,因此导航栏返回三个节点。仍可用 IX: 按位置点击标签,但不能用 T: 按文字点击。

UI Explorer 框选的三个标签,每个标签对应一个节点。
device.sendAai({query:"TP:appNavBar", action:"click(IX:2)"});
{retval: true}

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

返回一个可滚动区域中当前可见的项目(叶子节点)。可按位置选择区域(从上到下、从 0 开始计数),也可用查询选择。相比之下,BP:scrollable 会匹配屏幕上的所有可滚动区域。

6 个示例,8 条命令

不带参数运行 TP:scrollable

tp-scrollable-ex1

未指定位置或查询时,使用默认位置 0。本例返回五个节点 ID。

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

使用 BP:scrollable 列出所有可滚动区域

tp-scrollable-ex2

BP:scrollable 匹配屏幕上的所有可滚动节点。getNodes(B) 返回每个节点的边界和 ID。

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

读取设置界面中的标题

tp-scrollable-ex3

一次调用执行三个操作:打开 Android 设置,在可滚动区域中查询标题(android:id/title),并读取文字。每个操作对应一个结果项。

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

选择 Gmail 导航抽屉

tp-scrollable-ex4

抽屉作为弹出窗口打开,屏幕中有多个可滚动区域。可按位置(2)或通过匹配区域内元素的查询(T:Sent)选中它。两种方式都返回相同的 39 个节点。

菜单图标(三条横线)用于打开导航抽屉。
在 UI Explorer 中使用 TP:more 查看已打开的抽屉。
使用 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']}

在抽屉内搜索

tp-scrollable-ex5

将模板限定为抽屉后,查询找到“Sent”,并读取其右侧节点,即数量 505。

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

不指定模板时相同搜索会失败

tp-scrollable-ex6

不使用 TP:scrollable 时,查询范围不限于弹出窗口,无法找到从“Sent”偏移的节点。

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

第 4 章,共 10 章

基本查询详解

基本查询(BQ)逐个检查模板返回的节点,根据各节点自身的属性排除不匹配的节点。

P:<package name>

选择要查询的应用。未指定 P 时使用当前活动应用。可用它访问屏幕上的其他应用,例如系统界面。

2 个示例,4 条命令

读取系统状态栏时间

p-ex1

读取 com.android.systemui 中时钟(R:.clock)的三种方式。可选查询不支持 P,因此第三种方式先通过 newQuery 选择应用。

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’}]}

点击主屏幕按钮

p-ex2

主屏幕按钮属于系统界面,因此查询选择该包并点击 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 匹配文字,D 匹配描述,TD 匹配两者之一。T0 相当于带 BP:none 的 T,T1 相当于带 BP:!editable 的 T,TN 在匹配前对连字符和空格进行规范化。

1 个示例,3 条命令

匹配使用不同连字符的文字

t-ex1

屏幕文字使用另一种 Unicode 连字符,因此 T:*Wi-Fi* 无法匹配。TN 将这些连字符和空格转换为键盘字符后,可找到两项。

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

边界匹配(BI)

BI 按屏幕位置返回节点:包含指定点、与水平或垂直线相交,或位于矩形内的节点。坐标无法直接跨设备复用,因此主要用于调试。

1 个示例,2 条命令

通过直线或矩形查找节点

bound-in-ex1

BI:[-1,645] 返回与 y = 645 的水平线相交的节点,IX:-1 选取最后一个并点击。BI:[0, 0, 1000, 1000] 返回指定矩形内的节点。

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>

根据布尔属性匹配节点,例如 clickable、editable 或 checked。多个属性用逗号分隔,在某个属性前加 ! 表示匹配 false。

5 个示例,11 条命令

匹配一个或多个属性

bp-ex1

第一个查询保留可点击的文本框。后两个查询还要求 enabled;属性列表外的引号可省略。

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

显示节点的全部布尔属性

bp-ex2

getNodes(BP) 返回匹配节点的所有布尔属性及其值。

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

匹配值为 false 的属性

bp-ex3

BP:!enabled 匹配处于禁用状态、显示为灰色的下一周期箭头。遍历周期的循环可据此停止。

没有后续周期时,右侧箭头变为灰色。
device.sendAai({query:"R:.btn_next_period&&BP:!enabled", action:"getCount"})
{count: 1}

相反的查询没有匹配结果

bp-ex4

箭头处于禁用状态,因此 BP:enabled 返回 null,lastError() 报告无匹配。最后一条命令使用 TX 读取与“Day”同一行的标签。

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

可点击但不可勾选

bp-ex5

可勾选节点通常也可点击。26 个可点击节点中有 10 个可勾选,因此 BP:clickable,!checkable 剩余 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}

基本查询示例

更多基本查询示例,以及无匹配时的行为。

3 个示例,10 条命令

默认查询与节点计数

basic-query-examples-ex1

空调用相当于使用 getIds 的 TP:more。T:/./、D:/./ 和 TD:/./ 分别统计包含文字、描述或两者之一的节点。

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}

组合多个查询键

basic-query-examples-ex2

在计算器中,这会匹配文字为单个字符(T:/^.$/)、类型为按钮(C:.Button)且资源 ID 匹配 .button_* 的节点。

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

错误结果的形式

basic-query-examples-ex3

无匹配的查询返回 null,lastError() 显示错误信息。使用未知键(如 X)会产生另一种错误。

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"

第 5 章,共 10 章

扩展查询详解

扩展查询(EQ)键对多个节点执行偏移、分组、交集和排序等操作。按从左到右的顺序执行,同一键可重复使用。

索引(IX)M O

IX:<number> 按位置从 ML 中选取一个节点,位置从 0 开始。负数表示从末尾计数。

1 个示例,3 条命令

按位置选取节点

index-ex1

ML 中有九个节点。IX:1 返回第二个,IX:-2 返回倒数第二个。

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

偏移(OX 与 OY)O O

OX 和 OY 从参考节点移动到相邻节点。OX 向右(正数)或向左(负数),OY 向下(正数)或向上(负数)。

4 个示例,7 条命令

读取标签右侧的值

offset-ex1

OX:1 从标签偏移到其右侧的值。

“关于手机”界面:对应值位于标签右侧。
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'}

读取标签下方的值

offset-ex2

OY:1 从标签向下偏移一个节点。

本例中对应值位于标签下方。
device.sendAai({query:"T:Android version&&OY:1", action:"getText"})
{retval: '12'}

在计算器按键之间移动

offset-ex3

从按键 5 开始,OX:1 移动到右侧的 6,OY:-1 移动到上方的 8。

UI Explorer 中的计算器按键。
device.sendAai({query:"T:5&&OX:1", action:"getText"})
{retval: '6'}
device.sendAai({query:"T:5&&OY:-1", action:"getText"})
{retval: '8'}

OX 和 OY 的顺序会影响结果

offset-ex4

从 0 开始,先向上再向右移动两格,会到达 ×;先向右移动两格再向上,会到达 −。

// 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: '−'}

视图组(VG)O M

VG 从节点向上找到包含它的组,将整个组放入 ML,组节点排在首位。VG:<level> 可向上查找更多层级,默认层级为 1。

7 个示例,7 条命令

按钮所在的第一级组

viewgroup-ex1

从“Start Charging”执行 VG 仅返回两个节点,第一级组不包含充电电流。

第一级组(绿色)仅包含按钮。
device.sendAai({query:"T:Start Charging&&VG"})
{count: 2, retval: ['53b16','53ed7']}

滚动到选项并打开开关

viewgroup-ex2

scrollIntoView 将选项标题滚动到屏幕内,setChecked 再从标题移动到开关(OY:1&&OX:1)并打开它。

开发者选项中的该选项。
device.sendAai({action:"scrollIntoView(T:Disable adb auth*);setChecked(OY:1&&OX:1, true)"})
{count: 2, list: [{retval: true},{retval: true}]}

使用 VG 将整个选项滚动到屏幕内

viewgroup-ex3

标题若位于屏幕最后一行,选项的其他部分可能仍在屏幕外。添加 VG 可将整个选项滚动到屏幕内,再用 BP:checkable 查找开关。

使用 VG 后,组包含标题、说明和开关。
device.sendAai({action:"scrollIntoView(T:Disable adb auth*&&VG);setChecked(BP:checkable, true)"})
{count: 2, list: [{retval: true},{retval: true}]}

第二级组

viewgroup-ex4

VG:2 返回整个充电组,共 24 个节点。第一个节点包含整个组,适合配合 showOnScreen 使用。

第二级组(绿色)及其中的节点。
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']}

读取组内的充电电流

viewgroup-ex5

在组内,XT:/^[0-9]+ A$/ 查找数字后跟“ A”的文字。

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

将电流降低 1 A

viewgroup-ex6

读取值,点击左侧箭头(OX:-1),刷新节点后再次读取:32 A 变为 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'}]}

将电流降低 5 A

viewgroup-ex7

repeat(5, …) 点击左箭头五次,电流从 32 A 变为 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'}]}

交集(TX 与 TY)O M

TX 和 TY 以第一个节点为参考,返回与其水平(TX)或垂直(TY)相交的所有节点。

2 个示例,4 条命令

同一行或同一列的节点

intersect-ex1

从按键 2 开始,TX 返回与其水平相交的六个节点,TY 返回垂直相交的六个节点。

UI Explorer 中按键 2 所在的行。
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']}

宽参考节点

intersect-ex2

结果框横跨整个屏幕宽度。TX 仅返回结果框本身,TY 返回 23 个节点。

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

精简节点(RN)M M

RN 对 ML 执行节点精简,保留最有意义的节点,优先保留小节点而不是包含它们的容器。

1 个示例,2 条命令

将 242 个节点精简为 55 个

reduce-nodes-ex1

默认查询返回 242 个节点,使用 RN 后减少为 55 个。

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

排序(ST)M M

ST:X、ST:Y 或 ST:YX 分别按左边界、上边界,或先上后左对节点排序。

1 个示例,4 条命令

对计算器按钮排序

sort-ex1

按钮原本就按从上到下的顺序返回,因此 ST:Y 和 ST:YX 不改变顺序。ST:X 按列从左到右排序。

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','-','+','%','=']}

基本查询(BQ)M M

写在 EQ 键后面的基本查询键,仍会作为 BQ 的一部分先执行。要在 EQ 的当前位置执行,可用 BQ:'…' 包裹、加上 X 前缀,或启用 selector:allowBqAfterEq。

2 个示例,4 条命令

匹配组内文字

basic-query-2-ex1

VG 选中 CPU 组后,XT:Cpu Cores 或 BQ:'T:Cpu Cores' 查找组内标签,OX:1 读取对应值。

通过 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'}

允许在扩展查询键之后使用基本查询键

basic-query-2-ex2

将 selector:allowBqAfterEq 设为 true(第 14 版起支持)后,VG 后面的普通 T: 会作为 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'}

赋值(V)M M

V:<value> 设置 ML 中第一个节点的值,因此查询可以在一步内找到并填写文本框。

2 个示例,4 条命令

填写标题并打开全天开关

value-ex1

查询先为 Title 文本框设置文字,再移动到 All day 开关并设为 true。未指定操作时执行默认的 getIds。

操作前:标题为空,全天开关关闭。
操作后:标题已填写,全天开关打开。
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']}]}

值的类型错误

value-ex2

开关需要 true 或 false,因此 V:111 返回 null,lastError() 说明原因。

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

第 6 章,共 10 章

操作

操作可以作用于查询找到的节点,也可独立执行而不使用节点。action 可接收一条命令、由 ; 分隔的多条命令,或命令数组。

概述

调用失败时,sendAai 返回 null,lastError() 保存失败原因。

1 个示例,2 条命令

没有找到匹配节点的操作

actions-ex1

查询没有匹配结果,因此 sendAai 返回 null,lastError() 保存错误信息。

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

单个操作

在 action 中传入一条命令。结果通过 retval 返回,发生错误时返回 null。

1 个示例,2 条命令

未知的操作名称

single-action-ex1

操作名称拼写错误会返回 null,lastError() 指出无效的操作。

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

多个操作

多条命令从左到右执行,结果为列表,每条命令对应一项。任何一条失败,整个调用都会返回 null。

2 个示例,3 条命令

三个操作,三个结果

multiple-actions-ex1

列表按执行顺序包含 FindNode 版本、当前包名和节点数。

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

确定哪个操作失败

multiple-actions-ex2

lastError() 给出失败操作从 0 开始的位置(本例为 1),以及对应错误信息。

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

数据类型

操作参数可以是字符串、整数、双精度数、布尔值和 null。第 17 版新增数组、对象和函数,遵循 JavaScript 约定。

1 个示例,16 条命令

回显各类数据

data-type-ex1

echo 返回传入的参数,用于展示各类型的解析方式。字符串可不加引号,也可使用单引号或双引号。数组和对象可通过 [n]、.key 访问元素,嵌套结构也支持。

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 读取节点属性。在 filter 中会检查 ML 内每个节点,在其他命令中读取第一个节点。

2 个示例,3 条命令

仅保留按钮

node-x-ex1

filter(node.class == '.Button') 移除类名不是 .Button 的节点,ML 从 26 个节点减少为 20 个。

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

按布尔属性过滤

node-x-ex2

计算器中只有按钮可点击,因此 node.isClickable 同样保留 20 个节点。第二条命令回显第一个节点的类名和边界。

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)'}]}

操作与查询中的变量

查询或操作的一部分只有在运行时才能确定时,可在 JavaScript 中通过字符串拼接、模板字符串,或辅助函数 aaix 和 buildAction 构建字符串。

12 个示例,29 条命令

使用字符串拼接构建操作

variables-in-actions-and-query-ex1

将 input 设为“Hello”后,这三个表达式展示不同引号写法生成的字符串。

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

发送拼接后的操作

variables-in-actions-and-query-ex2

构建的 setText 字符串作为第一个操作发送,然后由 click(OX:2) 点击右侧相隔两个位置的节点。

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

使用模板字符串实现相同操作

variables-in-actions-and-query-ex3

反引号中的 ${input} 会插入变量。变量外的引号可确保包含空格的文字作为一个整体处理。

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

将正则表达式保存在变量中并插入查询,返回计算器上的全部数字。

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

${c()} 和 ${d()} 调用函数,返回要匹配的类名和描述。${x} 为 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'}]}

在函数中构建完整操作

variables-in-actions-and-query-ex6

disp() 以字符串形式返回完整的 getNodes 操作,再将它作为操作传入。

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

模板字符串可将函数的操作写成多行。enterText 设置文字、检查文字、点击右侧相隔两个位置的节点,然后发送返回键。

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 根据名称和参数构建操作字符串,主要用于第 14 版之前的版本;现在模板字符串也能完成同样的工作。

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

在调用中使用 aaix

variables-in-actions-and-query-ex9

由 aaix 构建的字符串作为操作传入。

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 处理多行文字

variables-in-actions-and-query-ex10

buildAction 安全地为每个参数添加引号,因此包含换行或 HTML 的文字也能发送,不会破坏命令格式。

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 与 setConfig 配合使用

variables-in-actions-and-query-ex11

构建并发送 setConfig 操作,再通过 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'}

使用 raw() 插入不带引号的参数

variables-in-actions-and-query-ex12

raw() 原样插入文字,使 JSON 以对象而非字符串形式传给 echo。

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]}}

操作参数中的特殊字符

从第 16 版开始,参数内的引号、逗号和分号可通过 \\ 转义。

3 个示例,5 条命令

转义引号、逗号和分号

special-character-in-action-parameters-ex1

转义后的字符作为普通文字保存,可通过 getText 查看。

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;'}]}

echo 返回文字,其中保留转义后的引号、逗号和双引号。

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

可选查询(OQ)

操作可携带自己的查询(OQ)。无前缀时在 ML 内搜索,不改变 ML。+ 执行新查询并建立新 ML;* 在 ML 内搜索,并用结果替换 ML。

4 个示例,9 条命令

三种前缀形式

optional-query-ex1

三种形式都找到“5”。无前缀时 ML 保留七个节点;使用 + 或 * 后,ML 仅保留“5”,可通过第二次 getText 查看。

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

无前缀与 * 的区别

optional-query-ex2

无前缀时,每次 OX:1 都从同一个首节点(7)开始并返回 8。使用 * 时,每次都会替换 ML,因此第二步继续移动到 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'}]}

先执行新查询,再在结果中搜索

optional-query-ex3

+TP:anyText&&T:/[^0-9]/ 建立新的 ML,其中有 11 个非数字文字节点。T:/^.$/ 再从中选取单字符节点。

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

+ 搜索屏幕,* 仅搜索 ML

optional-query-ex4

T:/[3-9]/ 建立的 ML 不包含“1”。+ 可以在屏幕上找到它,* 仅搜索 ML,因此失败。

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

条件表达式(CondExp)

用于 if、assert 和 filter 的条件。条件可以是布尔值、比较表达式,或按类似 JavaScript 的规则判定为真或假的值。

5 个示例,39 条命令

条件可以是字面量或 exists 等函数。为 false 时执行第三个参数,本例中 error 停止执行并给出信息。最后一条命令将 node.editable 作为条件。

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}

哪些值会被视为 true

conditional-expression-ex2

isTrue 函数回显参数的真假判定。0、空字符串、false 和 null 为假;非零数、非空字符串、数组和对象为真。

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 显示比较结果,便于测试。最后三条命令在 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}

比较数组和对象

conditional-expression-ex4

数组的元素及顺序相同时判定为相等。对象的键和值相同时判定为相等,与键的顺序无关。

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}

检查 Android 版本是否为 14 或更高

conditional-expression-ex5

getAndroidVersion 打开设置并读取版本。ensureAndroidVersion 将其与传入参数比较(默认为 14),返回 true 或 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}

第 7 章,共 10 章

获取信息命令

用于读取节点、设备或屏幕上应用信息的命令。

getActivity

返回前台 Activity,格式可直接用于 openActivity。

1 个示例,4 条命令

读取并重新打开当前 Activity

getactivity-ex1

前两条命令显示 Gmail 和 Chrome 的 Activity 名称。后两条将当前 Activity 保存到变量,再通过 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)

以 [left, top, right, bottom] 格式返回 ML 中节点的边界,并附带节点 ID。

1 个示例,1 条命令

获取数字按键的边界

getbounds-ex1

为每个匹配节点返回一个边界数组及其节点 ID。

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 与 getAllBoolProps(O)

getBoolProp 返回节点具有的主要布尔属性:editable、progressible、clickable、checkable、scrollable 或 longClickable。getAllBoolProps 返回节点的全部布尔属性。

1 个示例,2 条命令

对于“Checkbox 2”,getBoolProp 返回 checkable 和 clickable;getAllBoolProps 返回七个属性,包括 checked。

Checkbox 2 已勾选。
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)

类似 getBoolProp,但还会沿父节点向上遍历,直到节点树顶部。

1 个示例,1 条命令

查找父节点上的属性

getboolpropinancestors-ex1

返回每个属性及拥有该属性的节点 ID。

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

getChecked (M)

返回开关、复选框、单选按钮等可勾选节点的 true 或 false 状态。多个节点返回数组。

3 个示例,3 条命令

哪些日期已勾选

getchecked-ex1

D:Sunday&&TX 选择七个日期按钮所在的行,getChecked 返回每个按钮的状态。

星期一至星期五已勾选。
device.sendAai({action:"getChecked(D:Sunday&&TX)"})
{count: 7, retval: [false,true,true,true,true,true,false]}

通过类名查找同一行

getchecked-ex2

该界面中没有其他元素使用类名 .CheckBox,因此可将第一个复选框作为参考节点。

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

按名称列出已勾选的日期

getchecked-ex3

XBP:checked 仅保留该行中已勾选的节点,getDescription 返回它们的名称。

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

getCount (M)

返回 ML 的节点数,或可选查询匹配的节点数。

1 个示例,5 条命令

统计有匹配和无匹配时的节点数

getcount-ex1

前两条命令统计全部节点,以及文字包含数字的节点。无匹配的 query 返回 null 并报错;将相同查询写在 getCount 内则返回 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

返回设备名称。

1 个示例,1 条命令

获取设备名称

getdevicename-ex1

名称通过 retval 返回。

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

getFocus

返回获得焦点的节点,通常为文本框或滑块,并将其设为 ML。

1 个示例,1 条命令

填写获得焦点的文本框及其下方文本框

getfocus-ex1

getFocus 选择获得焦点的文本框,setText 填写它。addQuery(OY:1) 移动到下方文本框,供第二次 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

在函数内部返回此前执行的命令输出:可返回全部输出,也可仅返回参数指定命令的输出。

3 个示例,8 条命令

返回函数的全部输出

getfuncretval-ex1

不带参数的 getFuncRetval() 是 testEcho 的最后一条命令,因此调用函数会返回五条 echo 命令的全部输出。

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]}}]}

返回指定命令的输出

getfuncretval-ex2

函数将参数传给 getFuncRetval,每次调用仅返回指定命令的输出。

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}]}

返回其他界面读取的文字

getfuncretval-ex3

函数打开位置信息界面,读取地址后返回。getFuncRetval(getText) 将地址作为函数结果。

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)

返回 ML 的节点数量和 ID。未指定操作时,这是默认操作。

1 个示例,2 条命令

列出节点 ID

getids-ex1

空调用返回全部 26 个节点的 ID。使用可选查询时,getIds 仅返回四个 .Button 节点。

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

返回设备上安装的全部应用(包括系统应用)的名称、包名和版本。

2 个示例,5 条命令

列出已安装的应用

getinstalledapps-ex1

结果包含 567 个应用,此处仅展示前两个。

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
    ]
}

在 JavaScript 中处理结果

getinstalledapps-ex2

列表是普通数组,其长度与 count 相同,可按索引读取元素。

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 将 lastError() 的信息作为操作结果返回。clearLastError 将其重置为空字符串。

1 个示例,5 条命令

读取并清除最近一次错误

getlasterror-ex1

无效查询会设置错误信息。getLastError 返回相同信息,调用 clearLastError 后变为空字符串。

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)

返回节点信息。可选的字段参数指定返回内容,例如 T 表示文字,R 表示资源 ID,BP 表示布尔属性,也可使用 all。

3 个示例,3 条命令

数字按键的资源 ID 与文字

getnodes-ex1

字段 'R,T' 返回各节点的资源 ID 和文字。由于字段列表包含逗号,需要加引号。

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

滑块的范围信息

getnodes-ex2

RI 返回每个滑块的类型、最小值、最大值和当前值。

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

单个滑块的布尔属性

getnodes-ex3

IX:-1 选择最后一个滑块,BP 返回其全部布尔属性。

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 与 getCurrentPackageName 相同,返回正在运行的应用包名。getOnScreenPackageNames 返回屏幕上所有应用的包名。

1 个示例,2 条命令

获取包名

getpackagename-ex1

第一条命令返回当前应用包名,第二条返回屏幕上三个应用的包名。

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)

返回滑块或类似元素的范围信息,是 getNodes(RI) 的简写。

1 个示例,1 条命令

读取滑块的值

getprogress-ex1

滑块范围为 0 到 100,当前值为 41。

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

getQuery/getUniqQuery (O)

生成匹配指定节点的查询。getUniqQuery 返回仅匹配该节点的查询;getQuery 生成的查询可能匹配多个节点。

1 个示例,1 条命令

为同一行的每个节点生成唯一查询

getquery-ex1

T:Controls&&TX 返回该行的三个节点。对于每个节点,getUniqQuery 构建从“Controls”文字定位到它的查询。

该行包含图标、文字和箭头。
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

读取 Android 系统属性,如 getprop。普通参数会搜索属性名称和值;以 = 开头的参数返回指定键的值。

1 个示例,2 条命令

搜索属性或精确读取属性

getsystemprop-ex1

搜索返回名称中包含 ro.product.model 的两个属性。使用 = 时,仅返回该键的值。

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)

读取节点的文字、描述或提示文字。单个节点返回值,多个节点返回数组。getTextFormats 还会以 HTML 返回格式化文字。

4 个示例,8 条命令

单个或多个节点

gettext-ex1

四个节点返回文字数组,单个节点只返回文字本身。

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

文字变化,提示文字不变

gettext-ex2

填写两个文本框后,getText 返回新内容,密码以掩码显示;getHintText 仍返回原来的提示文字。

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

以 HTML 返回格式化文字

gettext-ex3

对于带颜色的文字,getTextFormats 在 retval 中返回纯文本,在 html 中返回格式信息。

屏幕上的格式化文字。
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'}

获取两个节点的格式化文字

gettext-ex4

匹配两个节点时,结果为数组,每个元素包含 html 和 retval。

两行格式化文字。
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'}]}

第 8 章,共 10 章

查询命令

在操作内部建立或修改匹配列表(ML)的命令。

newQuery/addQuery/waitQuery/refreshQuery exists (M)

newQuery 执行新查询并重建 ML;addQuery 在当前 ML 内搜索;waitQuery 等待查询匹配但不改变 ML;refreshQuery 重新读取屏幕;exists 返回 true 或 false。

3 个示例,6 条命令

等待节点并点击的三种方式

newquery-ex1

每种方式最多等待 10 秒,文字出现后点击。newQuery 和 exists(+…) 还会改变 ML,waitQuery 不会。

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}]}

点击后打开新窗口

newquery-ex2

点击打开新窗口,但 ML 仍保留旧窗口节点,因此 getText 没有匹配结果。

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

使用 refreshQuery 重新读取新窗口

newquery-ex3

点击后调用 refreshQuery 读取新窗口,随后 getText 返回 Android 版本。

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)

TX 和 TY 的操作形式。使用查询返回的节点或 ML 的首节点作为参考,将与它相交的节点放入 ML。

5 个示例,5 条命令

按键所在的行

intersectx-ex1

intersectX(T:3) 找到与按键 3 位于同一行的四个节点。

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

按键所在的列

intersectx-ex2

intersectY 也会返回没有可见文字的节点,例如结果框或视图组,此处显示为 {}。

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

使用 addQuery 仅保留有文字的节点

intersectx-ex3

addQuery(T:/.+/) 保留至少包含一个字符的节点。

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

使用可选查询实现相同操作

intersectx-ex4

直接将文字过滤条件传给 getText。

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

使用 filter 实现相同操作

intersectx-ex5

filter(node.text != null) 移除没有文字的节点。

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)

这些命令保存当前 ML,供以后恢复。使用名称时,通过 save 保存的 ML 可在后续 sendAai() 调用中加载。

4 个示例,13 条命令

保存并复用 ML

push-ex1

将数字按键保存为 calc。后续调用直接加载,并点击 2 和 3,无需重新查询。结果框先显示 12,再显示 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'}]}

加载比搜索更快

push-ex2

使用 execTime:true 时,结果包含 FindNode 的耗时。每次完整查询约需 160 ms,加载已保存的 ML 约需 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}

在其他应用中加载会失败

push-ex3

ML 在 Play 商店中保存,但当前前台是另一个应用,因此 load 返回错误。

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

加载未知名称会失败

push-ex4

没有使用该名称保存过 ML。

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

reduceNodes (M M)

查询 RN 的操作形式。精简当前 ML,并返回剩余节点数。

1 个示例,2 条命令

与 TP:reduced 得到相同的数量

reducenodes-ex1

TP:reduced 返回 54 个节点,对默认 ML 执行 reduceNodes 后也剩余 54 个。

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

viewGroup (O M)

查询 VG 的操作形式。接收可选查询和层级参数,并将 ML 改为该组。

1 个示例,2 条命令

读取 Battery 组内的值

viewgroup-2-ex1

两种形式都会选中“Battery”所在的组,并读取其中包含百分比的文字。

Battery 组(绿色)。
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'}]}

第 9 章,共 10 章

节点操作命令

对节点执行点击、输入、滚动定位、遍历,或等待变化等操作的命令。

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

点击节点。click 等待屏幕开始变化;nsClick 点击后不等待,也支持 x、y 坐标;clickDown 和 clickUp 分别执行按下与松开。

3 个示例,9 条命令

点击节点

click-ex1

两条命令都点击按键 5。nsClick 不等待屏幕变化。

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

点击计算得到的位置

click-ex2

根据按键 4 的边界计算其右侧按键的中心,再由 nsClick(x, y) 点击该位置。

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}

按住点击 3 秒

click-ex3

clickDown 按下,sleep(3000) 等待,clickUp 松开。

// 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 在 retval 中返回参数;log 将参数写入 logcat。参数可以是值、函数调用、比较表达式或算术表达式。

3 个示例,6 条命令

回显不同的值

echo-ex1

一次调用传入六个值,每个值分别通过对应的 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-ex2

listAndroidSettings() 返回列表,[0] 和 [-1] 分别选取首项和末项。

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

算术运算与使用 log 替代 echo

echo-ex3

文本框中的文字是算术表达式,echo 返回计算结果 192807。log 返回 true,并将值写入 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)

遍历 ML 中的每个节点,移除不满足条件的节点。条件通常使用 node.X 编写。

2 个示例,3 条命令

仅保留可编辑节点

filter-ex1

push 保存包含 227 个节点的 ML,filter(node.isEditable) 将其精简为一个供 setText 使用的输入框,pop 恢复全部 227 个节点。第二条命令仅保留有文字的节点。

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','.','±','=']}]}

串联两个过滤器

filter-ex2

node.text 是 node.text != null 的简写,第二个过滤器再仅保留数字。

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)

对 ML 中的每个节点执行一次操作,每个节点返回一个结果。

1 个示例,4 条命令

对每个节点执行操作

foreach-ex1

getText 一次处理四个节点并返回数组;forEach(getText) 执行四次并返回四个结果。最后一条命令对每个日期按钮执行 setChecked(true),true 表示状态已改变。

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>) 执行两个操作之一。assert(<condition>) 返回 true 或抛出 AssertionError。

7 个示例,17 条命令

按需切换显示模式后读取

if-ex1

电池显示为英里数或百分比。若存在英里数文字,函数点击它切换模式,再读取百分比。

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

返回阈值的函数

if-ex2

getLowPctg 返回 20,即需要充电的阈值。

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

比较两个函数结果

if-ex3

电池百分比小于或等于阈值时,needCharge 返回 true;79% 时返回 false。

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

使用 return 实现相同操作

if-ex4

return 可直接返回比较结果。将阈值重新定义为 100 后,needCharge 返回 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}

具有两个参数的函数

if-ex5

add 在计算器上点击两个数字及 +、=,然后读取结果。

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

将计算器结果与 echo 对比

if-ex6

equal 将计算器结果与 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

条件为 false 时抛出 AssertionError;若提供了自定义信息,则包含该信息。条件为 true 时返回 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)

节点信息会被缓存。屏幕变化后,refresh 重新读取节点信息。

1 个示例,1 条命令

读取变化的值

refresh-ex1

读取 CPU 使用率,等待两秒并执行 refresh 后再次读取:29 变为 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)

按指定次数执行操作,每次执行返回一个结果。默认最多 20 次,可通过 setConfig 提高上限。

4 个示例,4 条命令

点击按键五次

repeat-ex1

点击计算器的按键 1 五次。

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

将充电电流降低 1 A

repeat-ex2

点击电流值左侧的箭头。

充电电流及其左右箭头。
device.sendAai({action:"click(T:/[0-9]+ A/&&OX:-1)"})
{retval: true}

降低 5 A

repeat-ex3

重复同一次点击五次。

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

降低电流并检查结果

repeat-ex4

读取值,点击五次,刷新后再次读取:27 A 变为 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 将 ML 中节点的图片保存到设备,并返回文件路径;saveImageSettings 修改格式、文件夹、质量和分辨率;saveImageInfo 返回当前设置。

4 个示例,8 条命令

保存数字按键的图片

saveimage-ex1

第一张图片以 JPEG 格式保存到共享 Pictures 文件夹。修改设置后,同一命令将 PNG 图片保存到 Total Control 的私有文件夹。

保存的图片包含按键 0 到 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'}

仅保存一个节点

saveimage-ex2

IX:0 将图片范围限定为第一个节点,即按键 7。先修改设置,再通过 saveImageInfo 检查。

第一个节点为 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'}

保存导航栏

saveimage-ex3

VG 选择“Calls”所在的组,因此图片包含整个导航栏。

保存的图片。
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'}

保存复选框

saveimage-ex4

查询匹配复选框,图片包含全部匹配项。

保存的图片。
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)

持续滚动,直到找到与查询匹配且完全可见的节点,然后将其放入 ML。方向和最大页数为可选参数。

2 个示例,2 条命令

滚动长列表查找姓名并点击

scrollintoview-ex1

最多滚动 50 页,直到找到“John”,然后点击。

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

滚动到指定值并读取

scrollintoview-ex2

查找数字后跟“miles”的文字,并读取它。

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

openActivity

通过 Android 的 am start 启动 Activity。普通 Activity 名称通过 -n 传入;以 - 开头的参数原样传入。

1 个示例,8 条命令

打开 Activity 与 Intent

openactivity-ex1

前几条命令按名称重新打开当前 Activity。其他命令传入 am start 选项:打开网页、启动 YouTube、打开 Wi-Fi 设置和新建邮件。

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)

设置滑块的值。范围信息来自 getProgress 或 getNodes('RI')。

2 个示例,4 条命令

将亮度设为 50%

setprogress-ex1

读取滑块最大值,并将当前值设为其一半。

显示设置中的亮度滑块。
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}

将所有滑块设为 50%

setprogress-ex2

遍历声音设置中的全部滑块,读取各自最大值并设为一半。

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)

直接向文本框输入文字,无需输入法。默认替换已有文字,参数开头加 + 则追加文字。

7 个示例,15 条命令

读取文本框、按提示填写并追加文字

settext-ex1

通过 getText 和 getHintText 读取文本框,再按提示文字选择。最后一条命令通过开头的 + 追加“ World”。

通过 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'}]}

使用 Enter 发送

settext-ex2

对于按 Enter 发送消息的应用,在文字末尾添加 \n 可触发发送。

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

插入表情符号

settext-ex3

模板字符串根据码点构建表情符号,并插入文字中间。

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

定位 Number 文本框的五种方式

settext-ex4

可通过文字、模板的提示参数、HT、标签,或使用 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}

填写下一个文本框的函数

settext-ex5

setNextText 向下移动一个文本框(*OY:1),并设置其文字。

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

一次调用填写五个文本框

settext-ex6

按位置填写第一个文本框,再通过 setNextText 依次填写下方各文本框。

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}]}

检查结果

settext-ex7

getText 显示五个值,密码以掩码显示。

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

showOnScreen (O)

滚动直到第一个节点在屏幕上完全可见。

1 个示例,1 条命令

使节点完全显示在屏幕内

showonscreen-ex1

由于屏幕发生滚动,返回 true;若返回 false,则表示节点原本已完全可见。

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

swipeAcross (O)

沿指定方向从一端滑动到另一端,横穿第一个节点。适合用于移除消息。

1 个示例,1 条命令

滑动移除消息

swipeacross-ex1

向右滑动“Alarm deleted”消息。

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

until (M)

等待节点消失(gone),或其文字、描述发生变化(changed),直到超时。

2 个示例,4 条命令

等待按钮消失

until-ex1

两种形式都最多等待 20 秒,直到重置按钮消失。

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}

等待值发生变化

until-ex2

最多等待 30 秒,直到“Nasdaq”下方的描述变化,然后读取新值。

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

第 10 章,共 10 章

其他操作命令

不涉及节点的命令:打开应用、发送按键、操作剪贴板、修改配置及构建自定义函数。

error

停止执行。sendAai() 返回 null,lastError() 保存“User Exception:”及自定义信息。

1 个示例,2 条命令

使用自定义错误信息停止执行

error-ex1

从系统界面读取的电池百分比低于 10 时,error 停止调用,lastError() 显示错误信息。

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

通过 sh 在设备上执行 shell 命令并返回输出。并非所有命令都允许执行,且命令不能包含 >。

1 个示例,4 条命令

执行 shell 命令

exec-ex1

第一条命令查找已恢复的 Activity。第二条被拒绝并显示“Permission denied”。第三条串联多条命令,返回组合后的输出。

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 将操作列表定义为新操作。%1、%2 等代表参数,定义时提供的值作为默认值。funcExists、funcDelete、funcList 和 funcReset 用于管理函数。

10 个示例,21 条命令

定义具有默认参数的函数

function-ex1

add 在计算器上点击两个数字及 +、=,然后读取结果。%1 和 %2 默认为 1 和 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}

带参数调用函数

function-ex2

3 和 4 替换 %1 和 %2。函数返回最后一条命令 getText 的值。

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

返回每条命令的结果

function-ex3

将 getFuncRetval() 作为最后一条命令时,函数返回各命令的输出,与第 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'}]}

不带参数调用函数

function-ex4

使用两个默认值,因此点击 1 + 2 =。

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

只传入一个参数调用函数

function-ex5

%1 变为 9,%2 保留默认值 2。

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

检查函数是否存在并删除

function-ex6

执行 funcDelete 后,funcExists 返回 false;再调用 add 会报无效操作。

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

使用数组定义函数

function-ex7

sendText 找到文本框,设置文字,再点击右侧相隔两个节点的发送按钮。可用文字字面量或变量调用。

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}]}

调用其他函数的函数

function-ex8

findAndSend 滚动到指定姓名,打开聊天,调用 sendText 后返回。

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}]}

从其他应用读取值

function-ex9

getProduction 打开 mySolarEdge,等待界面,读取当前功率,再返回两次。getFuncRetval(getText) 返回文字。

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

封装为 JavaScript 函数

function-ex10

用户指南定义了 JavaScript 函数 getProduction(),用于调用该操作并将文字转换为瓦特数。本例返回 5890。

getProduction()
5890

installApp 与 uninstallApp

使用 Windows PC 上的 APK 文件安装应用,或按包名卸载应用。Total Control 通过 adb 执行。

1 个示例,6 条命令

安装与卸载应用

installapp-and-uninstallapp-ex1

安装成功返回包名,卸载成功返回 true。文件不存在或包名未知时,会以超时错误结束。

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

返回 openAndroidSetting 可打开的 Android 设置界面名称。

1 个示例,2 条命令

列出可用设置

listandroidsettings-ex1

列表包含 70 个名称。

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 启动应用或将其切换到前台,closeApp 强制关闭应用,restartApp 先关闭再启动。可通过包名或显示名称指定应用。

2 个示例,4 条命令

打开并重新启动应用

openapp-ex1

启用耗时统计后,首次打开约需 3 秒;应用已在内存中时再次打开约需 0.7 秒;重启约需 2.9 秒。

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}

等待启动较慢的应用

openapp-ex2

查询和超时参数使 openApp 等待“Lifetime”出现,本例约需 13 秒。

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

openAndroidSetting

按名称打开 Android 设置界面。名称会转为大写,空格替换为 _;若缺少 _SETTINGS,会自动补上。

1 个示例,1 条命令

打开应用设置

openandroidsetting-ex1

“manage applications”会转换为 MANAGE_APPLICATIONS_SETTINGS。

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

return

在函数内,return 停止函数并返回值;在函数外,行为类似 echo。

3 个示例,4 条命令

返回值或 null

return-ex1

若存在可编辑文本框,则返回其文字,否则返回 null。

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

函数外的 return

return-ex2

不会停止主操作:echo(3) 仍会执行,return 只向结果中添加 true。

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

在函数执行中途停止

return-ex3

第一个 return 在两条 echo 命令后结束函数,因此 echo(3) 不会执行。

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

向屏幕发送按键:可使用键码和可选的修饰键状态,或 back、home 等快捷键名称。

1 个示例,1 条命令

输入大写 A

sendkey-ex1

发送 A 的键码,并附带 Shift 修饰键状态。

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

setClipData/getClipData/getClipText/clearClipData/sendClipData

获取、设置、清空并粘贴 Android 剪贴板内容。数据可以是文字、HTML、URI 或 Intent。类型以 Send 结尾时,还会粘贴数据。

4 个示例,9 条命令

设置 HTML 并粘贴

setclipdata-ex1

htmlSend 将纯文本和 HTML 都放入剪贴板,并粘贴到文本框。

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

从剪贴板读取格式化文字

setclipdata-ex2

在设备上复制该文字后,getClipData 在 retval 中返回 HTML,在 text 中返回纯文本。

复制的格式化文字。
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、文字与 Intent

setclipdata-ex3

getClipData 返回数据及类型,getClipText 仅返回文字。

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

使用 buildAction 处理长 HTML

setclipdata-ex4

buildAction 在发送操作前安全地为文字和 HTML 添加引号。

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

setConfig, getConfig, unsetConfig

setConfig 按名称修改 FindNode 设置,getConfig 读取设置,unsetConfig 恢复原值。

1 个示例,5 条命令

修改默认模板并恢复

setconfig-ex1

将默认模板设为 reduced 后,getCount 从 58 降为 46。执行 unsetConfig 后恢复为 58。

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

返回 FindNode API 版本号。

1 个示例,1 条命令

获取版本

version-ex1

两个名称都返回同一个版本号:21。

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

TCHelp