From 21c4a38b86e619a11c7f322b137ea9dd33cc7fa5 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Mon, 17 Jul 2017 23:26:15 +0800 Subject: [PATCH] add files and docs of files and images --- app/build.gradle | 4 +- app/src/main/assets/help/catalogue.json | 37 +++- .../help/documentation/图片与图色处理.md | 178 +++++++++++++++- .../help/documentation/如何阅读本文档.md | 30 +++ .../assets/help/documentation/文件读写.md | 196 ++++++++++++++++++ .../assets/help/documentation/自动操作函数.md | 6 +- .../sample/图片与图色处理/颜色获取和检测.js | 8 +- .../sample/文件读写/删除所有空文件夹.js | 20 ++ .../InputEventToSendEventJsConverter.java | 26 +-- .../script_list/MyScriptListFragment.java | 5 +- autojs/src/main/assets/modules/__images__.js | 50 +++-- autojs/src/main/assets/modules/__io__.js | 1 + .../runtime/api/image/ColorDetector.java | 24 ++- .../autojs/runtime/api/image/ColorFinder.java | 1 + .../autojs/runtime/api/image/Images.java | 17 +- .../res/xml/accessibility_service_config.xml | 4 +- .../accessibility/AccessibilityService.java | 34 ++- .../src/main/java/com/stardust/pio/PFile.java | 156 ++++++++++---- 18 files changed, 666 insertions(+), 131 deletions(-) create mode 100644 app/src/main/assets/help/documentation/如何阅读本文档.md create mode 100644 app/src/main/assets/help/documentation/文件读写.md create mode 100644 app/src/main/assets/sample/文件读写/删除所有空文件夹.js diff --git a/app/build.gradle b/app/build.gradle index e00b7437..20d2ee55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "com.stardust.scriptdroid" minSdkVersion 17 targetSdkVersion 23 - versionCode 148 - versionName "2.0.14 Alpha" + versionCode 150 + versionName "2.0.14 Alpha3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true ndk { diff --git a/app/src/main/assets/help/catalogue.json b/app/src/main/assets/help/catalogue.json index 1f6fe58d..0789d4dc 100644 --- a/app/src/main/assets/help/catalogue.json +++ b/app/src/main/assets/help/catalogue.json @@ -5,6 +5,11 @@ "type": "markdown", "path":"documentation" }, + { + "title": "如何阅读本文档", + "type": "markdown", + "path":"documentation" + }, { "title": "ECMAScript教程", "type": "catalogue" @@ -24,16 +29,6 @@ "type": "markdown", "path":"documentation" }, - { - "title": "控制台与输出", - "type": "markdown", - "path":"documentation" - }, - { - "title": "UI(用户界面)", - "type": "markdown", - "path":"documentation" - }, { "title": "选择器", "type": "markdown", @@ -45,7 +40,12 @@ "path": "documentation" }, { - "title": "模块与第三方jar", + "title": "图片与图色处理", + "type": "markdown", + "path":"documentation" + }, + { + "title": "控制台与输出", "type": "markdown", "path":"documentation" }, @@ -59,6 +59,21 @@ "type": "markdown", "path":"documentation" }, + { + "title": "UI(用户界面)", + "type": "markdown", + "path":"documentation" + }, + { + "title": "文件读写", + "type": "markdown", + "path":"documentation" + }, + { + "title": "模块与第三方jar", + "type": "markdown", + "path":"documentation" + }, { "title": "调用Java API", "type": "markdown", diff --git a/app/src/main/assets/help/documentation/图片与图色处理.md b/app/src/main/assets/help/documentation/图片与图色处理.md index d2d88e3b..e370f366 100644 --- a/app/src/main/assets/help/documentation/图片与图色处理.md +++ b/app/src/main/assets/help/documentation/图片与图色处理.md @@ -1,5 +1,179 @@ +目录: +* [截图](#截图) +* [images](#images) +* [colors](#colors) +* [Image](#Image) +* [Point](#Point) + +# 截图 + +截图有关的函数需要安卓5.0以上才支持。 ### requestScreenCapture(\[width, height\]) +* width \ 截图宽度 +* height \ 截图高度 -参数width和height用于指定截图的分辨率,默认为屏幕宽高。 +向系统申请屏幕截图权限,返回是否请求成功。 -未完待续。 \ No newline at end of file +第一次使用该函数会弹出截图权限请求,建议选择“总是允许”。 + +这个函数只是申请截图权限,并不会真正执行截图,真正的截图函数是[captureScreen](#captureScreen())。 + +不指定参数时默认为屏幕宽高。指定参数时,并不会严格以width和height为截图宽度和高度,而是以和width, height接近的最适合的宽高为截图宽高。例如在1920\*1080屏幕中requestScreenCapture(600, 1000)请求的截图的高度一般是540\*960。 + +如果在第一次权限请求时选择"总是允许", 之后的脚本执行该函数通常耗时200毫秒以内(测试机型:小米6)。 + +建议在本软件界面运行该函数,在其他软件界面运行时容易出现一闪而过的黑屏。 + +### captureScreen() + +截取当前屏幕并返回一个[Image](#Image)对象。该图片的宽高取决于requestScreenCapture所指定的宽高。 + +没有截图权限时执行该函数会抛出SecurityException。 + +该函数耗时一毫秒以内(测试机型:小米6)。因此不必担心截图的速度。图色处理的瓶颈通常在找色部分。 + +### captureScreen(path) +* path \ 截图保存路径 + +截取当前屏幕并以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。 + +# images + +images是图片与图色处理的工具。包括获取图片某点颜色,保存图片,找色等。 + +### images.pixel(image, x, y) +* image \ 图片 +* x \ 要获取的像素的横坐标。坐标系以图片左上角为原点。 +* y \ 要获取的像素的纵坐标。 + +返回图片image在点(x, y)处的像素的ARGB值。 +该值的格式为0xAARRGGBB。也就是,如果`var argb = images.pixel(image, x, y)`,那么可以通过`(argb & 0xFF000000) >> 24`获取透明度,通过`(argb & 0xFF0000) >> 16`获取R值,通过`(argb & 0xFF000000) >> 24`获取G值等,或者通过`images.alpha(argb)`, `images.red(argb)`等获取。 + +### images.saveImage(image, path) + +* image \ 图片 +* path \ 路径 + +把图片image以PNG格式保存到path中。如果文件不存在会被创建;文件存在会被覆盖。 + +### images.findColor(image, color, options) +* image \ 图片 +* color \或\ 要寻找的颜色的RGB值。如果是一个整数,则以0xRRGGBB的形式代表RGB值(A通道会被忽略);如果是字符串,则以"#RRGGBB"代表其RGB值。 +* options \ 选项 + +在图片中寻找颜色color。找到时返回找到的点[Point](#Point),找不到时返回null。 +选项包括: +* region \ 找色区域。是一个两个或四个元素的数组。(region\[0\], region\[1\])表示找色区域的左上角;region\[2\]*region\[3\]表示找色区域的宽高。如果只有region只有两个元素,则找色区域为(region\[0\], region\[1\])到屏幕右下角。如果不指定region选项,则找色区域为整张图片。 +* threads \ 指定找色使用的线程数。不能超过16。最适合的线程数取决于设备。默认线程数为2。 +* algorithm \ 指定颜色匹配算法。包括: + * "equal": 相等匹配,只有与给定颜色color完全相等时才匹配。 + * "diff": 差值匹配。与给定颜色的R、G、B差的绝对值之和小于threshold时匹配。 + * "rgb": rgb欧拉距离相似度。与给定颜色color的rgb欧拉距离小于等于threshold时匹配。 + * "rgb+": 加权rgb欧拉距离匹配([LAB Delta E](https://en.wikipedia.org/wiki/Color_difference))。 + * "hs": hs欧拉距离匹配。hs为HSV空间的色调值。 +* threshold \ 找色时颜色相似度的临界值,范围为0~255(越小越相似,0为颜色相等,255为任何颜色都能匹配)。默认为16。threshold和浮点数相似度(0.0~1.0)的换算为 similarity = (255 - threshold) / 255. + +该函数也可以作为全局函数使用。 + + +### images.findColorInRegion(img, color, x, y\[, width, height, threads, algorithm, threshold\]) + +相当于 +``` +images.findColor(img, color, { + region: [x, y, width, height], + algorithm: algorithm, + threshold: threshold, + threads: threads +}); +``` + +该函数也可以作为全局函数使用。 + +### images.findColorEquals(img, color, x, y, width, height, threads) + +相当于 +``` +images.findColor(img, color, { + region: [x, y, width, height], + algorithm: "equal", + threads: threads +}); +``` + +该函数也可以作为全局函数使用。 + + +### images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "rgb"\]) +* image \ 图片 +* color \ 要检测的颜色 +* x \ 要检测的位置横坐标 +* y \ 要检测的位置纵坐标 +* threshold \ 颜色相似度临界值,默认为16 +* algorithm \ 颜色匹配算法,默认为rgb欧式距离 + +返回图片image在位置(x, y)处是否匹配到颜色color。有关threshold和algorithm的信息,参考findColor函数。 + +# colors + +colors是颜色处理的工具对象。包含一些常用方法,除了android.graphics.Color的所有方法外,加上toString方法。 + +### colors.toString(color) +* color \ 整数RGB颜色值 + +返回颜色值的字符串,格式为 #AARRGGBB。 + +### colors.red(color) +* color \ 整数RGB颜色值 + +返回颜色color的R通道的值,范围0~255. + +### colors.green(color) +* color \ 整数RGB颜色值 + +返回颜色color的G通道的值,范围0~255. + +### colors.blue(color) +* color \ 整数RGB颜色值 + +返回颜色color的B通道的值,范围0~255. + +### colors.alpha(color) +* color \ 整数RGB颜色值 + +返回颜色color的Alpha通道的值,范围0~255. + +### colors.rgb(red, green, blue) +* red \ 颜色的R通道的值 +* blue \ 颜色的G通道的值 +* green \ 颜色的B通道的值 + +返回这些颜色通道构成的整数颜色值。Alpha通道将是255(不透明)。 + +### colors.argb(alpha, red, green, blue) +* alpha \ 颜色的Alpha通道的值 +* red \ 颜色的R通道的值 +* green \ 颜色的G通道的值 +* blue \ 颜色的B通道的值 + +返回这些颜色通道构成的整数颜色值。 + +# Image + +[captureScreen](#captureScreen()) 返回的对象。 + +### Image.getWidth() + +返回以像素为单位图片宽度。 + +### Image.getHeight() + +返回以像素为单位的图片高度。 + +# Point + +findColor返回的对象。 + +### Point.x +### Point.y + 坐标。 diff --git a/app/src/main/assets/help/documentation/如何阅读本文档.md b/app/src/main/assets/help/documentation/如何阅读本文档.md new file mode 100644 index 00000000..70b96c0a --- /dev/null +++ b/app/src/main/assets/help/documentation/如何阅读本文档.md @@ -0,0 +1,30 @@ +先看一个例子,下面是《自动操作函数》的章节中input函数的部分说明。 +### input(\[i, \]text) +* i \ 表示要输入的为第i + 1个输入框 +* text \ 要输入的文本 + +input表示函数名,括号内为函数的参数。下面是参数列表,"\"表示该参数类型为数值,"\"表示该参数类型为字符串。 + +例如`input(1, "啦啦啦")`,执行这个语句会在屏幕上的第2个输入框处输入"啦啦啦“。 + +方括号\[ \]表示参数为可选参数。也就是说,可以省略i直接调用input。例如`input("嘿嘿嘿")`,按照文档,这个语句会在屏幕上所有输入框输入"嘿嘿嘿"。 + +调用有可选参数的函数时请**不要**写上方括号。 + +我们再看第二个例子。图片和图色处理中detectsColor函数的部分说明。 + +### images.detectsColor(image, color, x, y\[, threshold = 16, algorithm = "rgb"\]) +* image \ 图片 +* color \ 要检测的颜色 +* x \ 要检测的位置横坐标 +* y \ 要检测的位置纵坐标 +* threshold \ 颜色相似度临界值,默认为16 +* algorithm \ 颜色匹配算法,默认为rgb欧式距离 + +同样地,"\[, threshold = 16, algorithm = "rgb"\]"为可选参数,并且,等于号=后面的值为参数的默认值。也就是如果不指定该参数,则该参数将会为这个值。 + +例如 `images.detectsColor(captureScreen(), "#112233", 100, 200)` 相当于 `images.detectsColor(captureScreen(), "#112233", 100, 200, 16, "rgb")`,而`images.detectsColor(captureScreen(), "#112233", 100, 200, 64)` 相当于`images.detectsColor(captureScreen(), "#112233", 100, 200, 64, "rgb")` 。 + +调用有可选参数及默认值的函数时请**不要**写上方括号和等于号。 + +好了先这样吧 :-\) diff --git a/app/src/main/assets/help/documentation/文件读写.md b/app/src/main/assets/help/documentation/文件读写.md new file mode 100644 index 00000000..052c9177 --- /dev/null +++ b/app/src/main/assets/help/documentation/文件读写.md @@ -0,0 +1,196 @@ + +### open(path\[, mode = "r", encoding = "utf-8", bufferSize = 8192\]) +* path \ 文件路径,例如"/sdcard/1.txt"。 +* mode \ 文件打开模式,包括: + * "r": 只读模式。该模式下只能对文件执行**文本**读取操作。 + * "w": 只写模式。该模式下只能对文件执行**文本**覆盖写入操作。 + * "a": 附加模式。该模式下将会把写入的文本附加到文件末尾。 + 目前暂不支持二进制模式,随机读写模式。 +* encoding \ 字符编码。 +* bufferSize \ 文件读写的缓冲区大小。 + +打开一个文件。根据打开模式返回不同的文件对象。包括: +* "r": 返回一个ReadableTextFile对象。 +* "w", "a": 返回一个WritableTextFile对象。 + +对于"w"模式,如果文件并不存在,则会创建一个,已存在则会清空该文件内容;其他模式文件不存在会抛出FileNotFoundException。 + +# files + +文件处理的工具对象。 + +### files.isFile(path) +* path \ 路径 + +返回路径path是否是文件。 + +### files.isDir(path) +* path \ 路径 + +返回路径path是否是文件夹。 + +### files.isDirEmpty(path) +* path \ 路径 + +返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回false。 + +### files.join(parent, child) +* parent \ 父目录路径 +* child \ 子路径 + +连接两个路径并返回,例如`files.join("/sdcard/", "1.txt")`返回"/sdcard/1.txt"。 + +### files.create(path) +* path \ 路径 + +创建一个文件并返回是否创建成功。 + +### files.createIfNotExists(path) +* path \ 路径 + +创建一个文件并返回是否创建成功。 + +### files.exists(path) +* path \ 路径 + +返回在路径path处的文件是否存在。 + +### files.ensureDir(path) +* path \ 路径 + +确保路径path所在的文件夹存在。 + +例如对于路径"/sdcard/Download/ABC/1.txt",如果/Download/文件夹不存在,则会先创建Download,再创建ABC文件夹。 + +### files.read(path\[, encoding = "utf-8"\]) +* path \ 路径 +* encoding \ 字符编码 + +读取文件path的所有内容并返回。 + +### files.write(path, text\[, encoding = "utf-8"\]) +* path \ 路径 +* text \ 要写入的文本内容 +* encoding \ 字符编码 + +把text写入到文件path中。如果文件存在则覆盖,不存在则创建。 + +### files.copy(fromPath, toPath) +* fromPath \ 要复制的原文件路径 +* toPath \ 复制到的文件路径 + +复制文件。例如`files.copy("/sdcard/1.txt", "/sdcard/Download/1.txt")`。 + +### files.rename(path, newName) +* path \ 要重命名的原文件路径 +* newName \ 要重命名的新文件名 + +重命名文件,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2.txt")`。 + +也可以用作文件移动,例如`files.rename("/sdcard/1.txt", "/sdcard/Download/1.txt")`会把1.txt文件从sd卡根目录移动到Download文件夹。 + +### files.renameWithoutExtension(path, newName) +* path \ 要重命名的原文件路径 +* newName \ 要重命名的新文件名 + +重命名文件,不包含拓展名,并返回是否重命名成功。例如`files.rename("/sdcard/1.txt", "2")`会把1.txt重命名为2.txt。 + +### files.getName(path) +* path \ 路径 + +返回文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1.txt"。 + +### files.getNameWithoutExtension(path) +* path \ 路径 + +返回不含拓展名的文件的文件名。例如`files.getName("/sdcard/1.txt")`返回"1"。 + +### files.getExtension(path) +* path \ 路径 + +返回文件的拓展名。例如`files.getExtension("/sdcard/1.txt")`返回"txt"。 + +### files.remove(path) +* path \ 路径 + +删除文件或**空文件夹**,返回是否删除成功。 + +### files.removeDir(path) +* path \ 路径 +* path \ 路径 + +删除文件夹,如果文件夹不为空,则删除该文件夹的所有内容再删除该文件夹,返回是否全部删除成功。 + +### files.getSdcardPath() + +返回SD卡路径。所谓SD卡,即外部存储器。 + +### files.listDir(path\[, filter\]) +* path \ 路径 +* filter \ 过滤函数。接收一个String参数(文件名),返回一个Boolean值。 + +列出文件夹path下的满足条件的文件和文件夹的名称的数组。如果不加filter参数,则返回所有文件和文件夹。 + +例如,获取sdcard目录下的txt文件为 +``` +var txtFiles = files.listDir("/sdcard/", function(name){ + return name.endsWith(".txt") && files.isFile("/sdcard/" + name); +}); +``` + +# ReadableTextFile + +可读文件对象。 + +### ReadableTextFile.read() + +返回该文件剩余的所有内容的字符串。 + +### ReadableTextFile.read(maxCount) + +* maxCount \ 最大读取的字符数量 + +读取该文件接下来最长为maxCount的字符串并返回。即使文件剩余内容不足maxCount也不会出错。 + +### ReadableTextFile.readline() + +读取一行并返回(不包含换行符)。 + +### ReadableTextFile.readlines() + +读取剩余的所有行,并返回它们按顺序组成的字符串数组。 + +### close() + +关闭该文件。 + +**打开一个文件不再使用时务必关闭** + +# PWritableTextFile + +可写文件对象。 + +### PWritableTextFile.write(text) +* text \ 文本 + +把文本内容text写入到文件中。 + +### PWritableTextFile.writeline(line) +* text \ 文本 + +把文本line写入到文件中并写入一个换行符。 + +### PWritableTextFile.writelines(lines) +* lines \ 字符串数组 + +把很多行写入到文件中.... + +### PWritableTextFile.flush() + +把缓冲区内容输出到文件中。 + +### PWritableTextFile.close() + +关闭文件。同时会被缓冲区内容输出到文件。 + +**打开一个文件写入后,不再使用时务必关闭,否则文件可能会丢失** diff --git a/app/src/main/assets/help/documentation/自动操作函数.md b/app/src/main/assets/help/documentation/自动操作函数.md index 3df99541..9b44ebd8 100644 --- a/app/src/main/assets/help/documentation/自动操作函数.md +++ b/app/src/main/assets/help/documentation/自动操作函数.md @@ -27,6 +27,8 @@ while(!click("扫一扫")); * bottom 要点击的长方形区域下边与屏幕上边的像素距离 * right 要点击的长方形区域右边与屏幕左边的像素距离 +**注意,该函数一般只用于录制的脚本中使用,在自己写的代码中使用该函数一般没有作用。** + 当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text[, i])`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。 至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。 @@ -43,13 +45,13 @@ while(!click("扫一扫")); 参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0);`。 ### scrollDown 下滑。不加参数时与scrollUp类似。 -### input([i, ]text) +### input(\[i, \]text) * i \ 表示要输入的为第i + 1个输入框 * text \ 要输入的文本 返回是否输入成功。当找不到对应的文本框时返回false。 -这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。 不加参数i则会把所有输入框的文本都置为text。例如`input("测试")`。 +这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。 ### back() 模拟按下返回键 diff --git a/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js b/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js index bbaaaa40..f19a9e2c 100644 --- a/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js +++ b/app/src/main/assets/sample/图片与图色处理/颜色获取和检测.js @@ -7,6 +7,10 @@ var y = 180; //获取在点(x, y)处的颜色 var c = images.pixel(captureScreen(), x, y); //显示该颜色 -toast((c >>> 0).toString(16)); +var msg = ""; +msg += "在位置(" + x + ", " + y + ")处的颜色为" + colors.toString(c); +msg += "\nR = " + colors.red(c) + ", G = " + colors.green(c) + ", B = " + colors.blue(c); //检测在点(x, y)处是否有颜色0x73bdb6 (模糊比较) -toast(images.detectsColor(captureScreen(), x, y, 0x73bdb6)) \ No newline at end of file +var isDetected = images.detectsColor(captureScreen(), "#73bdb6", x, y); +msg += "\n该位置是否匹配到颜色#73bdb6: " + isDetected; +alert(msg); \ No newline at end of file diff --git a/app/src/main/assets/sample/文件读写/删除所有空文件夹.js b/app/src/main/assets/sample/文件读写/删除所有空文件夹.js new file mode 100644 index 00000000..8bb9eef7 --- /dev/null +++ b/app/src/main/assets/sample/文件读写/删除所有空文件夹.js @@ -0,0 +1,20 @@ +if(confirm("该操作会删除SD卡目录及其子目录下所有空文件夹,是否继续?")){ + toast("请点击右上角打开日志"); + deleteAllEmptyDirs(files.getSdcardPath()); + toast("全部完成!"); +} + +function deleteAllEmptyDirs(dir){ + var list = files.listDir(dir); + var len = list.length; + if(len == 0){ + log("删除目录 " + dir + " " + (files.remove(dir) ? "成功" : "失败")); + return; + } + for(let i = 0; i < len; i++){ + var child = files.join(dir, list[i]); + if(files.isDir(child)){ + deleteAllEmptyDirs(child); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java index 93b4efa5..c9be24bc 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floatingwindow/menu/record/inputevent/InputEventToSendEventJsConverter.java @@ -1,5 +1,6 @@ package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent; +import android.media.Image; import android.support.annotation.NonNull; import java.util.regex.Matcher; @@ -22,8 +23,8 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { private int mLastTouchY = -1; public InputEventToSendEventJsConverter() { - mCode.append("var ies = new InputEventSender();\n") - .append("ies.setScreenMetrics(").append(getDeviceScreenWidth()).append(", ") + mCode.append("var sh = new Shell(true);\n") + .append("sh.SetScreenMetrics(").append(getDeviceScreenWidth()).append(", ") .append(getDeviceScreenHeight()).append(");\n"); } @@ -33,7 +34,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { if (mLastEventTime == 0) { mLastEventTime = event.time; } else if (event.time - mLastEventTime > 0.03) { - mCode.append("sleep(").append((long) (1000L * (event.time - mLastEventTime))).append(");\n"); + mCode.append("sh.usleep(").append((long) (1000000 * (event.time - mLastEventTime))).append(");\n"); mLastEventTime = event.time; } int device = parseDeviceNumber(event.device); @@ -51,7 +52,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { } } checkLastTouch(); - mCode.append("ies.sendEvent("); + mCode.append("sh.SendEvent("); if (device != mTouchDevice) { mCode.append(device).append(", "); } @@ -62,11 +63,11 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { private void checkLastTouch() { if (mLastTouchX >= 0) { - mCode.append("ies.touchX(").append(mLastTouchX).append(");\n"); + mCode.append("sh.TouchX(").append(mLastTouchX).append(");\n"); mLastTouchX = -1; } if (mLastTouchY >= 0) { - mCode.append("ies.touchY(").append(mLastTouchY).append(");\n"); + mCode.append("sh.TouchY(").append(mLastTouchY).append(");\n"); mLastTouchY = -1; } } @@ -93,7 +94,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { setTouchDevice(device); } if (mLastTouchX >= 0) { - mCode.append("ies.touch(") + mCode.append("sh.Touch(") .append(mLastTouchX).append(", ") .append(value).append(");\n"); mLastTouchX = -1; @@ -103,7 +104,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { } private void setTouchDevice(int i) { - mCode.append("ies.setInputDevice(").append(i).append(");\n"); + mCode.append("sh.SetTouchDevice(").append(i).append(");\n"); mTouchDevice = i; } @@ -119,14 +120,7 @@ public class InputEventToSendEventJsConverter extends InputEventConverter { @Override public void stop() { super.stop(); - mCode.append("ies.exitAndWaitFor();"); + mCode.append("sh.exitAndWaitFor();"); } - private static String hex2dec(String hex) { - try { - return String.valueOf((int) Long.parseLong(hex, 16)); - } catch (NumberFormatException e) { - throw new EventFormatException(e); - } - } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java index aef226f0..e21ad312 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/script_list/MyScriptListFragment.java @@ -15,6 +15,7 @@ import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.app.Fragment; import com.stardust.app.OperationDialogBuilder; +import com.stardust.pio.UncheckedIOException; import com.stardust.scriptdroid.script.ScriptFile; import com.stardust.pio.PFile; import com.stardust.scriptdroid.R; @@ -137,7 +138,9 @@ public class MyScriptListFragment extends Fragment { public void createScriptFile(String path, String script) { if (PFile.createIfNotExists(path)) { if (script != null) { - if (!PFile.write(path, script)) { + try { + PFile.write(path, script); + } catch (UncheckedIOException e) { Snackbar.make(getView(), R.string.text_file_write_fail, Snackbar.LENGTH_LONG).show(); } } diff --git a/autojs/src/main/assets/modules/__images__.js b/autojs/src/main/assets/modules/__images__.js index e21ab0f1..d957b25c 100644 --- a/autojs/src/main/assets/modules/__images__.js +++ b/autojs/src/main/assets/modules/__images__.js @@ -1,6 +1,10 @@ module.exports = function(__runtime__, scope){ var images = {}; + var colors = Object.create(android.graphics.Color); + colors.toString = function(color){ + return '#' + (color >>> 0).toString(16); + } if(android.os.Build.VERSION.SDK_INT < 19){ return images; } @@ -17,25 +21,28 @@ module.exports = function(__runtime__, scope){ images.pixel = rtImages.pixel; - images.detectsColor = rtImages.detectsColor.bind(rtImages); + images.detectsColor = function(img, color, x, y, threshold, algorithm){ + color = parseColor(color); + algorithm = algorithm || "rgb"; + threshold = threshold || 16; + var colorDetector = getColorDetector(color, algorithm, threshold); + var pixel = images.pixel(img, x, y); + return colorDetector.detectsColor(colors.red(pixel), colors.green(pixel), colors.blue(pixel)); + } images.findColor = function(img, color, options){ - if(typeof(color) == 'string'){ - if(color.startsWith('#')){ - color = parseInt('0x' + color.substring(1)); - }else{ - color = parseInt('0x' + color); - } - } + color = parseColor(color); options = options || {}; var region = options.region || []; - x = region[0] || 0; - y = region[1] || 0; - width = region[2] || (img.getWidth() - x); - height = region[3] || (img.getHeight() - y); - threads = options.threads || 2; - if(options.threshold !== 0){ - threshold = options.threshold || 8; + var x = region[0] || 0; + var y = region[1] || 0; + var width = region[2] || (img.getWidth() - x); + var height = region[3] || (img.getHeight() - y); + var threads = options.threads || 2; + if(options.similarity){ + var threshold = parseInt(255 * (1 - options.similarity)); + }else{ + var threshold = options.threshold || 16; } algorithm = options.algorithm || "rgb"; var rect = new android.graphics.Rect(x, y, width + x, height + y); @@ -76,7 +83,20 @@ module.exports = function(__runtime__, scope){ throw new Error("Unknown algorithm: " + algorithm); } + function parseColor(color){ + if(typeof(color) == 'string'){ + if(color.startsWith('#')){ + return parseInt('0x' + color.substring(1)); + }else{ + return parseInt('0x' + color); + } + } + return color; + } + scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findColor', 'findColorInRegion', 'findColorEquals']); + scope.colors = colors; + return images; } \ No newline at end of file diff --git a/autojs/src/main/assets/modules/__io__.js b/autojs/src/main/assets/modules/__io__.js index 2c6e4017..d5cae36e 100644 --- a/autojs/src/main/assets/modules/__io__.js +++ b/autojs/src/main/assets/modules/__io__.js @@ -1,5 +1,6 @@ module.exports = function(__runtime__, scope){ + scope.files = com.stardust.pio.PFile; scope.open = function(path, mode, encoding, bufferSize){ if(arguments.length == 1){ return com.stardust.pio.PFile.open(path); diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java index 4a116eb9..e5a4241d 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java @@ -40,19 +40,17 @@ public interface ColorDetector { class DifferenceDetector extends AbstractColorDetector { - private final int mRThreshold, mGThreshold, mBThreshold; + private final int mThreshold; public DifferenceDetector(int color, int threshold) { super(color); - mRThreshold = Color.red(threshold); - mGThreshold = Color.green(threshold); - mBThreshold = Color.blue(threshold); + mThreshold = threshold * 3; } + @Override public boolean detectsColor(int R, int G, int B) { - return Math.abs(R - mR) <= mRThreshold && Math.abs(G - mG) <= mGThreshold && - Math.abs(B - mB) <= mBThreshold; + return Math.abs(R - mR) + Math.abs(G - mG) + Math.abs(B - mB) <= mThreshold; } } @@ -77,7 +75,7 @@ public interface ColorDetector { public RGBDistanceDetector(int color, int threshold) { super(color); - mThreshold = threshold * threshold; + mThreshold = threshold * threshold * 3; } @Override @@ -100,7 +98,7 @@ public interface ColorDetector { mR = (color & 0xff0000) >> 16; mG = (color & 0x00ff00) >> 8; mB = color & 0xff; - mThreshold = threshold * threshold; + mThreshold = threshold * threshold * 8; } @Override @@ -163,7 +161,11 @@ public interface ColorDetector { long HS = getHS(mR, mG, mB); mH = (int) (HS & 0xffffffffL); mS = (int) ((HS >> 32) & 0xffffffffL); - mThreshold = threshold * threshold; + mThreshold = threshold * 3729600 / 255; + } + + public HSDistanceDetector(int color, float similarity) { + this(color, (int) (1.0f - similarity) * 255); } @Override @@ -191,9 +193,9 @@ public interface ColorDetector { H = 240 + (R - G) / (max - min) * 60; } if (H < 0) H = H + 360; - int S = (max - min) / max; + int S = (max - min) * 100 / max; return H & ((long) S << 32); } } -} +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java index d54fb3b9..09674a81 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java @@ -1,5 +1,6 @@ package com.stardust.autojs.runtime.api.image; +import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.media.Image; diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java index ed716af9..fc6616d4 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java @@ -7,6 +7,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.Point; import android.media.Image; import android.os.Build; import android.support.annotation.RequiresApi; @@ -71,6 +72,9 @@ public class Images { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public Image captureScreen() { mScriptRuntime.requiresApi(21); + if(mScreenCapturer == null){ + throw new SecurityException("No screen capture permission"); + } colorFinder.prestartThreads(); return mScreenCapturer.capture(); } @@ -122,19 +126,6 @@ public class Images { return bitmap.getPixel(x, y); } - public boolean detectsColor(Image image, int x, int y, int color) { - if (image == null) - return false; - int pixel = pixel(image, x, y); - return colorFinder.defaultColorDetector(color).detectsColor(Color.red(pixel), Color.green(pixel), Color.blue(pixel)); - } - - public boolean detectsColor(Image image, int x, int y, int color, int threshold) { - if (image == null) - return false; - int pixel = pixel(image, x, y); - return colorFinder.defaultColorDetector(color, threshold).detectsColor(Color.red(pixel), Color.green(pixel), Color.blue(pixel)); - } public static Bitmap read(String path) { return BitmapFactory.decodeFile(path); diff --git a/autojs/src/main/res/xml/accessibility_service_config.xml b/autojs/src/main/res/xml/accessibility_service_config.xml index 78d50da7..62a664d6 100644 --- a/autojs/src/main/res/xml/accessibility_service_config.xml +++ b/autojs/src/main/res/xml/accessibility_service_config.xml @@ -1,8 +1,8 @@ eventTypes = new HashSet<>(); private volatile AccessibilityNodeInfo mRootInActiveWindow; private Timer mTimer; + private Handler mHandler; public static void addDelegate(int uniquePriority, AccessibilityDelegate delegate) { mDelegates.put(uniquePriority, delegate); @@ -84,11 +87,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi @Override public AccessibilityNodeInfo getRootInActiveWindow() { - try { - return super.getRootInActiveWindow(); - } catch (IllegalStateException e) { - return null; - } + return mRootInActiveWindow; } @Override @@ -107,19 +106,36 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi LOCK.lock(); ENABLED.signalAll(); LOCK.unlock(); + mHandler = new Handler(); mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { - AccessibilityNodeInfo root = getRootInActiveWindow(); - if (root != null) { - mRootInActiveWindow = root; - } + mHandler.post(new Runnable() { + @Override + public void run() { + AccessibilityNodeInfo root = superGetRootInActiveWindow(); + if (root != null) { + mRootInActiveWindow = root; + Log.d(TAG, "getRootInActiveWindow: " + root); + } + } + }); + } }, 0, 100); // FIXME: 2017/2/12 有时在无障碍中开启服务后这里不会调用服务也不会运行,安卓的BUG??? } + private AccessibilityNodeInfo superGetRootInActiveWindow() { + try { + return super.getRootInActiveWindow(); + } catch (IllegalStateException e) { + e.printStackTrace(); + return null; + } + } + public static boolean disable() { if (instance != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { instance.disableSelf(); diff --git a/common/src/main/java/com/stardust/pio/PFile.java b/common/src/main/java/com/stardust/pio/PFile.java index 3aea3c8b..c474f088 100644 --- a/common/src/main/java/com/stardust/pio/PFile.java +++ b/common/src/main/java/com/stardust/pio/PFile.java @@ -2,11 +2,16 @@ package com.stardust.pio; import android.content.Context; import android.content.res.AssetManager; +import android.os.Environment; +import android.provider.MediaStore; + +import com.stardust.util.Func1; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -47,9 +52,16 @@ public class PFile { return open(path, "r", DEFAULT_ENCODING, DEFAULT_BUFFER_SIZE); } + public static boolean create(String path) { + try { + return new File(path).createNewFile(); + } catch (IOException e) { + return false; + } + } public static boolean createIfNotExists(String path) { - ensureDirectory(path); + ensureDir(path); File file = new File(path); if (!file.exists()) { try { @@ -61,7 +73,11 @@ public class PFile { return false; } - public static boolean ensureDirectory(String path) { + public static boolean exists(String path) { + return new File(path).exists(); + } + + public static boolean ensureDir(String path) { int i = path.lastIndexOf("\\"); if (i < 0) i = path.lastIndexOf("/"); @@ -89,8 +105,7 @@ public class PFile { try { return read(new FileInputStream(file), encoding); } catch (FileNotFoundException e) { - e.printStackTrace(); - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -104,8 +119,7 @@ public class PFile { is.read(bytes); return new String(bytes, encoding); } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); + throw new UncheckedIOException(e); } } @@ -119,7 +133,7 @@ public class PFile { } public static boolean copyStream(InputStream is, String path) { - if (!ensureDirectory(path)) + if (!ensureDir(path)) return false; File file = new File(path); try { @@ -127,14 +141,15 @@ public class PFile { if (!file.createNewFile()) return false; FileOutputStream fos = new FileOutputStream(file); - return write(is, fos); - } catch (IOException e) { + write(is, fos); + return false; + } catch (IOException | UncheckedIOException e) { e.printStackTrace(); return false; } } - public static boolean write(InputStream is, OutputStream os) { + public static void write(InputStream is, OutputStream os) { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; try { while (is.available() > 0) { @@ -143,14 +158,45 @@ public class PFile { } is.close(); os.close(); - return true; } catch (IOException e) { - e.printStackTrace(); - return false; + throw new UncheckedIOException(e); } } + public static void write(String path, String text) { + write(new File(path), text); + } + + public static void write(String path, String text, String encoding) { + try { + write(new FileOutputStream(path), text, encoding); + } catch (FileNotFoundException e) { + throw new UncheckedIOException(e); + } + } + + public static void write(File file, String text) { + try { + write(new FileOutputStream(file), text); + } catch (FileNotFoundException e) { + throw new UncheckedIOException(e); + } + } + + public static void write(FileOutputStream fileOutputStream, String text) { + write(fileOutputStream, text, "utf-8"); + } + + + public static void write(OutputStream outputStream, String text, String encoding) { + try { + outputStream.write(text.getBytes(encoding)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public static boolean copy(String pathFrom, String pathTo) { try { return copyStream(new FileInputStream(pathFrom), pathTo); @@ -183,29 +229,6 @@ public class PFile { return fileName.substring(i + 1); } - public static boolean write(String path, String text) { - return write(new File(path), text); - } - - public static boolean write(File file, String text) { - try { - return write(new FileOutputStream(file), text); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return false; - } - } - - public static boolean write(OutputStream outputStream, String text) { - try { - outputStream.write(text.getBytes()); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - public static String generateNotExistingPath(String path, String extension) { if (!new File(path + extension).exists()) return path + extension; @@ -216,19 +239,18 @@ public class PFile { return pathI; i++; } - } - public static String getNameWithoutExtension(String fileName) { - int a = fileName.lastIndexOf('/'); - if (a < 0) - a = fileName.lastIndexOf('\\'); - if (a < 0) - a = -1; - int b = fileName.indexOf('.', a + 1); + public static String getName(String filePath) { + return new File(filePath).getName(); + } + + public static String getNameWithoutExtension(String filePath) { + String fileName = getName(filePath); + int b = fileName.lastIndexOf('.'); if (b < 0) b = fileName.length(); - fileName = fileName.substring(a + 1, b); + fileName = fileName.substring(0, b); return fileName; } @@ -257,6 +279,18 @@ public class PFile { return file.delete(); } + public static boolean remove(String path) { + return new File(path).delete(); + } + + public static boolean removeDir(String path) { + return deleteRecursively(new File(path)); + } + + public static String getSdcardPath() { + return Environment.getExternalStorageDirectory().getPath(); + } + public static String readAsset(AssetManager assets, String path) { try { return read(assets.open(path)); @@ -264,4 +298,36 @@ public class PFile { throw new UncheckedIOException(e); } } + + public static String[] listDir(String path) { + File file = new File(path); + return file.list(); + } + + public static String[] listDir(String path, final Func1 filter) { + final File file = new File(path); + return file.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return filter.call(name); + } + }); + } + + public static boolean isFile(String path) { + return new File(path).isFile(); + } + + public static boolean isDir(String path) { + return new File(path).isDirectory(); + } + + public static boolean isDirEmpty(String path) { + File file = new File(path); + return file.isDirectory() && file.list().length == 0; + } + + public static String join(String parent, String child) { + return new File(parent, child).getPath(); + } }