diff --git a/README.md b/README.md index b9418a98..d2537dcf 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,21 @@ ### 简介 -* Android 平台支持 [无障碍服务](https://developer.android.com/guide/topics/ui/accessibility/service?hl=zh-cn) 的 JavaScript 自动化工具 -* 需要 Android [API](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) [24](https://developer.android.com/reference/android/os/Build.VERSION_CODES#N) ([7.0](https://zh.wikipedia.org/wiki/Android_Nougat)) [[N](https://developer.android.com/reference/android/os/Build.VERSION_CODES#N)] 及以上操作系统 -* 克隆 (clone) 自 [hyb1996/Auto.js](https://github.com/hyb1996/Auto.js) - ****** -### 指南 +[Auto.js](https://github.com/hyb1996/Auto.js) 是一款 Android 平台支持 [无障碍服务](https://developer.android.com/guide/topics/ui/accessibility/service?hl=zh-cn) 的 JavaScript 自动化工具软件. -****** +Auto.js 由 [hyb1996](https://github.com/hyb1996) 于 Jan 27, 2017 初次发布, 于 Mar 13, 2020 停止维护, 最终版本名称为 `4.1.1 alpha2`. -* [应用文档](https://docs.autojs6.com) -* [使用手册 (待编写)](https://docs.autojs6.com/#/manual) -* [疑难解答](https://docs.autojs6.com/#/qa) -* [项目编译构建](#项目编译构建) -* [脚本开发辅助](#脚本开发辅助) +AutoJs6 在 Auto.js 最终项目的基础上, 于 Dec 1, 2021 进行二次开发, 继续保持开源免费. + +基于 Auto.js 二次开发的开源项目 (以开发时间排序, 仅部分列举): + +| 项目名称 | 应用名称 | 开发者 | 开发时间 | +|:-----------------------------------------------------:|:----------------------:|:-----------------------------------------------------:|:------------:| +| [Auto.js](https://github.com/TonyJiangWJ/Auto.js) | Auto.js M | [TonyJiangWJ](https://github.com/TonyJiangWJ) | Nov 21, 2019 | +| [AutoX](https://github.com/kkevsekk1/AutoX) | Autox.js / Autox.js v6 | [kkevsekk1](https://github.com/kkevsekk1) | Jul 24, 2020 | +| [AutoJs6](https://github.com/SuperMonster003/AutoJs6) | AutoJs6 | [SuperMonster003](https://github.com/SuperMonster003) | Dec 1, 2021 | ****** @@ -62,6 +62,27 @@ ****** +### 环境 + +****** + +- Android 操作系统 +- [API](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) [24](https://developer.android.com/reference/android/os/Build.VERSION_CODES#N) ([7.0](https://zh.wikipedia.org/wiki/Android_Nougat)) [[N](https://developer.android.com/reference/android/os/Build.VERSION_CODES#N)] 及以上 + +****** + +### 指南 + +****** + +* [应用文档](https://docs.autojs6.com) +* [使用手册 (待编写)](https://docs.autojs6.com/#/manual) +* [疑难解答](https://docs.autojs6.com/#/qa) +* [项目编译构建](#项目编译构建) +* [脚本开发辅助](#脚本开发辅助) + +****** + ### 主要变更 ****** @@ -74,33 +95,33 @@ * [Rhino](https://github.com/mozilla/rhino/) 引擎由 [v1.7.7.2](https://github.com/mozilla/rhino/releases/tag/Rhino1_7_7_2_Release) 升级至 [v1.7.15-SNAPSHOT](http://rhino.autojs6.com/blob/dbe3f43ba5eb01e7f76139208f36c383dcd1c488/gradle.properties#L3) - * 支持 [Object.values()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/values) + * 支持 [Object.values()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/values) - ```javascript - Object.values({name: 'Max', age: 4}); // ['max', 4] - ``` + ```javascript + Object.values({name: 'Max', age: 4}); // ['max', 4] + ``` - * 支持 [Array.prototype.includes()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) + * 支持 [Array.prototype.includes()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes) - ```javascript - [10, 20, NaN].includes(20); // true - ``` + ```javascript + [10, 20, NaN].includes(20); // true + ``` - * 支持 [BigInt](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt) + * 支持 [BigInt](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt) - ```javascript - typeof 567n === 'bigint'; // true - ``` + ```javascript + typeof 567n === 'bigint'; // true + ``` - * 支持 [模板字符串](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals) + * 支持 [模板字符串](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals) - ```javascript - `Lucky number: ${(Math.random() * 100).toFixed(0)}` - ``` + ```javascript + `Lucky number: ${(Math.random() * 100).toFixed(0)}` + ``` - * 查看 Rhino 引擎 [更多新特性](http://project.autojs6.com/blob/master/app/src/main/assets/doc/RHINO.md) + * 查看 Rhino 引擎 [更多新特性](http://project.autojs6.com/blob/master/app/src/main/assets/doc/RHINO.md) - * 查看 Rhino 引擎 [兼容性列表](https://mozilla.github.io/rhino/compat/engines.html) + * 查看 Rhino 引擎 [兼容性列表](https://mozilla.github.io/rhino/compat/engines.html) ****** @@ -110,6 +131,35 @@ [comment]: <> "Version history only shows last 3 versions" +# v6.3.4 + +###### 2023/10/03 + +* `新增` ocr 模块支持 Paddle Lite 引擎 (by [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`pr #120`](http://pr.autojs6.com/120)_ +* `新增` WebSocket 模块 (参阅 项目文档 > [颜色类](https://docs.autojs6.com/#/webSocketType)) +* `新增` device.rotation / device.orientation 等方法 +* `新增` 内部 Java 类支持 class 静态属性访问 +* `修复` VSCode 插件保存文件到本地时文件扩展名信息丢失的问题 +* `修复` 使用协程特性运行项目产生未捕获异常致使应用崩溃的问题 +* `修复` 重启或退出应用时浮动按钮无法记录其位置状态信息的问题 +* `修复` 设备屏幕方向改变时无法获取更新后的设备配置信息的问题 +* `修复` 屏幕旋转至横向时 Toolbar 标题字体过小的问题 +* `修复` 屏幕旋转至横向时应用主页的页签排版过于拥挤的问题 +* `修复` 屏幕旋转至横向时浮动按钮可能溢出屏幕的问题 _[`issue #90`](http://issues.autojs6.com/90)_ +* `修复` 屏幕多次旋转时无法恢复浮动按钮的坐标及屏幕侧边方向的问题 +* `修复` 消息浮动框在多个脚本同时运行时可能存在被遮蔽的问题 _[`issue #67`](http://issues.autojs6.com/67)_ +* `修复` 使用广播分析布局时点击布局无法弹出菜单且导致应用崩溃的问题 +* `修复` 第二次及以后创建的 WebSocket 实例均无法正常触发监听器的问题 +* `修复` 重新合并部分 PR (by [aiselp](https://github.com/aiselp)) 以解决部分脚本无法正常结束运行的问题 _[`pr #75`](http://pr.autojs6.com/75)_ _[`pr #78`](http://pr.autojs6.com/78)_ +* `修复` 撤销 importPackage 的全局重定向方法以避免某些作用域下的包导入异常 _[`issue #88`](http://issues.autojs6.com/88)_ +* `修复` 日志活动页面使用复制或导出功能时可能导致应用崩溃的问题 +* `优化` 日志活动页面导出功能重命名为发送功能并重新实现符合实际意义的导出功能 +* `优化` 日志活动页面发送功能支持条目数量过大时自动截取并作出提示 +* `优化` ocr 模块同时兼容 Google MLKit 及 Paddle Lite 引擎 (参阅 项目文档 > [光学字符识别](https://docs.autojs6.com/#/ocr?id=p-mode)) +* `优化` 提升无障碍服务自动启动的成功概率 +* `优化` 构建工具自适应 Java 发行版本以尽量避免 "无效的发行版本" 问题 +* `优化` 部分 Kotlin 注解处理由 kapt 迁移至 KSP + # v6.3.3 ###### 2023/07/21 @@ -161,42 +211,10 @@ * `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_ * `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_ * `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭) -* `优化` findMultiColors 方法内部实现 (by [LYS](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_ +* `优化` findMultiColors 方法内部实现 (by [LYS86](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_ * `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件 * `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v632)_ -# v6.3.1 - -###### 2023/05/26 - -* `新增` 发布通知权限及主页抽屉开关 _[`issue #55`](http://issues.autojs6.com/55)_ -* `新增` UI 模式支持简单的 Android 布局解析 (参阅 示例代码 > 布局 > 简单安卓布局) -* `新增` UI 模式增加 console / imagebutton / ratingbar / switch / textclock / togglebutton 等控件 -* `新增` UI 模式控件的颜色色值支持 [OmniColor](https://docs.autojs6.com/#/omniTypes?id=omnicolor) 类型 (如 color="orange") -* `新增` UI 模式的控件完全支持 attr 方法设置控件属性 (如 ui.text.attr('color', 'blue')) -* `新增` UI 模式控件支持布尔类型属性值的缺省形式 (如 clickable="true" 可简写为 clickable 或 isClickable) -* `新增` button 控件支持 isColored 及 isBorderless 布尔类型属性 -* `新增` console.resetGlobalLogConfig 方法用于重置全局日志配置 -* `新增` web.newWebSocket 方法用于创建 Web Socket 实例 (参阅 项目文档 > [万维网](https://docs.autojs6.com/#/web?id=m-newwebsocket)) -* `修复` 文件管理器的文件夹排序异常 -* `修复` floaty 模块构建的浮动窗口无法调节样式及位置的问题 _[`issue #60`](http://issues.autojs6.com/60)_ -* `修复` floaty 模块构建的浮动窗口与系统状态栏重叠的问题 -* `修复` http.postMultipart 方法功能异常 _[`issue #56`](http://issues.autojs6.com/56)_ -* `修复` Android 7.x 无法运行任何脚本的问题 _[`issue #61`](http://issues.autojs6.com/61)_ -* `修复` sign.property 文件不存在时无法构建项目的问题 -* `修复` 高版本系统 AutoJs6 置于后台时可能因无前台通知权限而崩溃的问题 (API >= 33) -* `修复` 调用 console.show 方法后日志窗口点击 FAB 按钮无法清空日志的问题 -* `修复` 脚本编辑器调试时出现的 prototype 空指针异常 -* `修复` 脚本编辑器运行脚本时在缓存文件夹运行临时脚本而非先保存再在原始位置运行以避免可能的脚本内容丢失问题 -* `修复` 调整布局层次分析的层级色条宽度避免层级过多时控件名称无法显示的问题 _[`issue #46`](http://issues.autojs6.com/46)_ -* `优化` 布局分析浮动窗口增加退出按钮以关闭窗口 _[`issue #63`](http://issues.autojs6.com/63)_ -* `优化` 脚本绝对路径使用简称形式以缩减文本长度并增加可读性 -* `优化` 将 Error 替换为 Exception 避免出现异常时 AutoJs6 应用崩溃 -* `优化` 视图 (View) 绑定方式由 ButterKnife 迁移至 View Binding _[`issue #48`](http://issues.autojs6.com/48)_ -* `优化` 服务端模式非正常关闭时将于 AutoJs6 启动时自动开启 _[`issue #64`](http://issues.autojs6.com/64)_ -* `优化` 客户端模式非正常关闭时将于 AutoJs6 启动时按最近一次的历史地址自动连接 -* `优化` 部分依赖或本地库版本调整 _[`CHANGELOG.md`](http://project.autojs6.com/blob/master/app/src/main/assets/doc/CHANGELOG.md#v631)_ - ##### 更多版本历史可参阅 * [CHANGELOG.md](http://changelog.autojs6.com) @@ -207,27 +225,18 @@ ****** -建议始终使用 Android Studio 进行 AutoJs6 调试或开发. +如需对 AutoJs6 开源项目进行调试或开发, 可使用 Android Studio 或 [IntelliJ IDEA](https://www.jetbrains.com/idea/) ([Jetbrains](https://www.jetbrains.com/) 公司产品). -如需使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) ([Jetbrains](https://www.jetbrains.com/) 公司产品), 需留意以下注意事项: - -- IntelliJ IDEA 版本建议不低于 `2022.3.3 (Ultimate Edition)` -- IntelliJ IDEA 的 Gradle JVM 版本不低于 `19` -- 回退 Gradle 构建工具版本至 IDEA 支持的版本 - - 如 `classpath("com.android.tools.build:gradle:7.4.0")` - - 具体版本可根据 IDEA 的错误提示获知 -- 回退 Kotlin 脚本 (KTS) 语法 - - 如 ` com.android.build.api.dsl.CommonExtension#options` 需回退为 `packagingOptions` -- ... ... +本小节以 Android Studio 为例介绍 AutoJs6 开源项目的编译构建方法, IntelliJ IDEA 与之类似. #### Android Studio 准备 -下载 `Android Studio Flamingo | 2022.2.1 Patch 2` 版本 (按需选择其一): +下载 `Android Studio Giraffe | 2022.3.1 Patch 2` 版本 (按需选择其一): -- [android-studio-2022.2.1.20-windows.exe](https://redirector.gvt1.com/edgedl/android/studio/install/2022.2.1.20/android-studio-2022.2.1.20-windows.exe) (1.0 GB) -- [android-studio-2022.2.1.20-windows.zip](https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2022.2.1.20/android-studio-2022.2.1.20-windows.zip) (1.0 GB) +- [android-studio-2022.3.1.20-windows.exe](https://redirector.gvt1.com/edgedl/android/studio/install/2022.3.1.20/android-studio-2022.3.1.20-windows.exe) (1.1 GB) +- [android-studio-2022.3.1.20-windows.zip](https://redirector.gvt1.com/edgedl/android/studio/ide-zips/2022.3.1.20/android-studio-2022.3.1.20-windows.zip) (1.1 GB) -> 注: 上述版本发布时间为 2023 年 5 月 24 日. 如需下载其他版本, 或上述链接已失效, 可访问 [Android Studio 发行版本归档](https://developer.android.com/studio/archive) 页面. +> 注: 上述版本发布时间为 2023 年 9 月 28 日. 如需下载其他版本, 或上述链接已失效, 可访问 [Android Studio 发行版本归档](https://developer.android.com/studio/archive?hl=en) 页面. 安装或解压上述文件, 运行 Android Studio 软件 (如 `"D:\android-studio\bin\studio64.exe"`). @@ -252,12 +261,12 @@ Android SDK (安卓软件开发工具包) #### JDK 准备 -AutoJs6 项目依赖的 `JDK (Java 开发工具包)` 发行版本不低于 `19`. +AutoJs6 项目依赖的 `JDK (Java 开发工具包)` 发行版本不低于 `17`. + +> 注: 如果计算机系统已安装 JDK 且版本不低于 17, 则可跳过此小节内容. JDK 可使用 IDE 直接下载, 或访问 [Oracle 网站](https://www.oracle.com/java/technologies/downloads/) 下载. -> 注: 如果计算机系统已安装 JDK 且版本不低于 19, 则可跳过此小节内容. - 在 Android Studio 软件中使用快捷键 `CTRL + ALT + S` 打开设置页面: ```text @@ -268,12 +277,9 @@ Gradle `Gradle JDK` 处可选择或添加不同版本的 JDK. -- 列表中存在合适版本的 JDK - - 直接选择即可 -- 选择 `Download JDK (下载 JDK)` 下载新的 JDK - - 选择一个版本不低于 19 的 JDK, 点击 `Download (下载)` 按钮并等待下载完成 -- 选择 `Add JDK (添加 JDK)` 添加已存在的本地 JDK - - 如计算机系统已存在合适版本的 JDK, 可定位其目录并完成 JDK 添加 +如果列表中已存在合适版本的 JDK (>= `17`), 则直接选择即可. +否则可以选择 `Download JDK (下载 JDK)` 下载合适的 JDK, 点击 `Download (下载)` 按钮并等待下载完成. +也可以选择 `Add JDK (添加 JDK)` 添加已存在的本地 JDK, 定位其目录并完成 JDK 添加. #### AutoJs6 资源克隆 @@ -295,14 +301,14 @@ Gradle 打包项目并生成可安装到安卓设备的 APK 文件: - 调试版 (Debug Version) - - `Build (构建)` -> `Build Bundle(s) / APK(s)` -> `Build APK(s)` - - 生成带默认签名的调试版安装包 - - 路径示例: `"D:\AutoJs6\app\build\outputs\apk\debug\"` + - `Build (构建)` -> `Build Bundle(s) / APK(s)` -> `Build APK(s)` + - 生成带默认签名的调试版安装包 + - 路径示例: `"D:\AutoJs6\app\build\outputs\apk\debug\"` - 发布版 (Release Version) - - `Build (构建)` -> `Generate Signed Bundle / APK` - - 选择 `APK` 选项 - - 准备好签名文件 (新建或选取), 生成已签名的发布版安装包 - - 路径示例: `"D:\AutoJs6\app\release\"` + - `Build (构建)` -> `Generate Signed Bundle / APK` + - 选择 `APK` 选项 + - 准备好签名文件 (新建或选取), 生成已签名的发布版安装包 + - 路径示例: `"D:\AutoJs6\app\release\"` > 参阅: [Android Docs](https://developer.android.com/studio/run?hl=zh-cn) @@ -328,31 +334,34 @@ Gradle - [AutoJs6-Documentation](http://docs-project.autojs6.com) - AutoJs6 应用文档 -****** +现有的脚本开发项目可作为参考, 激发个人脚本项目的创作灵感: -### 相关项目 +- [Ant-Forest](https://github.com/TonyJiangWJ/Ant-Forest) - 蚂蚁森林能量自动收取脚本 by [TonyJiangWJ](https://github.com/TonyJiangWJ) +- [Ant-Forest](https://github.com/SuperMonster003/Ant-Forest) - 蚂蚁森林能量自动收取脚本 by [SuperMonster003](https://github.com/SuperMonster003) +- [autojs](https://github.com/e1399579/autojs) - Auto.js 实用脚本 by [e1399579](https://github.com/e1399579) +- [autojsDemo](https://github.com/snailuncle/autojsDemo) - Auto.js 演示示例 by [snailuncle](https://github.com/snailuncle) ****** -[AutoX (Autox.js)](https://github.com/kkevsekk1/AutoX) by [kkevsekk1](https://github.com/kkevsekk1) +### 贡献参与 -- 安卓平台 JavaScript 自动化工具 (Auto.js 二次开发项目) +****** -[AutoJs6-Documentation](http://docs-project.autojs6.com) by [SuperMonster003](https://github.com/SuperMonster003) +感谢每一位参与 AutoJs6 项目开发的贡献人员. -- AutoJs6 应用文档 +| 贡献人员 | 提交数 | 最近提交 | +|:-----------------------------------------------------:|:-------------------------------------------------------------------------------:|:------------:| +| [TonyJiangWJ](https://github.com/TonyJiangWJ) | [3](https://github.com/SuperMonster003/AutoJs6/commits?author=TonyJiangWJ) | Aug 29, 2023 | +| [SuperMonster003](https://github.com/SuperMonster003) | [33](https://github.com/SuperMonster003/AutoJs6/commits?author=SuperMonster003) | Jul 21, 2023 | +| [LZX284](https://github.com/LZX284) (Ai) | [4](https://github.com/SuperMonster003/AutoJs6/commits?author=LZX284) | Jul 20, 2023 | +| [little-alei](https://github.com/little-alei) (抠脚本人) | [12](https://github.com/SuperMonster003/AutoJs6/commits?author=little-alei) | Jul 12, 2023 | +| [aiselp](https://github.com/aiselp) | [6](https://github.com/SuperMonster003/AutoJs6/pulls?q=is%3Apr+author%3Aaiselp) | Jun 14, 2023 | +| [LYS86](https://github.com/LYS86) (LYS) | [2](https://github.com/SuperMonster003/AutoJs6/commits?author=LYS86) | Jun 3, 2023 | -[AutoJs6-VSCode-Extension](http://vscext-project.autojs6.com) by [SuperMonster003](https://github.com/SuperMonster003) - -- AutoJs6 调试器 (VSCode 平台插件) - -[AutoJs6-TypeScript-Declarations](http://dts-project.autojs6.com) by [SuperMonster003](https://github.com/SuperMonster003) - -- AutoJs6 声明文件 (代码智能补全) - -[Ant-Forest](https://github.com/SuperMonster003/Ant-Forest) by [SuperMonster003](https://github.com/SuperMonster003) - -- 蚂蚁森林能量自动收取脚本 (AutoJs6 示例脚本项目) +> 数据统计区间: Dec 1, 2021 - Oct 3, 2023. +> 数据条目按 "最近提交" 列排序 (降序). +> 新发起的暂未处理的 Pull Request, 将在合并处理后加入数据统计. +> 部分贡献人员在 [GitHub Contributors](https://github.com/SuperMonster003/AutoJs6/graphs/contributors) 未能正常出现, 其提交记录为空, 仍可通过 [Pull Request](https://github.com/SuperMonster003/AutoJs6/pulls) 查看贡献记录. [//]: # ( # --------------------------------------------------------------# diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8ce725d3..1411ca8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,4 @@ -@file:Suppress("UnstableApiUsage", "SpellCheckingInspection") +@file:Suppress("SpellCheckingInspection") import com.android.build.gradle.internal.api.ApplicationVariantImpl import com.android.build.gradle.internal.api.BaseVariantOutputImpl @@ -15,8 +15,9 @@ val versions = Versions("$rootDir/version.properties") plugins { id("com.android.application") - kotlin("android") - kotlin("kapt") + id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.kapt") /* kotlin("kapt") */ + id("org.jetbrains.kotlin.android") /* kotlin("android") */ } dependencies /* Unclassified */ { @@ -38,7 +39,7 @@ dependencies /* Unclassified */ { implementation("de.psdev.licensesdialog:licensesdialog:2.2.0") // Commons Lang - implementation("org.apache.commons:commons-lang3:3.12.0") + implementation("org.apache.commons:commons-lang3:3.13.0") // Retrofit implementation("com.squareup.retrofit2:retrofit:2.9.0") @@ -47,14 +48,14 @@ dependencies /* Unclassified */ { implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2") // Glide - implementation("com.github.bumptech.glide:glide:4.15.1") - kapt("com.github.bumptech.glide:compiler:4.15.1") + implementation("com.github.bumptech.glide:glide:4.16.0") + ksp("com.github.bumptech.glide:ksp:4.16.0") // Joda Time implementation("joda-time:joda-time:2.12.5") // Flurry - implementation("com.flurry.android:analytics:14.3.0") + implementation("com.flurry.android:analytics:14.4.0") // Bugly implementation(project(":libs:com.tencent.bugly.crashreport-4.0.4")) @@ -63,7 +64,7 @@ dependencies /* Unclassified */ { implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") // Webkit - implementation("androidx.webkit:webkit:1.7.0") + implementation("androidx.webkit:webkit:1.8.0") // Gson implementation("com.google.code.gson:gson:2.10.1") @@ -76,8 +77,7 @@ dependencies /* Unclassified */ { implementation("log4j:log4j:1.2.17") // Preference - implementation("androidx.preference:preference:1.2.0") - implementation("androidx.preference:preference-ktx:1.2.0") + implementation("androidx.preference:preference-ktx:1.2.1") // RootShell implementation("com.github.Stericson:RootShell:1.6") @@ -130,6 +130,11 @@ dependencies /* Unclassified */ { implementation(files("$rootDir/libs/javamail-android/activation.jar")) implementation(files("$rootDir/libs/javamail-android/additionnal.jar")) implementation(files("$rootDir/libs/javamail-android/mail.jar")) + + // Shizuku + implementation("dev.rikka.shizuku:api:13.1.5") + implementation("dev.rikka.shizuku:provider:13.1.5") + implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3@aar") } dependencies /* Test */ { @@ -140,9 +145,11 @@ dependencies /* Test */ { dependencies /* Annotations */ { // Android Annotations - kapt("org.androidannotations:androidannotations:4.8.0") implementation("org.androidannotations:androidannotations-api:4.8.0") - implementation("androidx.annotation:annotation:1.6.0") + implementation("androidx.annotation:annotation:1.7.0") + // FIXME by SuperMonster003 on Aug 17, 2023. + // ! Failed to migrate it from kapt to KSP. + kapt("org.androidannotations:androidannotations:4.8.0") // JCIP Annotations implementation("net.jcip:jcip-annotations:1.0") @@ -250,7 +257,7 @@ dependencies /* Auto.js Extensions */ { // Extracted from com.github.hyb1996:MutableTheme:1.0.0 // @Legacy com.jrummyapps:colorpicker:2.1.7 implementation("com.jaredrummler:colorpicker:1.1.0") - implementation("androidx.recyclerview:recyclerview:1.3.0") + implementation("androidx.recyclerview:recyclerview:1.3.1") implementation("com.github.ozodrukh:CircularReveal:2.0.1") } @@ -349,6 +356,10 @@ android { buildFeatures { viewBinding = true + + // @Hint by SuperMonster003 on Aug 14, 2023. + // ! Substitution of "android.defaults.buildfeatures.buildconfig=true" + buildConfig = true } defaultConfig { @@ -466,22 +477,68 @@ class Versions(filePath: String) { val appVersionCode = properties["VERSION_BUILD"].let { it as String }.toInt() val vscodeExtRequiredVersion = properties["VSCODE_EXT_REQUIRED_VERSION"] as String + private val javaVersionMinSupported = properties["JAVA_VERSION_MIN_SUPPORTED"] + .let { it as String }.toInt() + .also { + if (!JavaVersion.current().isCompatibleWith(JavaVersion.toVersion(it))) { + throw GradleException("Current Gradle JDK version ${JavaVersion.current()} does not meet the minimum requirement which $it is needed.") + } + } + private val javaVersionMinSuggested = properties["JAVA_VERSION_MIN_SUGGESTED"] + .let { it as String }.toInt() + .also { + if (!JavaVersion.current().isCompatibleWith(JavaVersion.toVersion(it))) { + logger.error("It is recommended to upgrade current Gradle JDK version ${JavaVersion.current()} to $it or higher.") + } + } private val javaVersionRaw = properties["JAVA_VERSION"] as String + private var javaVersionInfoSuffix = "" + private val javaVersionCeilMap = mapOf( + "as" to mapOf( + /* Empty so far. */ + ), + "idea" to mapOf( + "2023.2" to javaVersionMinSuggested, /* Aug 17, 2023. */ + "2023.1" to javaVersionMinSuggested, /* Aug 17, 2023. */ + "2022.3" to javaVersionMinSuggested, /* Aug 17, 2023. */ + ), + ) val javaVersion: JavaVersion by lazy { - val minSupportedVersion = 17 var versionInt = javaVersionRaw.toInt() - while (versionInt > minSupportedVersion) { + + while (versionInt > javaVersionMinSupported) { + @Suppress("EnumValuesSoftDeprecate") if (JvmTarget.values().any { it.name.contains(Regex("_$versionInt$")) }) { break } versionInt -= 1 } + + if (!JavaVersion.toVersion("$versionInt").isCompatibleWith(JavaVersion.toVersion(javaVersionRaw))) { + javaVersionInfoSuffix += " [fallback]" + } + + val platformVersion = gradle.extra["platformVersion"] as String + val platformType = gradle.extra["platformType"] as String + + javaVersionCeilMap[platformType]!![platformVersion]?.let { ceil: Int -> + if (versionInt > ceil) { + versionInt = ceil + javaVersionInfoSuffix += " [coerced]" + } + } + + if (!JavaVersion.current().isCompatibleWith(JavaVersion.toVersion(versionInt))) { + versionInt = JavaVersion.current().majorVersion.toInt() + javaVersionInfoSuffix += " [consistent]" + } + JavaVersion.toVersion(versionInt.toString()) } private var isBuildNumberAutoIncremented = false - private val minBuildTimeGap = Utils.hours2Millis(0.5) + private val minBuildTimeGap = Utils.hours2Millis(0.75) private val assembleTargets = mapOf("app" to listOf("debug", "release")) private val isBuildGapEnough @@ -506,12 +563,28 @@ class Versions(filePath: String) { properties.store(file.writer(), null) } - fun showInfo() = arrayOf( - "Version name: $appVersionName", - "Version code: $appVersionCode${" [auto-incremented]".takeIf { isBuildNumberAutoIncremented } ?: ""}", - "SDK versions: min [$sdkVersionMin] / target [$sdkVersionTarget] / compile [$sdkVersionCompile]", - "Java version: $javaVersion${" [fallback]".takeUnless { javaVersion.isCompatibleWith(JavaVersion.toVersion(javaVersionRaw)) } ?: ""}", - ).forEach { println(it) } + fun showInfo() { + val title = "Version information for AutoJs6 app library" + + val infoVerName = "Version name: $appVersionName" + val infoVerCode = "Version code: $appVersionCode${" [auto-incremented]".takeIf { isBuildNumberAutoIncremented } ?: ""}" + val infoVerSdk = "SDK versions: min [$sdkVersionMin] / target [$sdkVersionTarget] / compile [$sdkVersionCompile]" + val infoVerJava = "Java version: $javaVersion$javaVersionInfoSuffix" + + val maxLength = arrayOf(title, infoVerName, infoVerCode, infoVerSdk, infoVerJava).maxOf { it.length } + + arrayOf( + "=".repeat(maxLength), + title, + "-".repeat(maxLength), + infoVerName, + infoVerCode, + infoVerSdk, + infoVerJava, + "=".repeat(maxLength), + "", + ).forEach { println(it) } + } fun handleIfNeeded(project: Project) = assembleTargets.forEach { val targetName = it.key diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04e7e9ff..8523e9c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,12 +20,18 @@ ! Target API >= 30: WRT + [ MSV<29> ] + LGC + MAN --> - + + + + + @@ -39,10 +45,6 @@ - - - - @@ -72,6 +74,8 @@ + + @@ -92,9 +96,6 @@ android:name="android.permission.WRITE_SECURE_SETTINGS" tools:ignore="ProtectedPermissions" /> - - @@ -103,6 +104,15 @@ + + + + + + + + + @@ -553,6 +564,14 @@ android:enabled="true" android:foregroundServiceType="mediaProjection" /> + + \ No newline at end of file diff --git a/app/src/main/assets/doc/CHANGELOG.md b/app/src/main/assets/doc/CHANGELOG.md index 28c865c8..a230157b 100644 --- a/app/src/main/assets/doc/CHANGELOG.md +++ b/app/src/main/assets/doc/CHANGELOG.md @@ -6,9 +6,42 @@ # v6.3.4 -###### 2023/07/25 +###### 2023/10/03 +* `新增` ocr 模块支持 Paddle Lite 引擎 (by [TonyJiangWJ](https://github.com/TonyJiangWJ)) _[`pr #120`](http://pr.autojs6.com/120)_ +* `新增` WebSocket 模块 (参阅 项目文档 > [颜色类](https://docs.autojs6.com/#/webSocketType)) +* `新增` device.rotation / device.orientation 等方法 +* `新增` 内部 Java 类支持 class 静态属性访问 * `修复` VSCode 插件保存文件到本地时文件扩展名信息丢失的问题 +* `修复` 使用协程特性运行项目产生未捕获异常致使应用崩溃的问题 +* `修复` 重启或退出应用时浮动按钮无法记录其位置状态信息的问题 +* `修复` 设备屏幕方向改变时无法获取更新后的设备配置信息的问题 +* `修复` 屏幕旋转至横向时 Toolbar 标题字体过小的问题 +* `修复` 屏幕旋转至横向时应用主页的页签排版过于拥挤的问题 +* `修复` 屏幕旋转至横向时浮动按钮可能溢出屏幕的问题 _[`issue #90`](http://issues.autojs6.com/90)_ +* `修复` 屏幕多次旋转时无法恢复浮动按钮的坐标及屏幕侧边方向的问题 +* `修复` 消息浮动框在多个脚本同时运行时可能存在被遮蔽的问题 _[`issue #67`](http://issues.autojs6.com/67)_ +* `修复` 使用广播分析布局时点击布局无法弹出菜单且导致应用崩溃的问题 +* `修复` 第二次及以后创建的 WebSocket 实例均无法正常触发监听器的问题 +* `修复` 重新合并部分 PR (by [aiselp](https://github.com/aiselp)) 以解决部分脚本无法正常结束运行的问题 _[`pr #75`](http://pr.autojs6.com/75)_ _[`pr #78`](http://pr.autojs6.com/78)_ +* `修复` 撤销 importPackage 的全局重定向方法以避免某些作用域下的包导入异常 _[`issue #88`](http://issues.autojs6.com/88)_ +* `修复` 日志活动页面使用复制或导出功能时可能导致应用崩溃的问题 +* `优化` 日志活动页面导出功能重命名为发送功能并重新实现符合实际意义的导出功能 +* `优化` 日志活动页面发送功能支持条目数量过大时自动截取并作出提示 +* `优化` ocr 模块同时兼容 Google MLKit 及 Paddle Lite 引擎 (参阅 项目文档 > [光学字符识别](https://docs.autojs6.com/#/ocr?id=p-mode)) +* `优化` 提升无障碍服务自动启动的成功概率 +* `优化` 构建工具自适应 Java 发行版本以尽量避免 "无效的发行版本" 问题 +* `优化` 部分 Kotlin 注解处理由 kapt 迁移至 KSP +* `优化` 附加 Rikka Shizuku 版本 13.1.5 +* `优化` 升级 Gradle Compile 版本 33 -> 34 +* `优化` 升级 Gradle 版本 8.3-rc-1 -> 8.4-rc-3 +* `优化` 升级 Apache Commons Lang3 版本 3.12.0 -> 3.13.0 +* `优化` 升级 Glide 版本 4.15.1 -> 4.16.0 +* `优化` 升级 Android Analytics 版本 14.3.0 -> 14.4.0 +* `优化` 升级 Androidx WebKit 版本 1.7.0 -> 1.8.0 +* `优化` 升级 Androidx Preference 版本 1.2.0 -> 1.2.1 +* `优化` 升级 Androidx Annotation 版本 1.6.0 -> 1.7.0 +* `优化` 升级 Androidx Recyclerview 版本 1.3.0 -> 1.3.1 # v6.3.3 @@ -62,7 +95,7 @@ * `优化` 服务端模式开启后保持常开状态 (除非手动关闭或应用进程结束) _[`issue #64`](http://issues.autojs6.com/64#issuecomment-1596990158)_ * `优化` 实现 AutoJs6 与 VSCode 插件的双向版本检测并提示异常检测结果 _[`issue #89`](http://issues.autojs6.com/89)_ * `优化` 增加短信数据读取权限 (android.permission.READ_SMS) (默认关闭) -* `优化` findMultiColors 方法内部实现 (by [LYS](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_ +* `优化` findMultiColors 方法内部实现 (by [LYS86](https://github.com/LYS86)) _[`pr #72`](http://pr.autojs6.com/72)_ * `优化` runtime.loadDex/loadJar/load 支持按目录级别加载或同时加载多个文件 * `优化` 升级 Leakcanary 版本 2.11 -> 2.12 * `优化` 升级 Android Analytics 版本 14.2.0 -> 14.3.0 @@ -166,7 +199,7 @@ * `优化` 升级 Zip4j 版本 2.11.2 -> 2.11.5 * `优化` 升级 Junit 版本 5.9.2 -> 5.9.3 * `优化` 升级 Androidx Annotation 版本 1.5.0 -> 1.6.0 -* `优化` 升级 Jackson Databind 版本 2.14.1 -> 2.14.2 +* `优化` 升级 Jackson DataBind 版本 2.14.1 -> 2.14.2 * `优化` 升级 Desugar JDK Libs 版本 2.0.0 -> 2.0.3 # v6.2.0 diff --git a/app/src/main/assets/docs/qa.html b/app/src/main/assets/docs/qa.html index 224798da..40ca7787 100644 --- a/app/src/main/assets/docs/qa.html +++ b/app/src/main/assets/docs/qa.html @@ -278,7 +278,7 @@

OCR 特性#

AutoJs6 的 OCR 特性是基于 Google ML Kit文字识别 API 实现的.

-

注: AutoJs6 的 OCR 实现源码 参考自 TonyJiangWJ开源项目.

+

注: AutoJs6 的 OCR 实现源码 参考自 TonyJiangWJ开源项目.

参阅: 光学字符识别 (OCR) 模块

diff --git a/app/src/main/assets/init.js b/app/src/main/assets/init.js index a23e882f..b34bbfd2 100644 --- a/app/src/main/assets/init.js +++ b/app/src/main/assets/init.js @@ -23,6 +23,7 @@ let Crypto = org.autojs.autojs.core.crypto.Crypto; let Image = org.autojs.autojs.core.image.ImageWrapper; let ColorTable = org.autojs.autojs.core.image.ColorTable; let Canvas = org.autojs.autojs.core.graphics.ScriptCanvas; +let EventEmitter = org.autojs.autojs.core.eventloop.EventEmitter; let UiObject = org.autojs.autojs.core.automator.UiObject; let UiObjectCollection = org.autojs.autojs.core.automator.UiObjectCollection; let ImageWrapper = org.autojs.autojs.core.image.ImageWrapper; @@ -33,6 +34,7 @@ let OkHttpClient = Packages.okhttp3.OkHttpClient; let ScriptInterruptedException = org.autojs.autojs.runtime.exception.ScriptInterruptedException; let ReentrantLock = java.util.concurrent.locks.ReentrantLock; let ScreenMetrics = org.autojs.autojs.runtime.api.ScreenMetrics; +let ScriptRuntime = org.autojs.autojs.runtime.ScriptRuntime; let StandardCharsets = java.nio.charset.StandardCharsets; let WebView = android.webkit.WebView; let WebViewClient = android.webkit.WebViewClient; @@ -93,13 +95,6 @@ let JsWebView = org.autojs.autojs.core.ui.widget.JsWebView; /* Global assignment. */ Object.assign(this, { - io: Packages.io, - okio: Packages.okio, - de: Packages.de, - ezy: Packages.ezy, - kotlin: Packages.kotlin, - okhttp3: Packages.okhttp3, - androidx: Packages.androidx, isNullish(o) { // nullish coalescing operator: ?? return o === null || o === undefined; @@ -358,15 +353,29 @@ Object.assign(this, { __importClass__(typeof clazz === 'string' ? Packages[clazz] : clazz); }); }, - // 重定向 importPackage 使其支持字符串参数. - /** - * @global - */ - importPackage() { - Array.from(arguments).forEach(pkg => { - __importPackage__(typeof pkg === 'string' ? Packages[pkg] : pkg); - }); - }, + // FIXME by SuperMonster003 on Jul 27, 2023. + // ! This makes importPackage() behave abnormally + // ! when calling it in a JavaScript module, + // ! even with "with" scope binding expression. + // ! + // // 重定向 importPackage 使其支持字符串参数. + // /** + // * @global + // */ + // importPackage() { + // let args = Array.from(arguments); + // let expression = + // 'args.forEach((pkg) => {' + '\n' + + // ' __importPackage__(typeof pkg === \'string\' ? Packages[pkg] : pkg);' + '\n' + + // '});'; + // // @ScopeBinding + // // noinspection WithStatementJS + // with (context) { + // return (/* @IIFE */ function () { + // return eval(expression); + // })(); + // } + // }, /** * @global */ @@ -485,7 +494,7 @@ Object.assign(this, { /* ! ocr < images */ /* ! ocr < files */ - [ 'ocr', 'paddle_ocr' ], + [ 'ocr' ], /* Safe to put last regardless of the order, no guarantee ;). */ [ 'floaty', 'storages', 'device', 'recorder', 'toast' ], @@ -498,7 +507,7 @@ Object.assign(this, { bindPrologue() { // 重定向 require 以支持相对路径和 npm 模块. global.Module = require('jvm-npm'); - global.require = id => Module.require(id); + global.require = Module.require; global.i18n.loadAll(); global.i18n.setLocale('default'); diff --git a/app/src/main/assets/modules/__Arrayx__.js b/app/src/main/assets/modules/__Arrayx__.js index e5d7aff7..9c6a3d68 100644 --- a/app/src/main/assets/modules/__Arrayx__.js +++ b/app/src/main/assets/modules/__Arrayx__.js @@ -3,7 +3,7 @@ let { util, plugins } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Arrayx} */ diff --git a/app/src/main/assets/modules/__Mathx__.js b/app/src/main/assets/modules/__Mathx__.js index cbab27ff..0aefebc2 100644 --- a/app/src/main/assets/modules/__Mathx__.js +++ b/app/src/main/assets/modules/__Mathx__.js @@ -3,7 +3,7 @@ let { plugins, Arrayx, Numberx } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Mathx} */ @@ -14,7 +14,7 @@ module.exports = (scriptRuntime, scope) => { * @implements Internal.Mathx */ const MathxCtor = function () { - // Empty interface body. + /* Empty body. */ }; MathxCtor.prototype = { diff --git a/app/src/main/assets/modules/__Numberx__.js b/app/src/main/assets/modules/__Numberx__.js index d94eae65..054db42f 100644 --- a/app/src/main/assets/modules/__Numberx__.js +++ b/app/src/main/assets/modules/__Numberx__.js @@ -3,7 +3,7 @@ let { util, plugins } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Numberx} */ diff --git a/app/src/main/assets/modules/__RootAutomator__.js b/app/src/main/assets/modules/__RootAutomator__.js index a45a6547..a3a57001 100644 --- a/app/src/main/assets/modules/__RootAutomator__.js +++ b/app/src/main/assets/modules/__RootAutomator__.js @@ -3,7 +3,7 @@ let { autojs } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.RootAutomator} */ diff --git a/app/src/main/assets/modules/__app__.js b/app/src/main/assets/modules/__app__.js index 98c0c57d..1b9b7436 100644 --- a/app/src/main/assets/modules/__app__.js +++ b/app/src/main/assets/modules/__app__.js @@ -5,7 +5,7 @@ let { autojs, shell, files, util } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.App} */ @@ -30,7 +30,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.App */ const App = function () { - // Empty interface body. + /* Empty body. */ }; App.prototype = { diff --git a/app/src/main/assets/modules/__autojs__.js b/app/src/main/assets/modules/__autojs__.js index f97bfab8..f7282209 100644 --- a/app/src/main/assets/modules/__autojs__.js +++ b/app/src/main/assets/modules/__autojs__.js @@ -5,7 +5,7 @@ let { util } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Autojs} */ @@ -24,7 +24,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Autojs */ const Autojs = function () { - // Empty interface body. + /* Empty body. */ }; Autojs.prototype = { @@ -54,6 +54,18 @@ module.exports = function (scriptRuntime, scope) { }, R: global.R, name: context.getString(R.strings.app_name), + get rotation() { + return ScreenMetrics.getRotation(); + }, + get orientation() { + return ScreenMetrics.getOrientation(); + }, + isScreenPortrait() { + return ScreenMetrics.isScreenPortrait(); + }, + isScreenLandscape() { + return ScreenMetrics.isScreenLandscape(); + }, isRootAvailable() { return RootUtils.isRootAvailable(); }, diff --git a/app/src/main/assets/modules/__automator__.js b/app/src/main/assets/modules/__automator__.js index 8ecdd496..e85f480a 100644 --- a/app/src/main/assets/modules/__automator__.js +++ b/app/src/main/assets/modules/__automator__.js @@ -5,7 +5,7 @@ let { util } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Automator} */ @@ -136,8 +136,8 @@ module.exports = function (scriptRuntime, scope) { gestureAsync(duration, points) { return rtAutomator.gestureAsync.apply(rtAutomator, [ 0 ].concat(Array.from(arguments))); }, - swipe(x1, y1, x2, y2, delay) { - return rtAutomator.swipe(x1, y1, x2, y2, delay); + swipe(x1, y1, x2, y2, duration) { + return rtAutomator.swipe(x1, y1, x2, y2, duration); }, isServiceRunning() { return rtAutomator.isServiceRunning(); diff --git a/app/src/main/assets/modules/__base64__.js b/app/src/main/assets/modules/__base64__.js index da75cd9c..df0d77ba 100644 --- a/app/src/main/assets/modules/__base64__.js +++ b/app/src/main/assets/modules/__base64__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Base64} */ @@ -11,7 +11,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Base64 */ const Base64Ctor = function () { - // Empty interface body. + /* Empty body. */ }; Base64Ctor.prototype = { diff --git a/app/src/main/assets/modules/__colors__.js b/app/src/main/assets/modules/__colors__.js index 953d84bc..26fa8cec 100644 --- a/app/src/main/assets/modules/__colors__.js +++ b/app/src/main/assets/modules/__colors__.js @@ -5,7 +5,7 @@ let { util } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Colors} */ @@ -63,10 +63,10 @@ module.exports = function (scriptRuntime, scope) { Color.prototype = { constructor: Color, toString() { - return this.digest(); + return this.summary(); }, - digest() { - return colors.digest.apply(colors, [ this.color ].concat(Array.from(arguments))); + summary() { + return colors.summary.apply(colors, [ this.color ].concat(Array.from(arguments))); }, toHex() { return colors.toHex.apply(colors, [ this.color ].concat(Array.from(arguments))); @@ -250,7 +250,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Colors */ const Colors = function () { - // Empty class body. + /* Empty body. */ }; Colors.prototype = { @@ -666,7 +666,7 @@ module.exports = function (scriptRuntime, scope) { let args = [ _.Color ].concat(Array.from(arguments)); return new (Function.prototype.bind.apply(_.Color, args)); }, - digest(color) { + summary(color) { let [ r, g, b, a ] = this.toRgba(color); let niceA = _.toDoubleComponent(a).toFixed(2).replace(/0$/, ''); return `hex(${this.toHex(color)}), rgba(${r},${g},${b}/${niceA}), int(${this.toInt(color)})`; diff --git a/app/src/main/assets/modules/__console__.js b/app/src/main/assets/modules/__console__.js index cc605737..30d2fde2 100644 --- a/app/src/main/assets/modules/__console__.js +++ b/app/src/main/assets/modules/__console__.js @@ -4,7 +4,7 @@ let { files, util, s13n } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Console} */ @@ -27,7 +27,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Console */ const Console = function () { - // Empty interface body. + /* Empty body. */ }; Console.prototype = { diff --git a/app/src/main/assets/modules/__continuation__.js b/app/src/main/assets/modules/__continuation__.js index 4d5e70f9..958242e2 100644 --- a/app/src/main/assets/modules/__continuation__.js +++ b/app/src/main/assets/modules/__continuation__.js @@ -3,7 +3,7 @@ let { engines, global: _global } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Continuation} */ @@ -41,7 +41,13 @@ module.exports = function (scriptRuntime, scope) { */ let result = this.cont.suspend(); if (result.error !== null) { - throw result.error; + // throw result.error; + if (typeof result.error.message === 'string') { + ScriptRuntime.popException(result.error.message); + } else { + ScriptRuntime.popException(String(result.error)); + } + return result.error; } return result.result; }, @@ -64,7 +70,7 @@ module.exports = function (scriptRuntime, scope) { */ const Continuation = function () { return Object.assign(function () { - // Empty interface body. + /* Empty body. */ }, Continuation.prototype); }; @@ -97,7 +103,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Continuation.PromiseExtension */ const PromiseExtension = function () { - // Empty interface body. + /* Empty body. */ }; Object.assign(PromiseExtension.prototype, { diff --git a/app/src/main/assets/modules/__device__.js b/app/src/main/assets/modules/__device__.js index ae54bb17..c654f6af 100644 --- a/app/src/main/assets/modules/__device__.js +++ b/app/src/main/assets/modules/__device__.js @@ -3,12 +3,11 @@ let { util } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Device} */ module.exports = function (scriptRuntime, scope) { - const ScreenMetrics = org.autojs.autojs.runtime.api.ScreenMetrics; const NetworkUtils = org.autojs.autojs.util.NetworkUtils; const DeviceUtils = org.autojs.autojs.util.DeviceUtils; @@ -20,7 +19,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Device */ const Device = function () { - // Empty interface body. + /* Empty body. */ }; Device.prototype = { @@ -32,7 +31,10 @@ module.exports = function (scriptRuntime, scope) { return ScreenMetrics.getDeviceScreenHeight(); }, get rotation() { - return ScreenMetrics.getRotation(); + return rtDevice.getRotation(); + }, + get orientation() { + return rtDevice.getOrientation(); }, get density() { return ScreenMetrics.getDeviceScreenDensity(); @@ -63,6 +65,12 @@ module.exports = function (scriptRuntime, scope) { isScreenOff() { return !rtDevice.isScreenOn(); }, + isScreenPortrait() { + return rtDevice.isScreenPortrait(); + }, + isScreenLandscape() { + return rtDevice.isScreenLandscape(); + }, getIpAddress(useIPv4) { return useIPv4 === undefined ? NetworkUtils.getIpAddress() diff --git a/app/src/main/assets/modules/__dialogs__.js b/app/src/main/assets/modules/__dialogs__.js index 81ddf440..f4f48f56 100644 --- a/app/src/main/assets/modules/__dialogs__.js +++ b/app/src/main/assets/modules/__dialogs__.js @@ -3,7 +3,7 @@ let { colors, threads, ui } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Dialogs} */ @@ -19,7 +19,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Dialogs */ const Dialogs = function () { - // Empty interface body. + /* Empty body. */ }; Dialogs.prototype = { diff --git a/app/src/main/assets/modules/__engines__.js b/app/src/main/assets/modules/__engines__.js index 6b10fdab..e3915820 100644 --- a/app/src/main/assets/modules/__engines__.js +++ b/app/src/main/assets/modules/__engines__.js @@ -3,7 +3,7 @@ let { files } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Engines} */ @@ -18,7 +18,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Engines */ const Engines = function () { - // Empty class body. + /* Empty body. */ }; Engines.prototype = { diff --git a/app/src/main/assets/modules/__events__.js b/app/src/main/assets/modules/__events__.js index 5209120c..e92798d6 100644 --- a/app/src/main/assets/modules/__events__.js +++ b/app/src/main/assets/modules/__events__.js @@ -1,7 +1,7 @@ // noinspection JSUnusedGlobalSymbols /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Events} */ @@ -12,7 +12,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Events */ const Events = function () { - // Empty class body. + /* Empty body. */ }; Events.prototype = { @@ -37,7 +37,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Keys */ const Keys = function () { - // Empty interface body. + /* Empty body. */ }; Keys.prototype = { diff --git a/app/src/main/assets/modules/__files__.js b/app/src/main/assets/modules/__files__.js index 35cda0a0..6e771379 100644 --- a/app/src/main/assets/modules/__files__.js +++ b/app/src/main/assets/modules/__files__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Files} */ @@ -12,7 +12,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Files */ const Files = function () { - // Empty class body. + /* Empty body. */ }; Files.prototype = { diff --git a/app/src/main/assets/modules/__floaty__.js b/app/src/main/assets/modules/__floaty__.js index 267ce504..6233e142 100644 --- a/app/src/main/assets/modules/__floaty__.js +++ b/app/src/main/assets/modules/__floaty__.js @@ -1,5 +1,7 @@ +// noinspection UnnecessaryLocalVariableJS + /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Floaty} */ @@ -12,7 +14,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Floaty */ const Floaty = function () { - // Empty interface body. + /* Empty body. */ }; Floaty.prototype = { @@ -28,6 +30,7 @@ module.exports = function (scriptRuntime, scope) { }, hasPermission: () => rtFloaty.hasPermission(), requestPermission: () => rtFloaty.requestPermission(), + ensurePermission: () => rtFloaty.ensurePermission(), }; return Floaty; diff --git a/app/src/main/assets/modules/__globals__.js b/app/src/main/assets/modules/__globals__.js index 6841a509..d60482ad 100644 --- a/app/src/main/assets/modules/__globals__.js +++ b/app/src/main/assets/modules/__globals__.js @@ -7,8 +7,6 @@ let { autojs, console, device, pickup, ui, s13n, Numberx } = global; /* Here, importClass() is not recommended for intelligent code completion in IDE like WebStorm. */ /* The same is true of destructuring assignment syntax (like `let {Uri} = android.net`). */ -let ScriptRuntime = org.autojs.autojs.runtime.ScriptRuntime; - /** * @param {org.autojs.autojs.runtime.ScriptRuntime} runtime * @param {org.mozilla.javascript.Scriptable | global} scope diff --git a/app/src/main/assets/modules/__http__.js b/app/src/main/assets/modules/__http__.js index 5b7dd10f..d7a2394f 100644 --- a/app/src/main/assets/modules/__http__.js +++ b/app/src/main/assets/modules/__http__.js @@ -5,7 +5,7 @@ let { ui } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Http} */ @@ -26,7 +26,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Http */ const Http = function () { - // Empty interface body. + /* Empty body. */ }; Http.prototype = { diff --git a/app/src/main/assets/modules/__i18n__.js b/app/src/main/assets/modules/__i18n__.js index 8550a744..c7edd6a6 100644 --- a/app/src/main/assets/modules/__i18n__.js +++ b/app/src/main/assets/modules/__i18n__.js @@ -5,7 +5,7 @@ let { files } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.I18n} */ diff --git a/app/src/main/assets/modules/__images__.js b/app/src/main/assets/modules/__images__.js index c93f8050..4dcb8119 100644 --- a/app/src/main/assets/modules/__images__.js +++ b/app/src/main/assets/modules/__images__.js @@ -5,7 +5,7 @@ let { files, util, ui, threads } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Images} */ @@ -42,7 +42,7 @@ module.exports = function (scriptRuntime, scope) { */ const Images = function () { return Object.assign(function () { - // Empty interface body. + /* Empty body. */ }, Images.prototype); }; @@ -577,19 +577,23 @@ module.exports = function (scriptRuntime, scope) { }; /** + * @class * @extends Images.MatchingResult + * @param {Images.TemplateMatch[] | java.lang.Iterable} list */ const MatchingResult = function (list) { - Object.defineProperties(this, { - matches: { - value: Array.isArray(list) ? list : scriptRuntime.bridges.getBridges().toArray(list), - enumerable: true, - }, - points: { - get() { - return this.matches.map(m => m.point); - }, - enumerable: false, + Object.assign(this, { + matches: (() => { + if (Array.isArray(list)) { + return list; + } + // noinspection UnnecessaryLocalVariableJS + /** @type Images.TemplateMatch[] */ + let results = scriptRuntime.bridges.getBridges().toArray(list); + return results; + })(), + get points() { + return this.matches.map(m => m.point); }, }); }; @@ -606,6 +610,9 @@ module.exports = function (scriptRuntime, scope) { if (!this.matches.length) { return null; } + /** + * @type Images.TemplateMatch + */ let target = this.matches[0]; this.matches.forEach(m => target = compareFn(target, m) > 0 ? m : target); return target; diff --git a/app/src/main/assets/modules/__media__.js b/app/src/main/assets/modules/__media__.js index d57c2be1..1a6b1b58 100644 --- a/app/src/main/assets/modules/__media__.js +++ b/app/src/main/assets/modules/__media__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Media} */ @@ -10,7 +10,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Media */ const Media = function () { - // Empty interface body. + /* Empty body. */ }; Media.prototype = { diff --git a/app/src/main/assets/modules/__notice__.js b/app/src/main/assets/modules/__notice__.js index d5adcb91..5692bfed 100644 --- a/app/src/main/assets/modules/__notice__.js +++ b/app/src/main/assets/modules/__notice__.js @@ -1,7 +1,7 @@ // noinspection JSUnusedLocalSymbols,UnnecessaryLocalVariableJS /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Notice} */ @@ -47,7 +47,7 @@ module.exports = function (scriptRuntime, scope) { * @implements {Notice.Config} */ const Config = function () { - // Empty interface body. + /* Empty body. */ }; Config.prototype = { @@ -86,7 +86,7 @@ module.exports = function (scriptRuntime, scope) { * @implements {Notice.Channel} */ const Channel = function () { - // Empty interface body. + /* Empty body. */ }; Channel.prototype = { diff --git a/app/src/main/assets/modules/__ocr__.js b/app/src/main/assets/modules/__ocr__.js index 8ed65e20..6e405ca1 100644 --- a/app/src/main/assets/modules/__ocr__.js +++ b/app/src/main/assets/modules/__ocr__.js @@ -1,95 +1,228 @@ -// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols +// noinspection JSUnusedLocalSymbols /* Overwritten protection. */ -let { images, files } = global; +let { images } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Ocr} */ module.exports = function (scriptRuntime, scope) { - const rtOcr = scriptRuntime.mlKitOCR; - let _ = { OcrCtor: (/* @IIFE */ () => { + + /** + * @type {{[modeName in Internal.Ocr.ModeName]: Internal.Ocr.ModeName}} + */ + const modeName = { + mlkit: 'mlkit', + paddle: 'paddle', + unknown: 'unknown', + }; + + /** + * @type {Internal.Ocr.ModeName} + */ + let _mode = modeName.unknown; + + const defaultPrivateDescriptor = { + configurable: false, + writable: false, + enumerable: false, + }; + + const defaultProtectedDescriptor = { + configurable: false, + writable: false, + enumerable: true, /* Emphasis. */ + }; + + const instanceFunc = function (img, options) { + if (typeof arguments[0] === 'string') { + let img = images.read(/* path = */ arguments[0]); + if (img === null) { + throw TypeError(`Invalid image of path "${arguments[0]}" for ocr(img, options?)`); + } + return OcrCtor.prototype.recognizeText(img.oneShot(), options); + } + return OcrCtor.prototype.recognizeText.apply(OcrCtor.prototype, arguments); + }; + /** * @implements Internal.Ocr */ const OcrCtor = function () { - - /** @global */ - const ocr = function (img, options) { - if (typeof arguments[0] === 'string') { - let img = images.read(/* path = */ arguments[0]); - if (img === null) { - throw TypeError(`Invalid image of path "${arguments[0]}" for ocr(img, options?)`); - } - return OcrCtor.prototype.recognizeText(img.oneShot(), options); - } - return OcrCtor.prototype.recognizeText.apply(OcrCtor.prototype, arguments); - }; - - return Object.assign(ocr, OcrCtor.prototype); + let o = Object.assign(instanceFunc, OcrCtor.prototype); + Object.defineProperties(instanceFunc, Object.getOwnPropertyDescriptors(OcrCtor.prototype)); + return o; }; OcrCtor.prototype = { constructor: OcrCtor, - recognizeText(img, options) { + _modeName: modeName, + _forcibleModeArgIndex: 10, + mlkit: require('ocr-mlkit')(scriptRuntime, scope), + paddle: require('ocr-paddle')(scriptRuntime, scope), + get mode() { + return _mode; + }, + set mode(mode) { + this.tap(mode); + }, + tap(mode) { + if (mode === this.mlkit || String(mode).toLowerCase() === modeName.mlkit) { + _mode = modeName.mlkit; + } else if (mode === this.paddle || String(mode).toLowerCase() === modeName.paddle) { + _mode = modeName.paddle; + } else { + _mode = modeName.unknown; + throw TypeError(`Unknown mode (value: ${mode}, species: ${species(mode)}) for ocr.tap(mode)`); + } + }, + recognizeText() { if (typeof arguments[0] === 'string') { + + // @Signature + // recognizeText(imgPath: string, options?: DetectOptionsMLKit | DetectOptionsPaddle): string[]; + // recognizeText(imgPath: string, region: Images.Options.Region): string[]; + let img = images.read(/* path = */ arguments[0]); if (img === null) { throw TypeError(`Invalid image of path "${arguments[0]}" for ocr.recognizeText(img, options?)`); } - return this.recognizeText(img.oneShot(), options); + // @Overload + // recognizeText(img: ImageWrapper, options?: DetectOptionsMLKit | DetectOptionsPaddle): string[]; + // recognizeText(img: ImageWrapper, region: Images.Options.Region): string[]; + return this.recognizeText(img.oneShot(), arguments[1]); } if (_.shouldTakenAsRegion(arguments[1])) { + + // @Signature recognizeText(img: ImageWrapper, region: Images.Options.Region): string[]; + + // @Overload recognizeText(img: ImageWrapper, options: DetectOptionsMLKit | DetectOptionsPaddle): string[]; return this.recognizeText(img, { region: arguments[1] }); } - let opt = options || {}; + + // @Signature recognizeText(img: ImageWrapper, options?: DetectOptionsMLKit | DetectOptionsPaddle): string[]; + + /** @type {Internal.Ocr.DetectOptions} */ + let opt = /* options */ arguments[1] || {}; let region = opt.region; if (region === null) { return []; } - if (region === undefined) { - return Array.from(rtOcr.recognizeText(img)); + + /** @type Internal.Ocr.RecognizeTextMethod */ + let recognizeTextMethod; + + switch (arguments[OcrCtor.prototype._forcibleModeArgIndex] || _mode) { + case modeName.mlkit: + recognizeTextMethod = this.mlkit.recognizeTextMethodCreator(opt); + break; + case modeName.paddle: + recognizeTextMethod = this.paddle.recognizeTextMethodCreator(opt); + break; + default: + throw TypeError(`Can't call ocr.recognizeText with an unknown mode (value: ${_mode}, species: ${species(_mode)})`); } - let results = Array.from(rtOcr.recognizeText(images.clip(img, region).oneShot())); + + let img = arguments[0]; + if (region === undefined) { + return recognizeTextMethod(img); + } + let results = recognizeTextMethod( + images.clip(img, region).oneShot(), + ); img.shoot(); return results; }, - detect(img, options) { + detect() { if (typeof arguments[0] === 'string') { + + // @Signature + // detect(imgPath: string, options?: DetectOptionsMLKit | DetectOptionsPaddle): org.autojs.autojs.runtime.api.OcrResult[]; + // detect(imgPath: string, region: Images.Options.Region): org.autojs.autojs.runtime.api.OcrResult[]; + let img = images.read(/* path = */ arguments[0]); if (img === null) { throw TypeError(`Invalid image of path "${arguments[0]}" for ocr.detect(img, options?)`); } - return this.detect(img.oneShot(), options); + // @Overload + // detect(img: ImageWrapper, options?: DetectOptionsMLKit | DetectOptionsPaddle): org.autojs.autojs.runtime.api.OcrResult[]; + // detect(img: ImageWrapper, region: Images.Options.Region): org.autojs.autojs.runtime.api.OcrResult[]; + return this.detect(img.oneShot(), arguments[1]); } if (_.shouldTakenAsRegion(arguments[1])) { + + // @Signature detect(img: ImageWrapper, region: Images.Options.Region): org.autojs.autojs.runtime.api.OcrResult[]; + + // @Overload detect(img: ImageWrapper, options: DetectOptionsMLKit | DetectOptionsPaddle): org.autojs.autojs.runtime.api.OcrResult[]; return this.detect(img, { region: arguments[1] }); } - let opt = options || {}; + + // @Signature detect(img: ImageWrapper, options?: DetectOptionsMLKit | DetectOptionsPaddle): org.autojs.autojs.runtime.api.OcrResult[]; + + let opt = /* options */ arguments[1] || {}; let region = opt.region; if (region === null) { return []; } - /** - * @type {org.autojs.autojs.runtime.api.OcrResult[]} - */ - let resultList = rtOcr.detect(region !== undefined ? images.clip(img, region).oneShot() : img).toArray(); - if (!isNullish(region)) { - resultList.forEach((result) => { - let rect = images.buildRegion(img, region); - result.bounds.offset(rect.x, rect.y); - }); + + /** type {Internal.Ocr.DetectMethod} */ + let detectMethod; + + switch (_mode) { + case modeName.mlkit: + detectMethod = this.mlkit.detectMethodCreator(opt); + break; + case modeName.paddle: + detectMethod = this.paddle.detectMethodCreator(opt); + break; + default: + throw TypeError(`Can't call ocr.detect with an unknown mode (value: ${_mode}, species: ${species(_mode)})`); } - return Array.from(resultList); + + let img = arguments[0]; + if (region === undefined) { + return detectMethod(img); + } + let results = detectMethod(images.clip(img, region).oneShot()); + results.forEach((result) => { + let rect = images.buildRegion(img, region); + result.bounds.offset(rect.x, rect.y); + }); + img.shoot(); + return results; + }, + summary() { + return [ + `[ OCR summary ]`, + `Current mode: ${this.mode}`, + `Available modes: [ ${Object.values(modeName).filter(n => n !== modeName.unknown).join(', ')} ]`, + ].join('\n'); + }, + toString() { + return this.summary(); }, }; + /* Set prototype descriptors. */ + + Object.defineProperties(OcrCtor.prototype, { + _modeName: defaultPrivateDescriptor, + _forcibleModeArgIndex: defaultPrivateDescriptor, + mode: { configurable: false }, + constructor: defaultProtectedDescriptor, + mlkit: defaultProtectedDescriptor, + paddle: defaultProtectedDescriptor, + tap: defaultProtectedDescriptor, + summary: defaultProtectedDescriptor, + toString: defaultProtectedDescriptor, + }); + return OcrCtor; })(), shouldTakenAsRegion(o) { @@ -104,5 +237,8 @@ module.exports = function (scriptRuntime, scope) { */ const ocr = new _.OcrCtor(); + /* Init OCR mode with MLKit. */ + ocr.mode = 'mlkit'; + return ocr; }; \ No newline at end of file diff --git a/app/src/main/assets/modules/__paddle_ocr__.js b/app/src/main/assets/modules/__paddle_ocr__.js deleted file mode 100644 index bab33b74..00000000 --- a/app/src/main/assets/modules/__paddle_ocr__.js +++ /dev/null @@ -1,120 +0,0 @@ -// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols - -/* Overwritten protection. */ - -let { images } = global; - -/** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime - * @param {org.mozilla.javascript.Scriptable | global} scope - * @return {Internal.Ocr} - */ -module.exports = function (scriptRuntime, scope) { - - const rtOcr = scriptRuntime.paddleOCR; - - let _ = { - OcrCtor: (/* @IIFE */ () => { - /** - * @implements Internal.Ocr - */ - const OcrCtor = function () { - - /** @global */ - const ocr = function (img, options) { - if (typeof arguments[0] === 'string') { - let img = images.read(/* path = */ arguments[0]); - if (img === null) { - throw TypeError(`Invalid image of path "${arguments[0]}" for ocr(img, options?)`); - } - return OcrCtor.prototype.recognizeText(img.oneShot(), options); - } - return OcrCtor.prototype.recognizeText.apply(OcrCtor.prototype, arguments); - }; - - return Object.assign(ocr, OcrCtor.prototype); - }; - - OcrCtor.prototype = { - constructor: OcrCtor, - recognizeText(img, options) { - if (typeof arguments[0] === 'string') { - let img = images.read(/* path = */ arguments[0]); - if (img === null) { - throw TypeError(`Invalid image of path "${arguments[0]}" for ocr.recognizeText(img, options?)`); - } - return this.recognizeText(img.oneShot(), options); - } - if (_.shouldTakenAsRegion(arguments[1])) { - return this.recognizeText(img, { region: arguments[1] }); - } - let opt = options || {}; - let region = opt.region; - if (region === null) { - return []; - } - let cpuThreadNum = opt.cpuThreadNum || 4 - let useSlim = opt.useSlim - if (useSlim === undefined) { - // 默认使用轻量化模型 - useSlim = true - } - if (region === undefined) { - return Array.from(rtOcr.recognizeText(img, cpuThreadNum, useSlim)); - } - let results = Array.from(rtOcr.recognizeText(images.clip(img, region).oneShot())); - img.shoot(); - return results; - }, - detect(img, options) { - if (typeof arguments[0] === 'string') { - let img = images.read(/* path = */ arguments[0]); - if (img === null) { - throw TypeError(`Invalid image of path "${arguments[0]}" for ocr.detect(img, options?)`); - } - return this.detect(img.oneShot(), options); - } - if (_.shouldTakenAsRegion(arguments[1])) { - return this.detect(img, { region: arguments[1] }); - } - let opt = options || {}; - let region = opt.region; - if (region === null) { - return []; - } - let cpuThreadNum = opt.cpuThreadNum || 4 - let useSlim = opt.useSlim - if (useSlim === undefined) { - // 默认使用轻量化模型 - useSlim = true - } - /** - * @type {org.autojs.autojs.runtime.api.OcrResult[]} - */ - let resultList = rtOcr.detect(region !== undefined ? images.clip(img, region).oneShot() : img, cpuThreadNum, useSlim).toArray(); - if (!isNullish(region)) { - resultList.forEach((result) => { - let rect = images.buildRegion(img, region); - result.bounds.offset(rect.x, rect.y); - }); - } - return Array.from(resultList); - }, - }; - - return OcrCtor; - })(), - shouldTakenAsRegion(o) { - return o instanceof org.opencv.core.Rect - || o instanceof android.graphics.Rect - || Array.isArray(o); - }, - }; - - /** - * @type {Internal.Ocr} - */ - const ocr = new _.OcrCtor(); - - return ocr; -}; \ No newline at end of file diff --git a/app/src/main/assets/modules/__plugins__.js b/app/src/main/assets/modules/__plugins__.js index 8a578e26..c7ebb18a 100644 --- a/app/src/main/assets/modules/__plugins__.js +++ b/app/src/main/assets/modules/__plugins__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Plugins} */ @@ -10,7 +10,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Plugins */ const Plugins = function () { - // Empty interface body. + /* Empty body. */ }; Plugins.prototype = { diff --git a/app/src/main/assets/modules/__recorder__.js b/app/src/main/assets/modules/__recorder__.js index 7e415f2a..2a8b03c9 100644 --- a/app/src/main/assets/modules/__recorder__.js +++ b/app/src/main/assets/modules/__recorder__.js @@ -1,7 +1,7 @@ // noinspection JSUnusedGlobalSymbols /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Recorder} */ diff --git a/app/src/main/assets/modules/__s13n__.js b/app/src/main/assets/modules/__s13n__.js index 3da2a93a..af216d83 100644 --- a/app/src/main/assets/modules/__s13n__.js +++ b/app/src/main/assets/modules/__s13n__.js @@ -1,7 +1,7 @@ // noinspection JSUnusedLocalSymbols,UnnecessaryLocalVariableJS /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.S13n} */ @@ -12,7 +12,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.S13n */ const S13n = function () { - // Empty interface body. + /* Empty body. */ }; S13n.prototype = { diff --git a/app/src/main/assets/modules/__selector__.js b/app/src/main/assets/modules/__selector__.js index 300c97d2..0e4ee458 100644 --- a/app/src/main/assets/modules/__selector__.js +++ b/app/src/main/assets/modules/__selector__.js @@ -5,7 +5,7 @@ let { i18n } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {() => Internal.Selector} */ diff --git a/app/src/main/assets/modules/__sensors__.js b/app/src/main/assets/modules/__sensors__.js index 3ecb3c8d..b2781c3c 100644 --- a/app/src/main/assets/modules/__sensors__.js +++ b/app/src/main/assets/modules/__sensors__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Sensors} */ @@ -10,7 +10,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.Sensors */ const Sensors = function () { - // Empty interface body. + /* Empty body. */ }; Sensors.prototype = { diff --git a/app/src/main/assets/modules/__shell__.js b/app/src/main/assets/modules/__shell__.js index 8660c724..7062a85f 100644 --- a/app/src/main/assets/modules/__shell__.js +++ b/app/src/main/assets/modules/__shell__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Shell} */ diff --git a/app/src/main/assets/modules/__storages__.js b/app/src/main/assets/modules/__storages__.js index bc6ff836..17fdec0b 100644 --- a/app/src/main/assets/modules/__storages__.js +++ b/app/src/main/assets/modules/__storages__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Storages} */ @@ -47,7 +47,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Storages */ const Storage = function () { - // Empty interface body. + /* Empty body. */ }; Storage.prototype = { diff --git a/app/src/main/assets/modules/__tasks__.js b/app/src/main/assets/modules/__tasks__.js index eb6fe7ae..34e9444c 100644 --- a/app/src/main/assets/modules/__tasks__.js +++ b/app/src/main/assets/modules/__tasks__.js @@ -5,7 +5,7 @@ let { ui, threads, files } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Tasks} */ @@ -22,7 +22,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Tasks */ let Task = function () { - // Empty interface body. + /* Empty body. */ }; Task.prototype = { diff --git a/app/src/main/assets/modules/__threads__.js b/app/src/main/assets/modules/__threads__.js index 454beda3..0bdc065a 100644 --- a/app/src/main/assets/modules/__threads__.js +++ b/app/src/main/assets/modules/__threads__.js @@ -1,7 +1,7 @@ // noinspection NpmUsedModulesInstalled /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Threads} */ @@ -18,7 +18,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Threads */ const Threads = function () { - // Empty interface body. + /* Empty body. */ }; Threads.prototype = { @@ -73,7 +73,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Threads.PromiseExtension */ const PromiseExtension = function () { - // Empty interface body. + /* Empty body. */ }; Object.assign(PromiseExtension.prototype, { diff --git a/app/src/main/assets/modules/__timers__.js b/app/src/main/assets/modules/__timers__.js index e4bc6c37..e2801160 100644 --- a/app/src/main/assets/modules/__timers__.js +++ b/app/src/main/assets/modules/__timers__.js @@ -1,5 +1,5 @@ /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Timers} */ @@ -11,7 +11,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Timers */ const Timers = function () { - // Empty interface body. + /* Empty body. */ }; Timers.prototype = { diff --git a/app/src/main/assets/modules/__toast__.js b/app/src/main/assets/modules/__toast__.js index 273477c2..9d6d3dd8 100644 --- a/app/src/main/assets/modules/__toast__.js +++ b/app/src/main/assets/modules/__toast__.js @@ -1,86 +1,26 @@ +// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols + /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Toast} */ module.exports = function (scriptRuntime, scope) { + let rtToast = scriptRuntime.toast; let _ = { - uiHandler: runtime.getUiHandler(), - toasts: { - /** - * @type {Set} - */ - pool: new Set(), - lock: new ReentrantLock(), - add(t) { - if (t instanceof Toast) { - this.lock.lock(); - this.pool.add(t); - this.addCallback(t); - this.lock.unlock(); - } - }, - /** - * @param {android.widget.Toast} t - */ - addCallback(t) { - _.uiHandler.postDelayed(new java.lang.Runnable({ - run: () => _.toasts.remove(t), - }), this.getDuration(t) + 1e3 /* As toast may show with some delay. */); - }, - remove(t) { - if (this.pool.has(t)) { - this.lock.lock(); - this.pool.delete(t); - this.lock.unlock(); - } - }, - dismissAll() { - if (this.pool.size > 0) { - this.lock.lock(); - this.pool.forEach((t) => t.cancel()); - this.pool.clear(); - this.lock.unlock(); - } - }, - getDuration(t) { - let du = { - SHORT_DELAY: 2e3, - LONG_DELAY: 3.5e3, - }; - switch (t.getDuration()) { - case Toast.LENGTH_SHORT: - return du.SHORT_DELAY; - case Toast.LENGTH_LONG: - return du.LONG_DELAY; - default: - return Math.max.apply(null, Object.values(du)); - } - }, - }, ToastCtor: (/* @IIFE */ () => { /** * @implements Internal.Toast */ - const ToastCtor = function () { - // @Caution by SuperMonster003 on Oct 11, 2022. - // ! android.widget.Toast.makeText() doesn't work well on Android API Level 28 (Android 9) [P]. - // ! There hasn't been a solution for this so far. - // ! Tested devices: - // ! 1. SONY XPERIA XZ1 Compact (G8441) - // ! 2. Android Studio AVD (Android 9.0 x86) + const Ctor = function () { /** @global */ - const toast = function (msg, isLong, isForcible) { + const instance = function (msg, isLong, isForcible) { let $ = { - rex: { + badge: { long: /^l(ong)?$/i, short: /^s(hort)?$/i, forcible: /^f(orcible)?$/i, }, - toast() { - this.init(arguments); - this.show(); - }, init() { this.message = isNullish(msg) ? '' : String(msg); this.isForcible = this.parseIsForcible(isForcible); @@ -94,13 +34,13 @@ module.exports = function (scriptRuntime, scope) { return Boolean(isLong); } if (typeof isLong === 'string') { - if (this.rex.long.test(isLong)) { + if (this.badge.long.test(isLong)) { return true; } - if (this.rex.short.test(isLong)) { + if (this.badge.short.test(isLong)) { return false; } - if (this.rex.forcible.test(isLong)) { + if (this.badge.forcible.test(isLong)) { this.isForcible = true; return false; } @@ -116,42 +56,31 @@ module.exports = function (scriptRuntime, scope) { return Boolean(isForcible); } if (typeof isForcible === 'string') { - if (this.rex.forcible.test(isForcible)) { + if (this.badge.forcible.test(isForcible)) { return true; } throw Error(`Invalid param: {name: isForcible, value: ${isForcible}, type: ${species(isForcible)}.`); } return false; }, - show() { - _.uiHandler.post(() => { - if ($.isForcible) { - _.toasts.dismissAll(); - } - let len = $.isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT; - let o = Toast.makeText(_.uiHandler.getContext(), $.message, len); - _.toasts.add(o); - o.show(); - }); + makeToast() { + rtToast.makeToast(this.message, this.isLong, this.isForcible); }, }; - $.toast(); + $.init(); + $.makeToast(); }; - return Object.assign(toast, ToastCtor.prototype); + return Object.assign(instance, Ctor.prototype); }; - ToastCtor.prototype = { - constructor: ToastCtor, - dismissAll() { - _.uiHandler.post(() => { - _.toasts.dismissAll(); - }); - }, + Ctor.prototype = { + constructor: Ctor, + dismissAll: () => rtToast.dismissAll(), }; - return ToastCtor; + return Ctor; })(), }; diff --git a/app/src/main/assets/modules/__ui__.js b/app/src/main/assets/modules/__ui__.js index f00e25f5..ff284d99 100644 --- a/app/src/main/assets/modules/__ui__.js +++ b/app/src/main/assets/modules/__ui__.js @@ -5,7 +5,7 @@ let { files } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.UI} */ @@ -54,7 +54,7 @@ module.exports = function (scriptRuntime, scope) { * @extends Internal.UI */ const UI = function () { - // Empty class body. + /* Empty body. */ }; UI.prototype = { @@ -67,7 +67,7 @@ module.exports = function (scriptRuntime, scope) { let Widget = function () { this.__attrs__ = {}; return Object.assign(function () { - // Empty interface body. + /* Empty body. */ }, Widget.prototype); }; diff --git a/app/src/main/assets/modules/__util__.js b/app/src/main/assets/modules/__util__.js index d3efaf18..79c1995d 100644 --- a/app/src/main/assets/modules/__util__.js +++ b/app/src/main/assets/modules/__util__.js @@ -22,7 +22,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Util} */ @@ -38,7 +38,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Util */ const Util = function () { - // Empty interface body. + /* Empty body. */ }; Util.prototype = { diff --git a/app/src/main/assets/modules/__web__.js b/app/src/main/assets/modules/__web__.js index 6d685e2d..d1ca4226 100644 --- a/app/src/main/assets/modules/__web__.js +++ b/app/src/main/assets/modules/__web__.js @@ -5,7 +5,7 @@ let { http } = global; /** - * @param {org.autojs.autojs.runtime.ScriptRuntime} scriptRuntime + * @param {ScriptRuntime} scriptRuntime * @param {org.mozilla.javascript.Scriptable | global} scope * @return {Internal.Web} */ @@ -20,7 +20,7 @@ module.exports = function (scriptRuntime, scope) { * @implements Internal.Web */ const Web = function () { - // Empty interface body. + /* Empty body. */ }; Web.prototype = { @@ -43,7 +43,7 @@ module.exports = function (scriptRuntime, scope) { return new InjectableWebClient(Context.getCurrentContext(), scope); }, newWebSocket(url) { - return new org.autojs.autojs.core.web.WebSocket(http.__okhttp__, url); + return new scope.WebSocket(url); }, }; @@ -55,6 +55,33 @@ module.exports = function (scriptRuntime, scope) { */ let methods = [ 'newInjectableWebView', 'newInjectableWebClient', 'newWebSocket' ]; __asGlobal__(web, methods, scope); + + const WebSocket = function () { + if (!(this instanceof WebSocket)) { + throw TypeError('WebSocket must be called as constructor'); + } + if (arguments[0] instanceof MutableOkHttp) { + this.client = arguments[0]; + this.url = arguments[1]; + } else { + this.client = http.__okhttp__; + this.url = arguments[0]; + } + if (typeof this.url !== 'string') { + throw TypeError(`Invalid url (value: ${this.url}, species: ${species(this.url)}) for WebSocket`); + } + return new org.autojs.autojs.core.web.WebSocket(this.client, this.url); + }; + + /* Append all primitive static fields. */ + Object.keys(org.autojs.autojs.core.web.WebSocket).forEach((key) => { + let value = org.autojs.autojs.core.web.WebSocket[key] + if (isPrimitive(value)) { + WebSocket[key] = value; + } + }); + + scope.WebSocket = WebSocket; }, }; diff --git a/app/src/main/assets/modules/ocr-mlkit.js b/app/src/main/assets/modules/ocr-mlkit.js new file mode 100644 index 00000000..f6319d4b --- /dev/null +++ b/app/src/main/assets/modules/ocr-mlkit.js @@ -0,0 +1,50 @@ +// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols + +/** + * @param {ScriptRuntime} scriptRuntime + * @param {org.mozilla.javascript.Scriptable | global} scope + * @return {Internal.OcrMLKit} + */ +module.exports = function (scriptRuntime, scope) { + + const rtOcrMLKit = scriptRuntime.ocrMLKit; + + let _ = { + OcrMLKitCtor: (/* @IIFE */ () => { + /** + * @implements Internal.OcrMLKit + */ + const OcrMLKitCtor = function () { + return Object.assign(function () { + let argArray = Array.from(arguments); + /** @type {Internal.Ocr.ModeName} */ + argArray[ocr['_forcibleModeArgIndex']] = ocr['_modeName']['mlkit']; + return ocr.recognizeText.apply(ocr, argArray); + }, OcrMLKitCtor.prototype); + }; + + OcrMLKitCtor.prototype = { + constructor: OcrMLKitCtor, + recognizeTextMethodCreator(options) { + return function (img) { + return Array.from(rtOcrMLKit.recognizeText(img)); + }; + }, + detectMethodCreator(options) { + return function (img) { + return Array.from(rtOcrMLKit.detect(img).toArray()); + }; + }, + }; + + return OcrMLKitCtor; + })(), + }; + + /** + * @type {Internal.OcrMLKit} + */ + const ocrMLKit = new _.OcrMLKitCtor(); + + return ocrMLKit; +}; \ No newline at end of file diff --git a/app/src/main/assets/modules/ocr-paddle.js b/app/src/main/assets/modules/ocr-paddle.js new file mode 100644 index 00000000..b9b75d11 --- /dev/null +++ b/app/src/main/assets/modules/ocr-paddle.js @@ -0,0 +1,68 @@ +// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols + +/** + * @param {ScriptRuntime} scriptRuntime + * @param {org.mozilla.javascript.Scriptable | global} scope + * @return {Internal.OcrPaddle} + */ +module.exports = function (scriptRuntime, scope) { + + const rtOcrPaddle = scriptRuntime.ocrPaddle; + + let _ = { + OcrPaddleCtor: (/* @IIFE */ () => { + /** + * @implements Internal.OcrPaddle + */ + const OcrPaddleCtor = function () { + return Object.assign(function () { + let argArray = Array.from(arguments); + /** @type {Internal.Ocr.ModeName} */ + argArray[ocr['_forcibleModeArgIndex']] = ocr['_modeName']['paddle']; + return ocr.recognizeText.apply(ocr, argArray); + }, OcrPaddleCtor.prototype); + }; + + OcrPaddleCtor.prototype = { + constructor: OcrPaddleCtor, + recognizeTextMethodCreator(options) { + return function (img) { + const { cpuThreadNum, useSlim } = options; + + let niceUseSlim = useSlim === undefined ? true : Boolean(useSlim); + let niceCpuThreadNum = cpuThreadNum || 4; + + return Array.from(rtOcrPaddle.recognizeText( + img, + niceCpuThreadNum, + niceUseSlim, + )); + }; + }, + detectMethodCreator(options) { + return function (img) { + const { cpuThreadNum, useSlim } = options; + + let niceUseSlim = useSlim === undefined ? true : Boolean(useSlim); + let niceCpuThreadNum = cpuThreadNum || 4; + + return Array.from(rtOcrPaddle.detect( + img, + niceCpuThreadNum, + niceUseSlim, + ).toArray()); + }; + }, + }; + + return OcrPaddleCtor; + })(), + }; + + /** + * @type {Internal.OcrPaddle} + */ + const ocrPaddle = new _.OcrPaddleCtor(); + + return ocrPaddle; +}; \ 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..f3ee1e04 --- /dev/null +++ b/app/src/main/assets/sample/事件与监听/音量减键分析布局.js @@ -0,0 +1,7 @@ +auto(); +events.observeKey(); +events.setKeyInterceptionEnabled(true); +events.on('volume_down', () => { + app.sendBroadcast('bounds'); + exit(); +}); \ No newline at end of file diff --git a/app/src/main/assets/sample/对话框/简单计算器.js b/app/src/main/assets/sample/对话框/简单计算器.js index c5557263..597bb4e0 100644 --- a/app/src/main/assets/sample/对话框/简单计算器.js +++ b/app/src/main/assets/sample/对话框/简单计算器.js @@ -1,22 +1,24 @@ -var num1 = dialogs.input("请输入第一个数字"); -var op = dialogs.singleChoice("请选择运算", ["加", "减", "乘", "除", "幂"]); -var num2 = dialogs.input("请输入第二个数字"); -var result = 0; -switch(op){ -case 0: - result = num1 + num2; - break; -case 1: - result = num1 - num2; - break; -case 2: - result = num1 * num2; - break; -case 3: - result = num1 / num2; - break; -case 4: - result = Math.pow(num1, num2); - break; -} -alert("运算结果", result); \ No newline at end of file +(/* @IIFE */ function () { + var num1 = dialogs.input('请输入第一个数字'); + var op = dialogs.singleChoice('请选择运算', [ '加', '减', '乘', '除', '幂' ]); + var num2 = dialogs.input('请输入第二个数字'); + var result = 0; + switch (op) { + case 0: + result = num1 + num2; + break; + case 1: + result = num1 - num2; + break; + case 2: + result = num1 * num2; + break; + case 3: + result = num1 / num2; + break; + case 4: + result = Math.pow(num1, num2); + break; + } + alert('运算结果', result); +})(); diff --git a/app/src/main/assets/sample/无障碍服务/三指下滑.js b/app/src/main/assets/sample/无障碍服务/三指下滑.js index dee91eb3..a51651d9 100644 --- a/app/src/main/assets/sample/无障碍服务/三指下滑.js +++ b/app/src/main/assets/sample/无障碍服务/三指下滑.js @@ -8,7 +8,7 @@ * 每一个的时长都为350毫秒 */ -gestures([350, [300, 400], [300, 1400]], +gestures([0, 350, [300, 400], [300, 1400]], [350, [600, 400], [600, 1400]], [350, [900, 400], [900, 1400]] ); diff --git a/app/src/main/java/org/autojs/autojs/AbstractAutoJs.kt b/app/src/main/java/org/autojs/autojs/AbstractAutoJs.kt index 459c9ac0..eb2992d5 100644 --- a/app/src/main/java/org/autojs/autojs/AbstractAutoJs.kt +++ b/app/src/main/java/org/autojs/autojs/AbstractAutoJs.kt @@ -50,7 +50,6 @@ abstract class AbstractAutoJs protected constructor(protected val application: A private set val context: Context = application.applicationContext - val appUtils by lazy { createAppUtils(context) } val globalConsole by lazy { createGlobalConsole() } @@ -108,7 +107,7 @@ abstract class AbstractAutoJs protected constructor(protected val application: A application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallbacks() { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - ScreenMetrics.initIfNeeded(activity) + ScreenMetrics.init(activity) appUtils.setCurrentActivity(activity) registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key == StringUtils.key(R.string.key_keep_screen_on_when_in_foreground)) { diff --git a/app/src/main/java/org/autojs/autojs/App.kt b/app/src/main/java/org/autojs/autojs/App.kt index 1e1732e5..d0b6cdd6 100644 --- a/app/src/main/java/org/autojs/autojs/App.kt +++ b/app/src/main/java/org/autojs/autojs/App.kt @@ -1,12 +1,15 @@ package org.autojs.autojs import android.annotation.SuppressLint +import android.app.Application import android.content.Context import android.content.Intent import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build +import android.util.Log import android.view.View import android.widget.ImageView import androidx.localbroadcastmanager.content.LocalBroadcastManager @@ -31,10 +34,13 @@ import org.autojs.autojs.timing.TimedTaskManager import org.autojs.autojs.timing.TimedTaskScheduler import org.autojs.autojs.tool.CrashHandler import org.autojs.autojs.ui.error.ErrorReportActivity +import org.autojs.autojs.ui.floating.FloatyWindowManger import org.autojs.autojs.util.ViewUtils import org.autojs.autojs6.BuildConfig import org.autojs.autojs6.R +import org.lsposed.hiddenapibypass.HiddenApiBypass import java.lang.ref.WeakReference +import java.lang.reflect.Method /** @@ -61,7 +67,7 @@ class App : MultiDexApplication() { setUpDebugEnvironment() AutoJs.initInstance(this) - GlobalKeyObserver.init() + GlobalKeyObserver.initIfNeeded() setupDrawableImageLoader() TimedTaskScheduler.init(this) initDynamicBroadcastReceivers() @@ -73,7 +79,21 @@ class App : MultiDexApplication() { } override fun attachBaseContext(base: Context) { - super.attachBaseContext(localeAppDelegate.attachBaseContext(base)) + // @Caution by SuperMonster003 on Aug 2, 2023. + // ! This will cause AutoJs6 not being aware of + // ! configuration changes (like orientation and so forth). + // super.attachBaseContext(localeAppDelegate.attachBaseContext(base)) + + super.attachBaseContext(base) + + // @Dubious by SuperMonster003 on Aug 2, 2023. + // ! Locale helper may be not work as expected ? + localeAppDelegate.attachBaseContext(base) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + HiddenApiBypass.addHiddenApiExemptions("L"); + } + Log.d("Shizuku", "${App::class.java.getSimpleName()} attachBaseContext | Process=${getProcessNameCompat()}") } private fun setUpStaticsTool() { @@ -216,10 +236,11 @@ class App : MultiDexApplication() { override fun onConfigurationChanged(newConfig: Configuration) { localeAppDelegate.onConfigurationChanged(this) ViewUtils.onConfigurationChanged(newConfig) + FloatyWindowManger.getCircularMenu()?.savePosition(newConfig) super.onConfigurationChanged(newConfig) } - override fun getApplicationContext() = LocaleHelper.onAttach(super.getApplicationContext()) + override fun getApplicationContext() = LocaleHelper.onAttach(super.getApplicationContext()) companion object { @@ -230,6 +251,19 @@ class App : MultiDexApplication() { val app: App get() = instance.get()!! + fun getProcessNameCompat(): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) Application.getProcessName() else { + try { + @SuppressLint("PrivateApi") val activityThread = Class.forName("android.app.ActivityThread") + @SuppressLint("DiscouragedPrivateApi") val method: Method = activityThread.getDeclaredMethod("currentProcessName") + method.invoke(null) as String + } catch (e: ClassNotFoundException) { + e.printStackTrace() + "Unknown process name" + } + } + } + } } diff --git a/app/src/main/java/org/autojs/autojs/AutoJs.kt b/app/src/main/java/org/autojs/autojs/AutoJs.kt index 8d91ff2e..88c9cedd 100644 --- a/app/src/main/java/org/autojs/autojs/AutoJs.kt +++ b/app/src/main/java/org/autojs/autojs/AutoJs.kt @@ -57,12 +57,12 @@ class AutoJs private constructor(private val appContext: Application) : Abstract when { action.equals(LayoutBoundsFloatyWindow::class.java.name, true) -> { capture(object : LayoutInspectFloatyWindow { - override fun create(nodeInfo: NodeInfo?) = LayoutBoundsFloatyWindow(nodeInfo, context) + override fun create(nodeInfo: NodeInfo?) = LayoutBoundsFloatyWindow(nodeInfo, context, true) }) } action.equals(LayoutHierarchyFloatyWindow::class.java.name, true) -> { capture(object : LayoutInspectFloatyWindow { - override fun create(nodeInfo: NodeInfo?) = LayoutHierarchyFloatyWindow(nodeInfo, context) + override fun create(nodeInfo: NodeInfo?) = LayoutHierarchyFloatyWindow(nodeInfo, context, true) }) } } @@ -167,9 +167,11 @@ class AutoJs private constructor(private val appContext: Application) : Abstract /* Broadcasts. */ putProperty(BroadcastShortForm.INSPECT_LAYOUT_BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name) + putProperty(BroadcastShortForm.LAYOUT_BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name) putProperty(BroadcastShortForm.BOUNDS.fullName, LayoutBoundsFloatyWindow::class.java.name) putProperty(BroadcastShortForm.INSPECT_LAYOUT_HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name) + putProperty(BroadcastShortForm.LAYOUT_HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name) putProperty(BroadcastShortForm.HIERARCHY.fullName, LayoutHierarchyFloatyWindow::class.java.name) } @@ -181,6 +183,7 @@ class AutoJs private constructor(private val appContext: Application) : Abstract private set @Synchronized + @JvmStatic fun initInstance(application: Application) { if (!isInitialized) { instance = AutoJs(application) diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/ApkPackager.java b/app/src/main/java/org/autojs/autojs/apkbuilder/ApkPackager.java index a6f268c2..7945514a 100644 --- a/app/src/main/java/org/autojs/autojs/apkbuilder/ApkPackager.java +++ b/app/src/main/java/org/autojs/autojs/apkbuilder/ApkPackager.java @@ -18,7 +18,6 @@ import pxb.android.tinysign.TinySign; /** * Created by Stardust on 2017/10/23. */ - public class ApkPackager { private InputStream mApkInputStream; diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/util/BoolFunction.java b/app/src/main/java/org/autojs/autojs/apkbuilder/util/BoolFunction.java index eb43cb57..d8e65db3 100644 --- a/app/src/main/java/org/autojs/autojs/apkbuilder/util/BoolFunction.java +++ b/app/src/main/java/org/autojs/autojs/apkbuilder/util/BoolFunction.java @@ -3,7 +3,6 @@ package org.autojs.autojs.apkbuilder.util; /** * Created by Stardust on 2017/10/23. */ - public interface BoolFunction { boolean accept(T t); diff --git a/app/src/main/java/org/autojs/autojs/apkbuilder/util/StreamUtils.java b/app/src/main/java/org/autojs/autojs/apkbuilder/util/StreamUtils.java index d4c6b8a3..c4cfc605 100644 --- a/app/src/main/java/org/autojs/autojs/apkbuilder/util/StreamUtils.java +++ b/app/src/main/java/org/autojs/autojs/apkbuilder/util/StreamUtils.java @@ -1,6 +1,5 @@ package org.autojs.autojs.apkbuilder.util; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -9,10 +8,8 @@ import java.io.OutputStream; /** * Created by Stardust on 2017/10/23. */ - public class StreamUtils { - public static void write(InputStream inputStream, OutputStream out) throws IOException { byte[] buffer = new byte[4096]; int len; @@ -30,4 +27,5 @@ public class StreamUtils { } return outputStream.toByteArray(); } + } diff --git a/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt b/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt index 6a14c0eb..3005ad78 100644 --- a/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt +++ b/app/src/main/java/org/autojs/autojs/app/DialogUtils.kt @@ -102,14 +102,14 @@ object DialogUtils { MaterialDialog.Builder(context) .title(context.getString(R.string.dialog_button_more)) .content(contentMore) - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_dismiss) .build() .also { preference.longClickPromptMoreDialogHandler(it) } .show() } } } - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_dismiss) .onPositive { dialog, _ -> dialog.dismiss() } .autoDismiss(false) .build() diff --git a/app/src/main/java/org/autojs/autojs/app/tool/JsonSocketClientTool.kt b/app/src/main/java/org/autojs/autojs/app/tool/JsonSocketClientTool.kt index 8de80b76..0306f177 100644 --- a/app/src/main/java/org/autojs/autojs/app/tool/JsonSocketClientTool.kt +++ b/app/src/main/java/org/autojs/autojs/app/tool/JsonSocketClientTool.kt @@ -55,7 +55,7 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { val host = Pref.getServerAddress() if (isAutoConnect) { devPlugin - .connectToRemoteServer(host, mClientModeItem, true) + .connectToRemoteServer(context, host, mClientModeItem, true) .subscribe(Observers.emptyConsumer(), Observers.emptyConsumer()) return } @@ -96,14 +96,14 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { .show() } } - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .negativeColorRes(R.color.dialog_button_default) .onNegative { dHistories, _ -> dHistories.dismiss() } .autoDismiss(false) .show() .also { DialogUtils.toggleContentViewByItems(it) } } - .negativeText(R.string.text_back) + .negativeText(R.string.text_cancel) .onNegative { dialog, _ -> dialog.dismiss() } .autoDismiss(false) .dismissListener(onConnectionDialogDismissed) @@ -120,15 +120,15 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { dialog.inputEditText!!.filters += InputFilter { source, start, end, dest, dstart, dend -> if (end > start) { val fullText = dest.substring(0, dstart) + - source.subSequence(start, end) + - dest.substring(dend) + source.subSequence(start, end) + + dest.substring(dend) if (dstart > 0) { val prevNearest = dest[dstart - 1] - if (Regex(rexDot).matches(prevNearest.toString()) && Regex(rexDot).matches(source)) { + if (Regex(REGEX_DOT).matches(prevNearest.toString()) && Regex(REGEX_DOT).matches(source)) { showSnack(dialog, R.string.error_repeated_dot_symbol) return@InputFilter "" } - if (Regex(rexColon).matches(prevNearest.toString()) && Regex(rexColon).matches(source)) { + if (Regex(REGEX_COLON).matches(prevNearest.toString()) && Regex(REGEX_COLON).matches(source)) { showSnack(dialog, R.string.error_repeated_colon_symbol) return@InputFilter "" } @@ -141,8 +141,8 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { showSnack(dialog, R.string.error_invalid_ip_address) return@InputFilter "" } - if (!fullText.contains(Regex(rexColon))) { - fullText.split(Regex(rexDot)).dropLastWhile { it.isEmpty() }.forEach { s -> + if (!fullText.contains(Regex(REGEX_COLON))) { + fullText.split(Regex(REGEX_DOT)).dropLastWhile { it.isEmpty() }.forEach { s -> if (s.toIntOrNull()?.let { it <= 255 } != true) { showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255) return@InputFilter "" @@ -150,14 +150,14 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { } } else { if (!fullText.matches(rexFullIpWithColon)) { - if (!dest.substring(0, dstart).contains(Regex(rexColon)) && dend == dest.length) { + if (!dest.substring(0, dstart).contains(Regex(REGEX_COLON)) && dend == dest.length) { showSnack(dialog, R.string.error_colon_must_follow_a_valid_ip_address) } else { showSnack(dialog, R.string.error_invalid_ip_address) } return@InputFilter "" } - fullText.split(Regex("$rexDot|$rexColon")).dropLastWhile { it.isEmpty() }.forEachIndexed { index, s -> + fullText.split(Regex("$REGEX_DOT|$REGEX_COLON")).dropLastWhile { it.isEmpty() }.forEachIndexed { index, s -> if (index < 4 && s.toIntOrNull()?.let { it <= 255 } != true) { showSnack(dialog, R.string.error_dot_decimal_notation_num_over_255) return@InputFilter "" @@ -170,8 +170,8 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { } } return@InputFilter source - .replace(Regex("$rexDot+"), ".") - .replace(Regex("$rexColon+"), ":") + .replace(Regex("$REGEX_DOT+"), ".") + .replace(Regex("$REGEX_COLON+"), ":") } } } @@ -193,22 +193,22 @@ class JsonSocketClientTool(context: Context) : AbstractJsonSocketTool(context) { } dialog.dismiss() devPlugin - .connectToRemoteServer(input, mClientModeItem) + .connectToRemoteServer(context, input, mClientModeItem) .subscribe({ Pref.setServerAddress(input) }, onConnectionException) } companion object { - const val rexDot = "[,.,。\\u0020]" - const val rexColon = "[::]" + const val REGEX_DOT = "[,.,。\\u0020]" + const val REGEX_COLON = "[::]" - private const val rexIpDec = "\\d{1,3}" - private const val rexPort = "\\d{1,5}" + private const val REGEX_IP_DEC = "\\d{1,3}" + private const val REGEX_PORT = "\\d{1,5}" - val rexPartialIp = Regex("^$rexIpDec($rexDot($rexIpDec($rexDot($rexIpDec($rexDot($rexIpDec)?)?)?)?)?)?") - val rexFullIpWithColon = Regex("\\d+$rexDot\\d+$rexDot\\d+$rexDot\\d+$rexColon\\d*") - val rexValidIp = Regex("$rexIpDec$rexDot$rexIpDec$rexDot$rexIpDec$rexDot$rexIpDec($rexColon$rexPort)?") - val rexAcceptable = Regex("($rexDot|$rexColon|\\d)+") + val rexPartialIp = Regex("^$REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC($REGEX_DOT($REGEX_IP_DEC)?)?)?)?)?)?") + val rexFullIpWithColon = Regex("\\d+$REGEX_DOT\\d+$REGEX_DOT\\d+$REGEX_DOT\\d+$REGEX_COLON\\d*") + val rexValidIp = Regex("$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC$REGEX_DOT$REGEX_IP_DEC($REGEX_COLON$REGEX_PORT)?") + val rexAcceptable = Regex("($REGEX_DOT|$REGEX_COLON|\\d)+") } diff --git a/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt b/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt index aec67b23..3657bf90 100644 --- a/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt +++ b/app/src/main/java/org/autojs/autojs/core/accessibility/AccessibilityTool.kt @@ -97,6 +97,8 @@ class AccessibilityTool(val context: Context) { private fun enableWithRoot(timeout: Long? = null): Boolean = when (timeout != null) { true -> enableWithRoot() && AccessibilityService.waitForEnabled(timeout.toLong()) else -> try { + disableWithRoot() + val services = getEnabledWithRoot(true) val cmdServices = "settings put secure enabled_accessibility_services $services" @@ -116,8 +118,12 @@ class AccessibilityTool(val context: Context) { private fun enableWithSecure(timeout: Long? = null): Boolean = when (timeout != null) { true -> enableWithSecure() && AccessibilityService.waitForEnabled(timeout) else -> try { - Secure.putString(context.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, getEnabledWithSecure(true)) + disableWithSecure() + + val enabledWithSecure = getEnabledWithSecure(true) + Secure.putString(context.contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledWithSecure) Secure.putInt(context.contentResolver, Secure.ACCESSIBILITY_ENABLED, 1) + isEnabled() } catch (e: Exception) { false diff --git a/app/src/main/java/org/autojs/autojs/core/accessibility/SimpleActionAutomator.kt b/app/src/main/java/org/autojs/autojs/core/accessibility/SimpleActionAutomator.kt index 12b99eed..89414f27 100644 --- a/app/src/main/java/org/autojs/autojs/core/accessibility/SimpleActionAutomator.kt +++ b/app/src/main/java/org/autojs/autojs/core/accessibility/SimpleActionAutomator.kt @@ -171,7 +171,7 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge fun longClick(x: Int, y: Int) = globalActionAutomatorForGesture.longClick(x, y) @ScriptInterface - fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, delay: Int) = globalActionAutomatorForGesture.swipe(x1, y1, x2, y2, delay.toLong()) + fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, duration: Int) = globalActionAutomatorForGesture.swipe(x1, y1, x2, y2, duration.toLong()) @ScriptInterface fun paste(target: ActionTarget) = performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE)) diff --git a/app/src/main/java/org/autojs/autojs/core/automator/GlobalActionAutomator.kt b/app/src/main/java/org/autojs/autojs/core/automator/GlobalActionAutomator.kt index 70f3eeca..d49777d5 100644 --- a/app/src/main/java/org/autojs/autojs/core/automator/GlobalActionAutomator.kt +++ b/app/src/main/java/org/autojs/autojs/core/automator/GlobalActionAutomator.kt @@ -184,6 +184,6 @@ class GlobalActionAutomator(private val mHandler: Handler?, private val serviceP private fun scaleY(y: Int) = mScreenMetrics?.scaleX(y) ?: y - fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, delay: Long) = gesture(0, delay, intArrayOf(x1, y1), intArrayOf(x2, y2)) + fun swipe(x1: Int, y1: Int, x2: Int, y2: Int, duration: Long) = gesture(0, duration, intArrayOf(x1, y1), intArrayOf(x2, y2)) } diff --git a/app/src/main/java/org/autojs/autojs/core/automator/UiObjectCollection.kt b/app/src/main/java/org/autojs/autojs/core/automator/UiObjectCollection.kt index 7eeaef92..4929fa78 100644 --- a/app/src/main/java/org/autojs/autojs/core/automator/UiObjectCollection.kt +++ b/app/src/main/java/org/autojs/autojs/core/automator/UiObjectCollection.kt @@ -61,6 +61,10 @@ class UiObjectCollection private constructor(private val nodes: List) return success } + override fun toString(): String { + return "${UiObjectCollection::class.java.name}@${hashCode()}" + } + companion object { val EMPTY = of(emptyList()) diff --git a/app/src/main/java/org/autojs/autojs/core/console/ConsoleImpl.kt b/app/src/main/java/org/autojs/autojs/core/console/ConsoleImpl.kt index c6733649..aa56ba09 100644 --- a/app/src/main/java/org/autojs/autojs/core/console/ConsoleImpl.kt +++ b/app/src/main/java/org/autojs/autojs/core/console/ConsoleImpl.kt @@ -1,30 +1,40 @@ package org.autojs.autojs.core.console +import android.content.Context import android.content.Intent +import android.net.Uri import android.os.CountDownTimer import android.os.Looper import android.util.Log import androidx.annotation.ColorInt +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog import org.autojs.autojs.annotation.ScriptInterface import org.autojs.autojs.permission.DisplayOverOtherAppsPermission +import org.autojs.autojs.pref.Language import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.api.AbstractConsole import org.autojs.autojs.runtime.exception.ScriptInterruptedException import org.autojs.autojs.tool.UiHandler +import org.autojs.autojs.ui.common.NotAskAgainDialog import org.autojs.autojs.ui.enhancedfloaty.FloatyService import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow import org.autojs.autojs.ui.enhancedfloaty.gesture.DragGesture import org.autojs.autojs.util.ClipboardUtils +import org.autojs.autojs.util.StringUtils.key import org.autojs.autojs.util.ViewUtils -import org.autojs.autojs.util.ViewUtils.setViewMeasure import org.autojs.autojs6.R +import org.joda.time.format.DateTimeFormat import org.opencv.core.Point import org.opencv.core.Size +import java.io.IOException import java.lang.ref.WeakReference import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.BlockingQueue import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil +import kotlin.math.pow + /** * Created by Stardust on 2017/5/2. @@ -36,6 +46,8 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { var configurator = Configurator() private val mDefaultSafeDelay: Long = 360 + private val mSafeSizeToSend: Int = 2.0.pow(16).toInt() + private val mSafeSizeToCopy: Int = 2.0.pow(19).toInt() private var mLogListeners = ArrayList>() private var mConsoleView: WeakReference? = null @@ -43,7 +55,9 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { @get:Synchronized private var mCountDownTimer: CountDownTimer? = null - private val context = uiHandler.context + private val context: Context + get() = mConsoleView?.get()?.context ?: uiHandler.context + private val mLockWindowShow = Object() private val mLockWindowCreated = Object() private val mLockConsoleView = Object() @@ -51,7 +65,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { private val mFloatyWindow: ResizableExpandableFloatyWindow private val mConsoleFloaty: ConsoleFloaty private val mInput: BlockingQueue = ArrayBlockingQueue(1) - private val mDisplayOverOtherAppsPerm = DisplayOverOtherAppsPermission(context) + private val mDisplayOverOtherAppsPerm = DisplayOverOtherAppsPermission(uiHandler.context) val logEntries = ArrayList() @@ -60,7 +74,9 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { private set private val logEntriesJoint - get() = logEntries.joinToString("\n") { it.content } + get() = synchronized(logEntries) { + logEntries.joinToString("\n") { it.content } + } // val size: Size // get() = configurator.size ?: Size() @@ -123,26 +139,174 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { mLogListeners.forEach { it.get()?.onLogClear() } } - fun copyAll() { + fun copyAll() = copyAll(logEntriesJoint) + + private fun copyAll(text: String, cutOutEntriesSize: Int = -1) { + if (text.isEmpty()) { + ViewUtils.showToast(context, R.string.text_no_log_entries_to_copy) + return + } try { - ClipboardUtils.setClip(context, logEntriesJoint) - ViewUtils.showToast(context, R.string.text_already_copied_to_clip) - } catch (_: Exception) { - ViewUtils.showToast(context, R.string.text_failed) + ClipboardUtils.setClip(context, text) + if (cutOutEntriesSize >= 0) { + ViewUtils.showToast( + context, context.getString( + R.string.text_already_copied_to_clip_but_only_latest_few_items, + cutOutEntriesSize + ), true + ) + } else { + ViewUtils.showToast(context, R.string.text_already_copied_to_clip) + } + } catch (e: Exception) { + e.printStackTrace() + if (text.length < mSafeSizeToCopy || cutOutEntriesSize >= 0) { + ViewUtils.showToast(context, R.string.text_failed_to_copy) + return + } + try { + val cutOutEntries = cutOutEntries(mSafeSizeToCopy) + copyAll(cutOutEntries.joinToString("\n") { it.content }, cutOutEntries.size) + } catch (e: Exception) { + e.printStackTrace() + ViewUtils.showToast(context, R.string.text_failed_to_copy) + } } } fun export() { - val sendIntent: Intent = Intent().apply { + @Suppress("SpellCheckingInspection") + mConsoleView?.get()?.export("autojs6-log-${DateTimeFormat.forPattern("yyyyMMdd-HHmmss").print(System.currentTimeMillis())}.txt") + } + + fun export(uri: Uri?) { + val message = logEntriesJoint + if (message.isEmpty()) { + ViewUtils.showToast(context, R.string.text_no_log_entries_to_export) + return + } + try { + context.contentResolver.openOutputStream(uri ?: return)?.use { + it.write(message.toByteArray()) + it.flush() + } + ViewUtils.showToast( + context, + context.getString(R.string.text_some_items_exported, logEntries.size), + true, + ) + } catch (e: IOException) { + e.printStackTrace() + MaterialDialog.Builder(context) + .title(R.string.text_prompt) + .content(R.string.text_failed_to_export_log_entries) + .cancelable(false) + .autoDismiss(false) + .neutralText(R.string.text_details) + .neutralColorRes(R.color.dialog_button_hint) + .onNeutral { dialog, _ -> + dialog.setActionButton(DialogAction.NEUTRAL, null) + dialog.setActionButton(DialogAction.POSITIVE, R.string.dialog_button_dismiss) + dialog.contentView?.text = e.stackTraceToString() + dialog.titleView?.text = context.getString(R.string.text_details) + } + .positiveText(R.string.dialog_button_dismiss) + .onPositive { dialog, _ -> dialog.dismiss() } + .show() + } + } + + fun send() { + val message = logEntriesJoint + if (message.isEmpty()) { + ViewUtils.showToast(context, R.string.text_no_log_entries_to_send) + return + } + try { + send(message) + } catch (e: Exception) { + e.printStackTrace() + if (message.length < mSafeSizeToSend) { + throw Exception(e) + } + + try { + val cutOutEntries = cutOutEntries(mSafeSizeToSend) + + val msgReason = context.getString(R.string.text_num_of_log_entries_exceeds_limit_for_sending, logEntries.size) + val msgAction = context.getString(R.string.text_only_latest_few_items_will_be_sent, cutOutEntries.size) + + NotAskAgainDialog.Builder( + context, + key(R.string.key_dialog_num_of_log_entries_exceeds_limit_for_sending), + ).apply { + title(R.string.text_prompt) + content("$msgReason, $msgAction.") + cancelable(false) + negativeText(R.string.dialog_button_quit) + positiveText(R.string.dialog_button_continue) + positiveColorRes(R.color.dialog_button_attraction) + onPositive { _, _ -> send(cutOutEntries) } + if (show() == null) { + ViewUtils.showToast(context, msgAction.replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Language.getPrefLanguage().locale) + else it.toString() + }, true) + send(cutOutEntries) + } + } + } catch (e: Exception) { + e.printStackTrace() + MaterialDialog.Builder(context) + .title(R.string.text_prompt) + .content(R.string.text_failed_to_send_log_entries) + .cancelable(false) + .autoDismiss(false) + .neutralText(R.string.text_details) + .neutralColorRes(R.color.dialog_button_hint) + .onNeutral { dialog, _ -> + dialog.setActionButton(DialogAction.NEUTRAL, null) + dialog.setActionButton(DialogAction.POSITIVE, R.string.text_close) + dialog.contentView?.text = e.stackTraceToString() + dialog.titleView?.text = context.getString(R.string.text_details) + } + .positiveText(R.string.dialog_button_dismiss) + .onPositive { dialog, _ -> dialog.dismiss() } + .show() + } + } + } + + private fun send(entries: MutableList) { + send(entries.joinToString("\n") { it.content }) + } + + private fun send(message: String) { + val sendIntent = Intent().apply { action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, logEntriesJoint) + putExtra(Intent.EXTRA_TEXT, message) type = "text/plain" } - context.startActivity(Intent.createChooser(sendIntent, null).apply { + uiHandler.context.startActivity(Intent.createChooser(sendIntent, null).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) } + private fun cutOutEntries(maxLength: Int): MutableList { + var accLength = 0 + val chosenEntries = mutableListOf() + synchronized(logEntries) { + for (entry in logEntries.reversed()) { + if (accLength >= maxLength) { + break + } + accLength += entry.content.length + chosenEntries.add(0, entry) + } + } + return chosenEntries + } + override fun show() = show(false) @ScriptInterface @@ -153,7 +317,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { synchronized(mLockWindowShow) { if (!mDisplayOverOtherAppsPerm.has()) { mDisplayOverOtherAppsPerm.config() - uiHandler.toast(R.string.error_no_draw_overlays_permission) + uiHandler.toast(R.string.error_no_display_over_other_apps_permission) return } if (isReset) reset() @@ -168,7 +332,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { // SecurityException: https://github.com/hyb1996-guest/AutoJsIssueReport/issues/4781 } catch (e: Exception) { e.printStackTrace() - uiHandler.toast(R.string.error_no_draw_overlays_permission) + uiHandler.toast(R.string.error_no_display_over_other_apps_permission) } } synchronized(mLockWindowCreated) { @@ -214,7 +378,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { } private fun startFloatyService() { - context.startService(Intent(context, FloatyService::class.java)) + uiHandler.context.startService(Intent(uiHandler.context, FloatyService::class.java)) } @ScriptInterface @@ -234,7 +398,7 @@ open class ConsoleImpl(val uiHandler: UiHandler) : AbstractConsole() { configurator.setSize(w, h) if (isShowing) { mConsoleFloaty.expandedView?.let { - setViewMeasure(it, w.toInt(), h.toInt()) + ViewUtils.setViewMeasure(it, w.toInt(), h.toInt()) } } } diff --git a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java index 869df9eb..ee041cc7 100644 --- a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java +++ b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java @@ -22,6 +22,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.autojs.autojs.tool.MapBuilder; import org.autojs.autojs.ui.enhancedfloaty.ResizableExpandableFloatyWindow; +import org.autojs.autojs.ui.log.LogActivity; import org.autojs.autojs.util.DisplayUtils; import org.autojs.autojs6.R; import org.jetbrains.annotations.NotNull; @@ -40,6 +41,7 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener private final static int sRefreshInterval = 100; private final Map mColors = new MapBuilder().build(); private ConsoleImpl mConsole; + private LogActivity mLogActivity; private RecyclerView mLogListRecyclerView; private EditText mEditText; private ResizableExpandableFloatyWindow mWindow; @@ -240,6 +242,16 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener mConsole.setConsoleView(this); } + public void setLogActivity(LogActivity activity) { + mLogActivity = activity; + } + + public void export(String fileName) { + if (mLogActivity != null) { + mLogActivity.export(fileName); + } + } + @Override public void onNewLog(ConsoleImpl.LogEntry logEntry) { diff --git a/app/src/main/java/org/autojs/autojs/core/console/GlobalConsole.java b/app/src/main/java/org/autojs/autojs/core/console/GlobalConsole.java index 1de7901f..807ea19b 100644 --- a/app/src/main/java/org/autojs/autojs/core/console/GlobalConsole.java +++ b/app/src/main/java/org/autojs/autojs/core/console/GlobalConsole.java @@ -26,7 +26,7 @@ import java.util.Locale; */ @SuppressLint("ConstantLocale") public class GlobalConsole extends ConsoleImpl { - private static final String TAG = "GlobalConsole"; + private static final String TAG = GlobalConsole.class.getSimpleName(); private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()); private static final Logger LOGGER = Logger.getLogger(GlobalConsole.class); @@ -45,7 +45,7 @@ public class GlobalConsole extends ConsoleImpl { return log; } - private Priority toLog4jLevel(int level) { + protected Priority toLog4jLevel(int level) { switch (level) { case VERBOSE, DEBUG -> { return Level.DEBUG; @@ -66,7 +66,7 @@ public class GlobalConsole extends ConsoleImpl { } } - private String getLevelChar(int level) { + protected String getLevelChar(int level) { return switch (level) { case VERBOSE -> "V"; case DEBUG -> "D"; diff --git a/app/src/main/java/org/autojs/autojs/core/console/JsConsoleView.kt b/app/src/main/java/org/autojs/autojs/core/console/JsConsoleView.kt deleted file mode 100644 index 6c679ffe..00000000 --- a/app/src/main/java/org/autojs/autojs/core/console/JsConsoleView.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.autojs.autojs.core.console - -import android.content.Context -import android.util.AttributeSet -import org.autojs.autojs.AutoJs - -class JsConsoleView : ConsoleView { - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - init { - setConsole(AutoJs.instance.globalConsole) - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/http/MutableOkHttp.kt b/app/src/main/java/org/autojs/autojs/core/http/MutableOkHttp.kt index fd8f710d..df9ac63d 100644 --- a/app/src/main/java/org/autojs/autojs/core/http/MutableOkHttp.kt +++ b/app/src/main/java/org/autojs/autojs/core/http/MutableOkHttp.kt @@ -3,7 +3,6 @@ package org.autojs.autojs.core.http import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response -import java.net.SocketTimeoutException import java.util.concurrent.TimeUnit /** @@ -14,31 +13,28 @@ class MutableOkHttp : OkHttpClient() { private var mOkHttpClient: OkHttpClient private val maxRetries = 3 - private var mTimeout = (30 * 1000).toLong() + private var mTimeout = 30 * 1000L private val mRetryInterceptor = Interceptor { chain: Interceptor.Chain -> val request = chain.request() var response: Response? = null var tryCount = 0 do { - var succeed: Boolean + var isSuccessful: Boolean try { response?.close() - chain.proceed(request).apply { - response = this - succeed = isSuccessful - } - } catch (e: SocketTimeoutException) { - succeed = false + response = chain.proceed(request).also { isSuccessful = it.isSuccessful } + } catch (e: Exception) { + isSuccessful = false if (tryCount >= maxRetries) { throw e } } - if (succeed || tryCount >= maxRetries) { + if (isSuccessful || tryCount >= maxRetries) { break } tryCount++ } while (true) - response!! + response ?: throw Exception("Failed to make a request") } init { diff --git a/app/src/main/java/org/autojs/autojs/core/image/ColorFinder.java b/app/src/main/java/org/autojs/autojs/core/image/ColorFinder.java index 63cec701..09ebe73b 100644 --- a/app/src/main/java/org/autojs/autojs/core/image/ColorFinder.java +++ b/app/src/main/java/org/autojs/autojs/core/image/ColorFinder.java @@ -130,7 +130,7 @@ public class ColorFinder { @Deprecated @ScriptInterface @SuppressWarnings("deprecation") - @CodeAuthor(name = "LYS", homepage = "https://github.com/LYS86") + @CodeAuthor(name = "LYS86", homepage = "https://github.com/LYS86") public Point[] findAllMultiColors(ImageWrapper image, int firstColor, int threshold, Rect rect, int[] points) { Point[] firstPoints = findAllPointsForColor(image, firstColor, threshold, rect); List resultPoints = new ArrayList<>(); diff --git a/app/src/main/java/org/autojs/autojs/core/inputevent/InputEventObserver.java b/app/src/main/java/org/autojs/autojs/core/inputevent/InputEventObserver.java index ce94a81a..2d93f8ca 100644 --- a/app/src/main/java/org/autojs/autojs/core/inputevent/InputEventObserver.java +++ b/app/src/main/java/org/autojs/autojs/core/inputevent/InputEventObserver.java @@ -1,7 +1,9 @@ package org.autojs.autojs.core.inputevent; import android.content.Context; + import androidx.annotation.NonNull; + import android.text.TextUtils; import org.autojs.autojs6.R; @@ -67,7 +69,6 @@ public class InputEventObserver { void onInputEvent(@NonNull InputEvent e); } - private static InputEventObserver sGlobal; private final CopyOnWriteArrayList mInputEventListeners = new CopyOnWriteArrayList<>(); private final Context mContext; private Shell mShell; @@ -76,18 +77,10 @@ public class InputEventObserver { mContext = context; } - public static InputEventObserver getGlobal(Context context) { - if (sGlobal == null) { - initGlobal(context); - } - return sGlobal; - } - - private static void initGlobal(Context context) { - if (sGlobal != null) - return; - sGlobal = new InputEventObserver(context); - sGlobal.observe(); + public static InputEventObserver initObserver(Context context) { + var inputEventObserver = new InputEventObserver(context); + inputEventObserver.observe(); + return inputEventObserver; } public void observe() { diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt index 7f63fb1d..39e48c37 100644 --- a/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt +++ b/app/src/main/java/org/autojs/autojs/core/looper/Loopers.kt @@ -1,128 +1,45 @@ package org.autojs.autojs.core.looper -import android.os.Handler import android.os.Looper import android.os.MessageQueue -import android.os.MessageQueue.IdleHandler import android.util.Log import org.autojs.autojs.lang.ThreadCompat import org.autojs.autojs.rhino.AutoJsContext import org.autojs.autojs.runtime.ScriptRuntime -import org.autojs.autojs.runtime.api.Threads -import org.autojs.autojs.runtime.api.Timers import org.autojs.autojs.runtime.exception.ScriptInterruptedException import org.mozilla.javascript.Context -import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock /** * Created by Stardust on 2017/7/29. - * Modified by SuperMonster003 as of Jul 12, 2023. - * Transformed by SuperMonster003 on Jul 12, 2023. + * Modified by aiselp as of Jun 4, 2023. + * ! 调整内容: + * ! 使此类只负责单 loop 线程生命周期管理, 移除繁琐的调用链 + * ! 调整 timer 由此类创建 + * ! 通过向此类添加 AsyncTask 以监听线程退出事件 + * Modified by SuperMonster003 as of Aug 28, 2023. */ -// @Overruled by SuperMonster003 on Jul 12, 2023. -// ! Author: aiselp -// ! Related PR: -// ! http://pr.autojs6.com/75 -// ! Reason: -// ! Sorry but my current capabilities are not sufficient -// ! to fully understand everything from above pull request(s), -// ! so most of the code will remain as is. :) -class Loopers(runtime: ScriptRuntime) : IdleHandler { - interface LooperQuitHandler { - fun shouldQuit(): Boolean - } - - private val waitWhenIdle: ThreadLocal = object : ThreadLocal() { - override fun initialValue(): Boolean { - return Looper.myLooper() == Looper.getMainLooper() - } - } - private val waitIds: ThreadLocal> = object : ThreadLocal>() { - override fun initialValue(): HashSet { - return HashSet() - } - } - private val maxWaitId: ThreadLocal = object : ThreadLocal() { - override fun initialValue(): Int { - return 0 - } - } - private val looperQuitHandlers = ThreadLocal>() +class Loopers(val runtime: ScriptRuntime) { @Volatile private var mServantLooper: Looper? = null - private val mTimers: Timers + @Suppress("DEPRECATION") private var mMainLooperQuitHandler: LooperQuitHandler? = null - private val mMainHandler: Handler - private val mMainLooper: Looper? - private val mThreads: Threads - private val mMainMessageQueue: MessageQueue + private var waitWhenIdle: Boolean + private val allTasks = ConcurrentLinkedQueue() - init { - mTimers = runtime.timers - mThreads = runtime.threads - prepare() - mMainLooper = Looper.myLooper() - mMainHandler = Handler(Looper.getMainLooper()) - mMainMessageQueue = Looper.myQueue() - } - - fun addLooperQuitHandler(handler: LooperQuitHandler) { - var handlers = looperQuitHandlers.get() - if (handlers == null) { - handlers = CopyOnWriteArrayList() - looperQuitHandlers.set(handlers) - } - handlers.add(handler) - } - - fun removeLooperQuitHandler(handler: LooperQuitHandler): Boolean { - val handlers = looperQuitHandlers.get() - return handlers != null && handlers.remove(handler) - } - - private fun shouldQuitLooper(): Boolean { - if (Thread.currentThread().isInterrupted) { - return true - } - if (mTimers.hasPendingCallbacks()) { - return false - } - if (waitWhenIdle.get() == true || waitIds.get()?.isNotEmpty() == true) { - return false - } - if ((Context.getCurrentContext() as AutoJsContext).hasPendingContinuation()) { - return false - } - val handlers = looperQuitHandlers.get() ?: return true - for (handler in handlers) { - if (!handler.shouldQuit()) { - return false - } - } - return true - } - - private fun initServantThread() { - ThreadCompat { - Looper.prepare() - mServantLooper = Looper.myLooper() - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - synchronized(this@Loopers as Object) { - notifyAll() - } - Looper.loop() - }.start() - } + private val lock = ReentrantLock() + private val condition = lock.newCondition() val servantLooper: Looper get() { if (mServantLooper == null) { initServantThread() - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - synchronized(this@Loopers as Object) { + lock.withLock { try { - wait() + condition.await() } catch (e: InterruptedException) { throw ScriptInterruptedException(e) } @@ -131,69 +48,145 @@ class Loopers(runtime: ScriptRuntime) : IdleHandler { return mServantLooper!! } - private fun quitServantLooper() { - mServantLooper?.quit() + val timer: Timer + val myLooper: Looper + + init { + prepare() + myLooper = Looper.myLooper()!! + timer = Timer(runtime, myLooper) + waitWhenIdle = myLooper == Looper.getMainLooper() } - fun waitWhenIdle(): Int { - val id = maxWaitId.get()!! - Log.d(LOG_TAG, "waitWhenIdle: $id") - maxWaitId.set(id + 1) - waitIds.get()!!.add(id) - return id + @Deprecated("Deprecated in Java", ReplaceWith("AsyncTask")) + interface LooperQuitHandler { + fun shouldQuit(): Boolean } - fun doNotWaitWhenIdle(waitId: Int) { - Log.d(LOG_TAG, "doNotWaitWhenIdle: $waitId") - waitIds.get()!!.remove(waitId) + open class AsyncTask(private val desc: String) { + + private val allBind = ConcurrentLinkedQueue() + + var isEnd: Boolean = false + private set + + // 线程即将退出时调用, 返回 true 阻止线程退出, 只要有一个 task 返回 true 线程就不会退出 + open fun onFinish(loopers: Loopers) = true + + fun end() { + isEnd = true + } + + // 线程正在退出, 这里应该结束任务的执行, 回收资源 + open fun onStop(loopers: Loopers) = Unit + + override fun toString() = "AsyncTask: $desc" + } + fun createAndAddAsyncTask(desc: String): AsyncTask { + return AsyncTask(desc).also { allTasks.add(it) } + } + + fun addAsyncTask(task: AsyncTask) { + synchronized(myLooper) { + allTasks.add(task) + } + } + + fun removeAsyncTask(task: AsyncTask) { + synchronized(myLooper) { + allTasks.remove(task) + timer.post(EMPTY_RUNNABLE) + } + } + + private fun checkTask(): Boolean { + allTasks.removeAll(allTasks.filter { it.isEnd }.toSet()) + return allTasks.any { it.onFinish(this) } + } + + private fun shouldQuitLooper(): Boolean { + synchronized(myLooper) { + return when { + Thread.currentThread().isInterrupted -> true + timer.hasPendingCallbacks() -> false + // 检查是否有运行中的线程 + checkTask() -> false + waitWhenIdle -> false + (Context.getCurrentContext() as AutoJsContext).hasPendingContinuation() -> false + else -> true + } + } + } + + private fun initServantThread() { + ThreadCompat { + Looper.prepare() + mServantLooper = Looper.myLooper() + lock.withLock { + condition.signalAll() + } + Looper.loop() + }.start() + } + + @Deprecated("Deprecated in Java", ReplaceWith("AsyncTask")) fun waitWhenIdle(b: Boolean) { - waitWhenIdle.set(b) + waitWhenIdle = b } fun recycle() { - quitServantLooper() - mMainMessageQueue.removeIdleHandler(this) + Log.d(LOG_TAG, "recycle") + for (task in allTasks.filter { !it.isEnd }) { + try { + task.onStop(this) + } catch (e: Exception) { + Log.w(LOG_TAG, e) + } + } + mServantLooper?.quit() } - fun setMainLooperQuitHandler(mainLooperQuitHandler: LooperQuitHandler?) { + @Deprecated("Deprecated in Java", ReplaceWith("AsyncTask")) + fun setMainLooperQuitHandler(@Suppress("DEPRECATION") mainLooperQuitHandler: LooperQuitHandler?) { mMainLooperQuitHandler = mainLooperQuitHandler } - override fun queueIdle(): Boolean { - val l = Looper.myLooper() ?: return true - if (l == mMainLooper) { - Log.d(LOG_TAG, "main looper queueIdle") - if (shouldQuitLooper() && !mThreads.hasRunningThreads() && mMainLooperQuitHandler != null && mMainLooperQuitHandler!!.shouldQuit()) { - Log.d(LOG_TAG, "main looper quit") - l.quit() - } - } else { - Log.d(LOG_TAG, "looper queueIdle: $l") - if (shouldQuitLooper()) { - l.quit() - } - } - return true - } - - fun prepare() { + private fun prepare() { if (Looper.myLooper() == null) { LooperHelper.prepare() } - Looper.myQueue().addIdleHandler(this) + Looper.myQueue().addIdleHandler(MessageQueue.IdleHandler { + if (this == runtime.loopers) { + Log.d(LOG_TAG, "main looper queueIdle") + if (shouldQuitLooper() && mMainLooperQuitHandler?.shouldQuit() == true) { + Log.d(LOG_TAG, "main looper quit") + Looper.myLooper()?.quitSafely() + } + } else { + Log.d(LOG_TAG, "looper queueIdle $this") + if (shouldQuitLooper()) { + Log.d(LOG_TAG, "looper quit $this") + Looper.myLooper()?.quitSafely() + } + } + return@IdleHandler true + }) } fun notifyThreadExit(thread: TimerThread) { Log.d(LOG_TAG, "notifyThreadExit: $thread") - // 当子线程退成时,主线程需要检查自身是否退出(主线程在所有子线程执行完成后才能退出,如果主线程已经执行完任务仍然要等待所有子线程), - // 此时通过向主线程发送一个空的Runnable,主线程执行完这个Runnable后会触发IdleHandler,从而检查自身是否退出 - mMainHandler.post(EMPTY_RUNNABLE) + // 当子线程退成时, 主线程需要检查自身是否退出 (主线程在所有子线程执行完成后才能退出, 如果主线程已经执行完任务仍然要等待所有子线程), + // 此时通过向主线程发送一个空的 Runnable, 主线程执行完这个 Runnable 后会触发 IdleHandler, 从而检查自身是否退出 + // mHandler.post(EMPTY_RUNNABLE) } companion object { + private const val LOG_TAG = "Loopers" private val EMPTY_RUNNABLE = Runnable {} + } + } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt index 0a8153e5..bcd77bc6 100644 --- a/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt +++ b/app/src/main/java/org/autojs/autojs/core/looper/Timer.kt @@ -3,120 +3,137 @@ package org.autojs.autojs.core.looper import android.os.Handler import android.os.Looper import android.os.SystemClock -import android.util.SparseArray -import org.autojs.autojs.concurrent.VolatileBox import org.autojs.autojs.runtime.ScriptRuntime -import kotlin.math.max +import org.mozilla.javascript.BaseFunction +import org.mozilla.javascript.Context +import org.mozilla.javascript.Scriptable +import org.mozilla.javascript.Undefined +import java.util.concurrent.ConcurrentHashMap +import kotlin.random.Random /** * Created by Stardust on 2017/12/27. + * Modified by aiselp as of Jun 14, 2023. * Modified by SuperMonster003 as of Jul 12, 2023. - * Transformed by SuperMonster003 on Jul 12, 2023. */ -// @Overruled by SuperMonster003 on Jul 12, 2023. -// ! Author: aiselp -// ! Related PR: -// ! http://pr.autojs6.com/75 -// ! http://pr.autojs6.com/78 -// ! Reason: -// ! Sorry but my current capabilities are not sufficient -// ! to fully understand everything from above pull request(s), -// ! so most of the code will remain as is. :) -class Timer @JvmOverloads constructor(runtime: ScriptRuntime, maxCallbackMillisForAllThread: VolatileBox, private val looper: Looper? = Looper.myLooper()) { +class Timer(runtime: ScriptRuntime, looper: Looper) { - private val mHandlerCallbacks = SparseArray() - private var mCallbackMaxId = 0 + private val myLooper: Looper = looper + private val mHandlerCallbacks = ConcurrentHashMap() private val mRuntime: ScriptRuntime = runtime - @Suppress("DEPRECATION") - private val mHandler = looper?.let { Handler(it) } ?: Handler() - private var mMaxCallbackUptimeMillis: Long = 0 - private val mMaxCallbackMillisForAllThread: VolatileBox = maxCallbackMillisForAllThread + private val mHandler: Handler = Handler(looper) + private val isUiLoop: Boolean = looper == Looper.getMainLooper() + private val context: Context? by lazy { Context.getCurrentContext() } - fun setTimeout(callback: Any, delay: Long, vararg args: Array): Int { - mCallbackMaxId++ - val id = mCallbackMaxId + constructor(runtime: ScriptRuntime) : this(runtime, Looper.myLooper()!!) + + fun setTimeout(callback: Any, delay: Long, vararg args: Any?): Int { + val id = createTimerId() val r = Runnable { - callFunction(callback, args) + callFunction(callback, null, args) mHandlerCallbacks.remove(id) } - mHandlerCallbacks.put(id, r) + mHandlerCallbacks[id] = r postDelayed(r, delay) return id } - private fun callFunction(callback: Any, args: Array) { + private fun callFunction(callback: Any, thiz: Any?, args: Any?) { + val func = callback as BaseFunction + val map: Array = + (args as? Array<*>)?.map { Context.javaToJS(it, callback.parentScope) } + ?.toTypedArray() ?: emptyArray() try { - mRuntime.bridges.callFunction(callback, null, args) + func.call( + context ?: Context.enter(), func.parentScope, + thiz as? Scriptable ?: Undefined.SCRIPTABLE_UNDEFINED, map + ) } catch (e: Exception) { - if (Looper.myLooper() == Looper.getMainLooper()) { + if (isUiLoop) { mRuntime.exit(e) - } else { - throw e - } + } else throw e + } finally { + context ?: Context.exit() } } - fun clearTimeout(id: Int) = clearCallback(id) + @Synchronized + private fun createTimerId(): Int { + var id: Int + do { + id = Random.nextInt() + } while (mHandlerCallbacks.containsKey(id)) + mHandlerCallbacks[id] = EMPTY_RUNNABLE + return id + } - fun setInterval(listener: Any, interval: Long, vararg args: Any): Int { - mCallbackMaxId++ - val id = mCallbackMaxId - val r = object : Runnable { + fun setInterval(listener: Any, interval: Long, vararg args: Any?): Int { + val id = createTimerId() + val r: Runnable = object : Runnable { override fun run() { - mHandlerCallbacks[id] ?: return - callFunction(listener, args) + if (mHandlerCallbacks[id] == null) return + callFunction(listener, null, args) postDelayed(this, interval) } } - mHandlerCallbacks.put(id, r) + mHandlerCallbacks[id] = r postDelayed(r, interval) return id } fun postDelayed(r: Runnable, interval: Long) { - val uptime = SystemClock.uptimeMillis() + interval - mHandler.postAtTime(r, uptime) - mMaxCallbackUptimeMillis = mMaxCallbackUptimeMillis.coerceAtLeast(uptime) - synchronized(mMaxCallbackMillisForAllThread) { mMaxCallbackMillisForAllThread.set(max(mMaxCallbackMillisForAllThread.get(), uptime)) } - } - - // @Reference to aiselp (https://github.com/aiselp) on Jul 18, 2023. - fun post(r: Runnable) { - looper?.let { - synchronized(it) { - mHandler.post(r) - } + synchronized(myLooper) { + val uptime = SystemClock.uptimeMillis() + interval + mHandler.postAtTime(r, uptime) } } - fun clearInterval(id: Int) = clearCallback(id) + fun post(r: Runnable) { + synchronized(myLooper) { + mHandler.post(r) + } + } - fun setImmediate(listener: Any, vararg args: Any): Int { - mCallbackMaxId++ - val id = mCallbackMaxId + fun clearInterval(id: Int): Boolean = clearCallback(id) + fun clearImmediate(id: Int): Boolean = clearCallback(id) + fun clearTimeout(id: Int): Boolean = clearCallback(id) + + fun setImmediate(listener: Any, vararg args: Any?): Int { + val id = createTimerId() val r = Runnable { - callFunction(listener, args) + callFunction(listener, null, args) mHandlerCallbacks.remove(id) } - mHandlerCallbacks.put(id, r) - postDelayed(r, 0) + mHandlerCallbacks[id] = r + post(r) return id } - fun clearImmediate(id: Int) = clearCallback(id) private fun clearCallback(id: Int): Boolean { val callback = mHandlerCallbacks[id] if (callback != null) { mHandler.removeCallbacks(callback) mHandlerCallbacks.remove(id) + if (mHandlerCallbacks.isEmpty()) mHandler.post(EMPTY_RUNNABLE) return true } return false } - fun hasPendingCallbacks() = mMaxCallbackUptimeMillis > SystemClock.uptimeMillis() + fun hasPendingCallbacks(): Boolean { + return mHandlerCallbacks.size > 0 + } - fun removeAllCallbacks() = mHandler.removeCallbacksAndMessages(null) + fun removeAllCallbacks() { + mHandler.removeCallbacksAndMessages(null) + } + + companion object { + + private const val LOG_TAG = "Timer" + private val EMPTY_RUNNABLE = Runnable {} + + } } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt index 5899ac71..c06d7bc8 100644 --- a/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt +++ b/app/src/main/java/org/autojs/autojs/core/looper/TimerThread.kt @@ -1,60 +1,45 @@ package org.autojs.autojs.core.looper -import android.os.Handler import android.os.Looper import androidx.annotation.CallSuper -import org.autojs.autojs.concurrent.VolatileBox import org.autojs.autojs.engine.RhinoJavaScriptEngine import org.autojs.autojs.lang.ThreadCompat import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.exception.ScriptInterruptedException -import org.autojs.autojs.util.StringUtils.str -import org.autojs.autojs6.R import org.mozilla.javascript.Context import java.util.concurrent.ConcurrentHashMap /** * Created by Stardust on 2017/12/27. - * Modified by SuperMonster003 as of Jul 12, 2023. - * Transformed by SuperMonster003 on Jul 12, 2023. */ -// @Overruled by SuperMonster003 on Jul 12, 2023. -// ! Author: aiselp -// ! Related PR: -// ! http://pr.autojs6.com/75 -// ! Reason: -// ! Sorry but my current capabilities are not sufficient -// ! to fully understand everything from above pull request(s), -// ! so most of the code will remain as is. :) -@Suppress("unused") -open class TimerThread( - private val scriptRuntime: ScriptRuntime, - private val maxCallbackUptimeMillisForAllThreads: VolatileBox, - private val target: Runnable -) : ThreadCompat(target) { +open class TimerThread(private val mRuntime: ScriptRuntime, private val mTarget: Runnable) : ThreadCompat(mTarget) { private var mTimer: Timer? = null private var mRunning = false private val mRunningLock = Object() + private val mAsyncTask = Loopers.AsyncTask("TimerThread") + + var loopers: Loopers? = null + + init { + mRuntime.loopers.addAsyncTask(mAsyncTask) + } override fun run() { - scriptRuntime.loopers.prepare() - mTimer = Timer(scriptRuntime, maxCallbackUptimeMillisForAllThreads).also { - sTimerMap[currentThread()] = it - } - (scriptRuntime.engines.myEngine() as? RhinoJavaScriptEngine)?.enterContext() + loopers = Loopers(mRuntime) + mTimer = loopers!!.timer + sTimerMap[currentThread()] = mTimer!! + (mRuntime.engines.myEngine() as RhinoJavaScriptEngine).enterContext() notifyRunning() - @Suppress("DEPRECATION") - Looper.myLooper()?.let { - Handler(it).post(target) - } ?: Handler().post(target) + mTimer!!.post(mTarget) try { Looper.loop() } catch (e: Throwable) { if (!ScriptInterruptedException.causedByInterrupted(e)) { - scriptRuntime.console.error("${currentThread()}: $e") + mRuntime.console.error(currentThread().toString() + ": ", e) } } finally { + // mRuntime.console.log("TimerThread exit"); onExit() mTimer = null Context.exit() @@ -76,49 +61,69 @@ open class TimerThread( @CallSuper protected open fun onExit() { - scriptRuntime.loopers.notifyThreadExit(this) + mRuntime.loopers.removeAsyncTask(mAsyncTask) + mRuntime.loopers.notifyThreadExit(this) + } + + fun setTimeout(callback: Any, delay: Long, vararg args: Any): Int { + return timer.setTimeout(callback, delay, *args) + } + + fun setTimeout(callback: Any): Int { + return setTimeout(callback, 1) } val timer: Timer get() { - checkNotNull(mTimer) { str(R.string.error_thread_is_not_alive) } - return mTimer!! + checkNotNull(mTimer) { "thread is not alive" } + return mTimer as Timer } - fun setTimeout(callback: Any, delay: Long, vararg args: Array) = timer.setTimeout(callback, delay, *args) + fun clearTimeout(id: Int): Boolean { + return timer.clearTimeout(id) + } - fun clearTimeout(id: Int) = timer.clearTimeout(id) + fun setInterval(listener: Any?, interval: Long, vararg args: Any): Int { + return timer.setInterval(listener!!, interval, *args) + } - fun setInterval(listener: Any, interval: Long, vararg args: Array) = timer.setInterval(listener, interval, *args) + fun setInterval(listener: Any?): Int { + return setInterval(listener, 1) + } - fun clearInterval(id: Int) = timer.clearInterval(id) + fun clearInterval(id: Int): Boolean { + return timer.clearInterval(id) + } - fun setImmediate(listener: Any, vararg args: Array) = timer.setImmediate(listener, *args) + fun setImmediate(listener: Any, vararg args: Any): Int { + return timer.setImmediate(listener, *args) + } - fun clearImmediate(id: Int) = timer.clearImmediate(id) + fun clearImmediate(id: Int): Boolean { + return timer.clearImmediate(id) + } @Throws(InterruptedException::class) fun waitFor() { synchronized(mRunningLock) { - if (!mRunning) { - mRunningLock.wait() - } + if (mRunning) return + mRunningLock.wait() } } - override fun toString() = "Thread[$name,$priority]" + override fun toString(): String { + return "Thread[$name,$priority]" + } companion object { - private val sTimerMap = ConcurrentHashMap() @JvmStatic - fun getTimerForThread(thread: Thread) = sTimerMap[thread] + fun getTimerForThread(thread: Thread): Timer? { + return sTimerMap[thread] + } - @JvmStatic - val timerForCurrentThread + val timerForCurrentThread: Timer? get() = getTimerForThread(currentThread()) - } - } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java index d9e8022d..b685dbc4 100644 --- a/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java +++ b/app/src/main/java/org/autojs/autojs/core/ui/dialog/JsDialogBuilder.java @@ -4,7 +4,6 @@ import android.content.Context; import android.widget.EditText; import com.afollestad.materialdialogs.MaterialDialog; - import org.autojs.autojs.core.eventloop.EventEmitter; import org.autojs.autojs.core.looper.Loopers; import org.autojs.autojs.core.looper.Timer; @@ -13,6 +12,8 @@ import org.autojs.autojs.tool.UiHandler; /** * Created by Stardust on 2018/4/17. + * Modified by SuperMonster003 as of Mar 20, 2022. + * Modified by aiselp as of Jun 10, 2023. */ public class JsDialogBuilder extends MaterialDialog.Builder { @@ -21,7 +22,7 @@ public class JsDialogBuilder extends MaterialDialog.Builder { private final Timer mTimer; private final Loopers mLoopers; private JsDialog mDialog; - private volatile int mWaitId = -1; + private volatile Loopers.AsyncTask task; public JsDialogBuilder(Context context, ScriptRuntime runtime) { super(context); @@ -55,14 +56,14 @@ public class JsDialogBuilder extends MaterialDialog.Builder { } }); dismissListener(dialog -> { - mTimer.postDelayed(() -> mLoopers.doNotWaitWhenIdle(mWaitId), 0); + mTimer.postDelayed(() -> mLoopers.removeAsyncTask(task), 0); emit("dismiss", dialog); }); cancelListener(dialog -> emit("cancel", dialog)); } public void onShowCalled() { - mTimer.postDelayed(() -> mWaitId = mLoopers.waitWhenIdle(), 0); + mTimer.postDelayed(() -> task = mLoopers.createAndAddAsyncTask("js-dialog"), 0); } public JsDialog getDialog() { diff --git a/app/src/main/java/org/autojs/autojs/core/ui/widget/JsConsoleView.kt b/app/src/main/java/org/autojs/autojs/core/ui/widget/JsConsoleView.kt index 5fae02d5..35e74d9c 100644 --- a/app/src/main/java/org/autojs/autojs/core/ui/widget/JsConsoleView.kt +++ b/app/src/main/java/org/autojs/autojs/core/ui/widget/JsConsoleView.kt @@ -2,19 +2,42 @@ package org.autojs.autojs.core.ui.widget import android.content.Context import android.util.AttributeSet -import org.autojs.autojs.AutoJs +import android.util.Log +import org.apache.log4j.Logger +import org.autojs.autojs.core.console.ConsoleImpl import org.autojs.autojs.core.console.ConsoleView +import org.autojs.autojs.core.console.GlobalConsole +import org.autojs.autojs.tool.UiHandler +import org.autojs.autojs.util.ViewUtils +import java.util.Locale class JsConsoleView : ConsoleView { + private val TAG = JsConsoleView::class.java.simpleName + private val LOGGER = Logger.getLogger(JsConsoleView::class.java) + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - init { - setConsole(AutoJs.instance.globalConsole) - } + // init { + // setConsole(object : GlobalConsole(UiHandler(context)) { + // override fun println(level: Int, charSequence: CharSequence): String { + // val log = String.format(Locale.getDefault(), "%s", charSequence) + // LOGGER.log(toLog4jLevel(level), log) + // Log.d(TAG, log) + // super.println(level, log) + // return log + // } + // }) + // } + // + // override fun onNewLog(logEntry: ConsoleImpl.LogEntry?) { + // if (logEntry != null) { + // ViewUtils.showToast(context, logEntry.content.toString()) + // } + // } } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/core/web/WebSocket.kt b/app/src/main/java/org/autojs/autojs/core/web/WebSocket.kt index 2a4f014f..2b531f5a 100644 --- a/app/src/main/java/org/autojs/autojs/core/web/WebSocket.kt +++ b/app/src/main/java/org/autojs/autojs/core/web/WebSocket.kt @@ -8,19 +8,20 @@ import okhttp3.WebSocketListener import okio.ByteString import org.autojs.autojs.AutoJs import org.autojs.autojs.core.eventloop.EventEmitter - -val runtime = AutoJs.instance.runtime +import java.lang.ref.WeakReference /** * Created by SuperMonster003 on Apr 30, 2023. */ // @Reference to kkevsekk1/AutoX (https://github.com/kkevsekk1/AutoX) on Apr 30, 2023. class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: String, isInCurrentThread: Boolean = true) : EventEmitter( - runtime.bridges, runtime.timers.timerForCurrentThread.takeIf { isInCurrentThread } + AutoJs.instance.runtime.bridges, AutoJs.instance.runtime.timers.timerForCurrentThread.takeIf { isInCurrentThread } ), okhttp3.WebSocket { private var maxRebuildTimes = Int.MAX_VALUE private var currentRebuildTimes = 0 + private var isExitOnClose = false + private var exitOnCloseTimeout = DEFAULT_EXIT_ON_CLOSE_TIMEOUT private var listener: WebSocketMessageListener private lateinit var webSocket: okhttp3.WebSocket @@ -32,16 +33,17 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str private fun build() { webSocket = client.newWebSocket(Builder().url(url).build(), listener) + instances.add(WeakReference(this)) } fun rebuild() { cancel() if (currentRebuildTimes < maxRebuildTimes) { build() + currentRebuildTimes += 1 } else { - emit("max_rebuild", maxRebuildTimes, this) + emit(EVENT_MAX_REBUILDS, maxRebuildTimes, this) } - currentRebuildTimes += 1 } fun rebuild(maxRebuildTimes: Int) { @@ -53,7 +55,10 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str override fun close(code: Int, reason: String?) = webSocket.close(code, reason) - fun close(code: Int) = webSocket.close(code, null) + @JvmOverloads + fun close(code: Int = CODE_CLOSE_NORMAL) = webSocket.close(code, null) + + fun close(reason: String?) = webSocket.close(CODE_CLOSE_NORMAL, reason) override fun queueSize() = webSocket.queueSize() @@ -63,34 +68,47 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str override fun send(bytes: ByteString) = webSocket.send(bytes) + override fun on(eventName: String, listener: Any) = this.also { super.on(eventName, listener) } + override fun once(eventName: String, listener: Any) = this.also { super.once(eventName, listener) } + + @JvmOverloads + fun exitOnClose(isExitOnClose: Boolean = true) { + this.isExitOnClose = isExitOnClose + } + + fun exitOnClose(timeout: Long) { + exitOnClose(true) + exitOnCloseTimeout = maxOf(0, timeout) + } + inner class WebSocketMessageListener(private val ws: WebSocket) : WebSocketListener() { - override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) { - ws.emit("closed", code, reason, ws) - } - - override fun onClosing(webSocket: okhttp3.WebSocket, code: Int, reason: String) { - ws.emit("closing", code, reason, ws) - } - - override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) { - Log.w(Companion.TAG, "onFailure") - t.printStackTrace() - ws.emit("failure", t, response, ws) + override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) { + ws.emit(EVENT_OPEN, response, ws) } override fun onMessage(webSocket: okhttp3.WebSocket, text: String) { - ws.emit("message", text, ws) - ws.emit("text", text, ws) + ws.emit(EVENT_MESSAGE, text, ws) + ws.emit(EVENT_TEXT, text, ws) } override fun onMessage(webSocket: okhttp3.WebSocket, bytes: ByteString) { - ws.emit("message", bytes, ws) - ws.emit("bytes", bytes, ws) + ws.emit(EVENT_MESSAGE, bytes, ws) + ws.emit(EVENT_BYTES, bytes, ws) } - override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) { - ws.emit("open", response, ws) + override fun onClosing(webSocket: okhttp3.WebSocket, code: Int, reason: String) { + ws.emit(EVENT_CLOSING, code, reason, ws) + } + + override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) { + ws.emit(EVENT_CLOSED, code, reason, ws) + } + + override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) { + Log.w(TAG, "onFailure") + t.printStackTrace() + ws.emit(EVENT_FAILURE, t, response, ws) } } @@ -99,6 +117,139 @@ class WebSocket @JvmOverloads constructor(val client: OkHttpClient, val url: Str private val TAG = WebSocket::class.java.simpleName + private val instances = ArrayList>() + + /** + * Successful operation / regular socket shutdown. + * + * zh-CN: 成功操作或常规的 Socket 关闭. + */ + const val CODE_CLOSE_NORMAL = 1000 + + /** + * Client is leaving (browser tab closing). + * + * zh-CN: 终端正在处于移除状态, 服务端或客户端即将不可用. + */ + const val CODE_CLOSE_GOING_AWAY = 1001 + + /** + * Endpoint received a malformed frame. + * + * zh-CN: 终端因协议错误或无效帧而即将终止连接. + */ + const val CODE_CLOSE_PROTOCOL_ERROR = 1002 + + /** + * Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame). + * + * zh-CN: 终端因帧数据类型不支持而即将终止连接. + */ + const val CODE_CLOSE_UNSUPPORTED = 1003 + + /** + * Expected close status, received none. + * + * zh-CN: 不包含错误原因, 仅代表已经关闭的状态. + */ + const val CODE_CLOSED_NO_STATUS = 1005 + + /** + * No close code frame has been receieved. + * + * zh-CN: 异常关闭 (如浏览器关闭). + */ + const val CODE_CLOSE_ABNORMAL = 1006 + + /** + * Endpoint received inconsistent message (e.g. malformed UTF-8). + * + * zh-CN: 终端接收到不一致的报文 (如异常格式的 UTF-8). + */ + const val CODE_UNSUPPORTED_PAYLOAD = 1007 + + /** + * Generic code used for situations other than 1003 and 1009. + * + * zh-CN: 终端因收到了违反其策略的报文而即将终止连接. + */ + const val CODE_POLICY_VIOLATION = 1008 + + /** + * Endpoint won't process large frame. + * + * zh-CN: 终端因无法处理长度过大的报文而即将终止连接. + */ + const val CODE_CLOSE_TOO_LARGE = 1009 + + /** + * Client wanted an extension which server did not negotiate. + * + * zh-CN: 终端因期望与服务端进行扩展协商而即将终止连接. + */ + const val CODE_MANDATORY_EXTENSION = 1010 + + /** + * Internal server error while operating. + * + * zh-CN: 服务端因发生内部错误而即将终止连接. + */ + const val CODE_SERVER_ERROR = 1011 + + /** + * Server/service is restarting. + * + * zh-CN: 服务端正在重启过程中. + */ + const val CODE_SERVICE_RESTART = 1012 + + /** + * Temporary server condition forced blocking client's request. + * + * zh-CN: 服务端临时拒绝了终端请求. + */ + const val CODE_TRY_AGAIN_LATER = 1013 + + /** + * Server acting as gateway received an invalid response. + * + * zh-CN: 网关服务器接收到无效的请求. + */ + const val CODE_BAD_GATEWAY = 1014 + + /** + * Transport Layer Security handshake failure. + * + * zh-CN: TLS 握手失败 (如服务端证书未通过验证等). + */ + const val CODE_TLS_HANDSHAKE_FAIL = 1015 + + const val EVENT_CLOSED = "closed" + const val EVENT_CLOSING = "closing" + const val EVENT_FAILURE = "failure" + const val EVENT_TEXT = "text" + const val EVENT_MESSAGE = "message" + const val EVENT_BYTES = "bytes" + const val EVENT_OPEN = "open" + const val EVENT_MAX_REBUILDS = "max_rebuilds" + + private const val DEFAULT_EXIT_ON_CLOSE_TIMEOUT = 0L + + @JvmStatic + fun onExit(reason: String?) { + val wsList = instances.mapNotNull { ref -> ref.get() } + if (wsList.isNotEmpty()) { + Log.d(TAG, "onExit ready") + wsList.forEach { + val r = { + Log.d(TAG, "onExit triggered after delayed") + if (it.isExitOnClose) it.close(CODE_CLOSE_NORMAL, reason) + } + AutoJs.instance.runtime.uiHandler.postDelayed(r, it.exitOnCloseTimeout) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/engine/ScriptEngineService.java b/app/src/main/java/org/autojs/autojs/engine/ScriptEngineService.java index b0cbfd28..db8f0357 100644 --- a/app/src/main/java/org/autojs/autojs/engine/ScriptEngineService.java +++ b/app/src/main/java/org/autojs/autojs/engine/ScriptEngineService.java @@ -179,11 +179,12 @@ public class ScriptEngineService { return mScriptEngineManager.stopAll(); } - public void stopAllAndToast() { + public int stopAllAndToast() { int n = stopAll(); if (n > 0) { mUiHandler.toast(mContext.getResources().getQuantityString(R.plurals.text_already_stop_n_scripts, n, n)); } + return n; } public Set getEngines() { diff --git a/app/src/main/java/org/autojs/autojs/event/GlobalKeyObserver.java b/app/src/main/java/org/autojs/autojs/event/GlobalKeyObserver.java index a4d6ebf7..b02168ba 100644 --- a/app/src/main/java/org/autojs/autojs/event/GlobalKeyObserver.java +++ b/app/src/main/java/org/autojs/autojs/event/GlobalKeyObserver.java @@ -27,23 +27,18 @@ public class GlobalKeyObserver implements OnKeyListener, ShellKeyObserver.KeyLis private boolean mVolumeDownFromShell, mVolumeDownFromAccessibility; private boolean mVolumeUpFromShell, mVolumeUpFromAccessibility; - GlobalKeyObserver() { + public GlobalKeyObserver() { AccessibilityService.Companion.getStickOnKeyObserver() .addListener(this); ShellKeyObserver observer = new ShellKeyObserver(); observer.setKeyListener(this); - InputEventObserver.getGlobal(GlobalAppContext.get()).addListener(observer); + InputEventObserver.initObserver(GlobalAppContext.get()).addListener(observer); } - public static GlobalKeyObserver getSingleton() { + public static void initIfNeeded() { if (sSingleton == null) { sSingleton = new GlobalKeyObserver(); } - return sSingleton; - } - - public static void init() { - if (Pref.isUseVolumeControlRunningEnabled()) getSingleton(); } public void onVolumeUp() { diff --git a/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.java b/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.java index c5502611..ba339c67 100644 --- a/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.java +++ b/app/src/main/java/org/autojs/autojs/external/receiver/DynamicBroadcastReceivers.java @@ -1,5 +1,6 @@ package org.autojs.autojs.external.receiver; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IntentFilter; @@ -30,13 +31,22 @@ public class DynamicBroadcastReceivers { private final Context mContext; + @SuppressLint("UnspecifiedRegisterReceiverFlag") public DynamicBroadcastReceivers(Context context) { mContext = context; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS), Context.RECEIVER_NOT_EXPORTED); + } else { + mContext.registerReceiver(mDefaultActionReceiver, createIntentFilter(StaticBroadcastReceiver.ACTIONS)); + } IntentFilter filter = createIntentFilter(StaticBroadcastReceiver.PACKAGE_ACTIONS); filter.addDataScheme("package"); - mContext.registerReceiver(mPackageActionReceiver, filter); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mContext.registerReceiver(mPackageActionReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + mContext.registerReceiver(mPackageActionReceiver, filter); + } } } @@ -120,6 +130,7 @@ public class DynamicBroadcastReceivers { } } + @SuppressLint("UnspecifiedRegisterReceiverFlag") boolean register() { if (actions.isEmpty()) return false; @@ -128,7 +139,11 @@ public class DynamicBroadcastReceivers { LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(mContext); broadcastManager.registerReceiver(receiver, intentFilter); } else { - mContext.registerReceiver(receiver, intentFilter); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED); + } else { + mContext.registerReceiver(receiver, intentFilter); + } } Log.d(LOG_TAG, "register: " + actions); return true; diff --git a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java index 5d63f2fe..ec554732 100644 --- a/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java +++ b/app/src/main/java/org/autojs/autojs/network/UpdateChecker.java @@ -316,7 +316,7 @@ public class UpdateChecker { neutralButton.setOnClickListener(v -> new MaterialDialog.Builder(context) .title(R.string.text_prompt) .content(R.string.prompt_add_ignored_version) - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .positiveText(R.string.dialog_button_confirm) .positiveColorRes(R.color.dialog_button_warn) .onPositive((dPrompt, which) -> { @@ -525,7 +525,7 @@ public class UpdateChecker { this .title(title) .content(content) - .positiveText(R.string.text_back) + .positiveText(R.string.dialog_button_dismiss) .cancelable(false); } @@ -540,7 +540,7 @@ public class UpdateChecker { .content(R.string.text_getting_release_notes) .neutralText(R.string.dialog_button_ignore_current_update) .neutralColor(context.getColor(R.color.dialog_button_warn)) - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .negativeColor(context.getColor(R.color.dialog_button_default)) .positiveText(R.string.dialog_button_update_now) .positiveColor(context.getColor(R.color.dialog_button_unavailable)) diff --git a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java index 6a6f2b3d..a6dcee19 100644 --- a/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java +++ b/app/src/main/java/org/autojs/autojs/network/download/DownloadManager.java @@ -157,9 +157,9 @@ public class DownloadManager { .onNeutral((dialog, which) -> new MaterialDialog.Builder(context) .title(R.string.text_prompt) .content(R.string.text_download_interruption_warning) - .negativeText(R.string.text_back) + .negativeText(R.string.dialog_button_back) .negativeColorRes(R.color.dialog_button_hint) - .positiveText(R.string.text_continue) + .positiveText(R.string.dialog_button_continue) .positiveColorRes(R.color.dialog_button_caution) .onPositive((d2, which2) -> { dialog.getActionButton(DialogAction.POSITIVE).performClick(); diff --git a/app/src/main/java/org/autojs/autojs/permission/DisplayOverOtherAppsPermission.kt b/app/src/main/java/org/autojs/autojs/permission/DisplayOverOtherAppsPermission.kt index 60ab45a6..a565470e 100644 --- a/app/src/main/java/org/autojs/autojs/permission/DisplayOverOtherAppsPermission.kt +++ b/app/src/main/java/org/autojs/autojs/permission/DisplayOverOtherAppsPermission.kt @@ -46,7 +46,7 @@ class DisplayOverOtherAppsPermission(override val context: Context) : Permission } val r = Runnable { toggle() - ViewUtils.showToast(context, R.string.error_no_draw_overlays_permission) + ViewUtils.showToast(context, R.string.error_no_display_over_other_apps_permission) } if (Looper.myLooper() != Looper.getMainLooper()) { Handler(Looper.getMainLooper()).post(r) diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginResponseHandler.java b/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginResponseHandler.java index 7fc6a054..5232a567 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginResponseHandler.java +++ b/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginResponseHandler.java @@ -206,7 +206,7 @@ public class DevPluginResponseHandler implements Handler { private void copyDir(File fromDir, File toDir) throws FileNotFoundException { toDir.mkdirs(); File[] files = fromDir.listFiles(); - if (files == null || files.length == 0) { + if (files == null) { return; } for (File file : files) { diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginService.kt b/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginService.kt index edd74589..97f0e2b8 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginService.kt +++ b/app/src/main/java/org/autojs/autojs/pluginclient/DevPluginService.kt @@ -88,10 +88,10 @@ class DevPluginService(val context: Context) { } @AnyThread - fun connectToRemoteServer(host: String, clientModeItem: DrawerMenuDisposableItem?) = connectToRemoteServer(host, clientModeItem, false) + fun connectToRemoteServer(context: Context, host: String, clientModeItem: DrawerMenuDisposableItem?) = connectToRemoteServer(context, host, clientModeItem, false) @AnyThread - fun connectToRemoteServer(host: String, clientModeItem: DrawerMenuDisposableItem?, ignoreExceptions: Boolean): Observable { + fun connectToRemoteServer(context: Context, host: String, clientModeItem: DrawerMenuDisposableItem?, ignoreExceptions: Boolean): Observable { try { var port = Port.PC_SERVER var ip = host @@ -101,7 +101,7 @@ class DevPluginService(val context: Context) { ip = host.substring(0, i) } return Observable - .just(JsonSocketClient(this, ip, port)) + .just(JsonSocketClient(this, context, ip, port)) .observeOn(Schedulers.newThread()) .doOnNext { jsonSocketClient -> try { @@ -179,9 +179,10 @@ class DevPluginService(val context: Context) { } } - @AnyThread // FIXME by SuperMonster003 as of Dec 29, 2021. + // FIXME by SuperMonster003 as of Dec 29, 2021. // ! Would print double (may be even more times) the amount of // ! messages on VSCode when multi connection were established. + @AnyThread fun print(log: String?) { jsonSocketClient?.writeLog(log) jsonSocketServer?.writeLog(log) diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt index 3b09d24c..ca3761ae 100644 --- a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt +++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocketClient.kt @@ -1,6 +1,7 @@ package org.autojs.autojs.pluginclient import android.app.Activity +import android.content.Context import android.text.util.Linkify import android.util.Log import androidx.annotation.MainThread @@ -24,7 +25,7 @@ import java.net.SocketTimeoutException import java.util.concurrent.Executors -class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : JsonSocket(service) { +class JsonSocketClient(service: DevPluginService?, private val ctx: Context, host: String?, port: Int) : JsonSocket(service) { private val jsonSocketExecutor = Executors.newSingleThreadExecutor() @@ -83,6 +84,8 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J }, HANDSHAKE_TIMEOUT.toLong()) } + override fun getContext() = ctx + private fun onHello(message: JsonObject) { var currentVersion: String? = null val requiredVersion = BuildConfig.VSCODE_EXT_REQUIRED_VERSION @@ -127,7 +130,7 @@ class JsonSocketClient(service: DevPluginService?, host: String?, port: Int) : J MaterialDialog.Builder(activity) .title(activity.getString(R.string.text_connection_cannot_be_established)) .content(msg) - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_dismiss) .build() .also { it.contentView?.apply { diff --git a/app/src/main/java/org/autojs/autojs/pref/Pref.kt b/app/src/main/java/org/autojs/autojs/pref/Pref.kt index 5a80ca5c..f3aece9f 100644 --- a/app/src/main/java/org/autojs/autojs/pref/Pref.kt +++ b/app/src/main/java/org/autojs/autojs/pref/Pref.kt @@ -37,9 +37,6 @@ object Pref { if (key == key(R.string.key_guard_mode)) { AccessibilityConfig.refreshUnintendedGuardState() } - if (key == key(R.string.key_use_volume_control_record) || key == key(R.string.key_use_volume_control_running) && isUseVolumeControlRunningEnabled) { - GlobalKeyObserver.init() - } } init { @@ -249,9 +246,15 @@ object Pref { @JvmStatic fun putBoolean(@KeyRes keyRes: Int, value: Boolean) = putBoolean(key(keyRes), value) + @JvmStatic + fun putBooleanSync(@KeyRes keyRes: Int, value: Boolean) = putBooleanSync(key(keyRes), value) + @JvmStatic fun putBoolean(key: String?, value: Boolean) = sPref.edit().putBoolean(key, value).apply() + @JvmStatic + fun putBooleanSync(key: String?, value: Boolean) = sPref.edit().putBoolean(key, value).commit() + @JvmStatic fun getBoolean(@KeyRes keyRes: Int, defValue: Boolean) = getBoolean(key(keyRes), defValue) @@ -264,12 +267,24 @@ object Pref { @JvmStatic fun putInt(key: String?, value: Int) = sPref.edit().putInt(key, value).apply() + @JvmStatic + fun putIntSync(key: String?, value: Int) = sPref.edit().putInt(key, value).commit() + + @JvmStatic + fun putFloat(key: String?, value: Float) = sPref.edit().putFloat(key, value).apply() + + @JvmStatic + fun putFloatSync(key: String?, value: Float) = sPref.edit().putFloat(key, value).commit() + @JvmStatic fun getInt(@KeyRes keyRes: Int, defValue: Int): Int = getInt(key(keyRes), defValue) @JvmStatic fun getInt(key: String?, defValue: Int): Int = sPref.getInt(key, defValue) + @JvmStatic + fun getFloat(key: String?, defValue: Float): Float = sPref.getFloat(key, defValue) + @JvmStatic fun putLong(@KeyRes keyRes: Int, value: Long) = sPref.edit().putLong(key(keyRes), value).apply() diff --git a/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.java b/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.java index b93c5261..c0edd6de 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.java +++ b/app/src/main/java/org/autojs/autojs/runtime/ScriptRuntime.java @@ -22,6 +22,7 @@ import org.autojs.autojs.core.image.Colors; import org.autojs.autojs.core.image.ImageWrapper; import org.autojs.autojs.core.image.capture.ScreenCaptureRequester; import org.autojs.autojs.core.looper.Loopers; +import org.autojs.autojs.core.web.WebSocket; import org.autojs.autojs.engine.ScriptEngineService; import org.autojs.autojs.lang.ThreadCompat; import org.autojs.autojs.pio.PFiles; @@ -39,11 +40,12 @@ import org.autojs.autojs.runtime.api.Files; import org.autojs.autojs.runtime.api.Floaty; import org.autojs.autojs.runtime.api.Images; import org.autojs.autojs.runtime.api.Media; -import org.autojs.autojs.runtime.api.MlKitOCR; -import org.autojs.autojs.runtime.api.PaddleOCR; +import org.autojs.autojs.runtime.api.OcrMLKit; +import org.autojs.autojs.runtime.api.OcrPaddle; import org.autojs.autojs.runtime.api.Plugins; import org.autojs.autojs.runtime.api.ProcessShell; import org.autojs.autojs.runtime.api.ScreenMetrics; +import org.autojs.autojs.runtime.api.ScriptToast; import org.autojs.autojs.runtime.api.Sensors; import org.autojs.autojs.runtime.api.Threads; import org.autojs.autojs.runtime.api.Timers; @@ -242,10 +244,13 @@ public class ScriptRuntime { private final Images images; @ScriptVariable - public final MlKitOCR mlKitOCR; + public final ScriptToast toast; @ScriptVariable - public final PaddleOCR paddleOCR; + public final OcrMLKit ocrMLKit; + + @ScriptVariable + public final OcrPaddle ocrPaddle; private static WeakReference applicationContext; private final Map mProperties = new ConcurrentHashMap<>(); @@ -281,8 +286,9 @@ public class ScriptRuntime { media = new Media(context, this); plugins = new Plugins(context, this); - mlKitOCR = new MlKitOCR(); - paddleOCR = new PaddleOCR(); + ocrMLKit = new OcrMLKit(); + ocrPaddle = new OcrPaddle(); + toast = new ScriptToast(context, this); } public void init() { @@ -327,10 +333,6 @@ public class ScriptRuntime { return accessibilityBridge; } - public void toast(final String text) { - uiHandler.toast(text); - } - public void sleep(long millis) { try { Thread.sleep(millis); @@ -536,6 +538,8 @@ public class ScriptRuntime { } }); + ignoresException(() -> WebSocket.onExit("Triggered by " + ScriptRuntime.class.getSimpleName())); + // @Hint by 抠脚本人 on Jul 10, 2023. // ! 清空无障碍事件. ignoresException(AccessibilityService::clearAccessibilityEventCallback); @@ -558,8 +562,8 @@ public class ScriptRuntime { ignoresException(loopers::recycle); ignoresException(this::recycleShell); ignoresException(images::releaseScreenCapturer); - ignoresException(mlKitOCR::release); - ignoresException(paddleOCR::release); + ignoresException(ocrMLKit::release); + ignoresException(ocrPaddle::release); ignoresException(sensors::unregisterAll); ignoresException(timers::recycle); ignoresException(ui::recycle); diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/AbstractShell.java b/app/src/main/java/org/autojs/autojs/runtime/api/AbstractShell.java index a3fb8873..e44d2ac6 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/AbstractShell.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/AbstractShell.java @@ -44,8 +44,8 @@ public abstract class AbstractShell { this(false); } - public AbstractShell(boolean root) { - this(null, root); + public AbstractShell(boolean isExecWithRoot) { + this(null, isExecWithRoot); } public AbstractShell(Context context, boolean isExecWithRoot) { @@ -67,9 +67,9 @@ public abstract class AbstractShell { public abstract void exit(); public void SetTouchDevice(int touchDevice) { - if (mTouchDevice > 0) - return; - mTouchDevice = touchDevice; + if (mTouchDevice <= 0) { + mTouchDevice = touchDevice; + } } public void SendEvent(int type, int code, int value) { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/AppUtils.kt b/app/src/main/java/org/autojs/autojs/runtime/api/AppUtils.kt index ab2b279e..055fb93c 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/AppUtils.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/AppUtils.kt @@ -219,8 +219,10 @@ class AppUtils { enum class BroadcastShortForm(val shortName: String) { INSPECT_LAYOUT_BOUNDS("inspect_layout_bounds"), + LAYOUT_BOUNDS("layout_bounds"), BOUNDS("bounds"), INSPECT_LAYOUT_HIERARCHY("inspect_layout_hierarchy"), + LAYOUT_HIERARCHY("layout_hierarchy"), HIERARCHY("hierarchy"), ; diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Device.java b/app/src/main/java/org/autojs/autojs/runtime/api/Device.java index 4c244a95..770b2531 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Device.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Device.java @@ -1,5 +1,7 @@ package org.autojs.autojs.runtime.api; +import static android.content.Context.WINDOW_SERVICE; + import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManager; @@ -7,6 +9,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.media.AudioManager; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -17,6 +20,7 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.view.Display; +import android.view.Surface; import android.view.WindowManager; import androidx.annotation.NonNull; @@ -25,6 +29,8 @@ import androidx.annotation.Nullable; import org.autojs.autojs.pio.PFiles; import org.autojs.autojs.pio.UncheckedIOException; import org.autojs.autojs.util.DeviceUtils; +import org.autojs.autojs.util.RomUtils; +import org.autojs.autojs.util.RomUtils.Brand; import org.autojs.autojs6.R; import java.net.NetworkInterface; @@ -36,6 +42,7 @@ import ezy.assist.compat.SettingsCompat; /** * Created by Stardust on 2017/12/2. + * Modified by SuperMonster003 as of Jan 1, 2022. */ public class Device { @@ -87,6 +94,10 @@ public class Device { public static String imei; + public final Manufacturers manufacturers; + public final RomUtils roms; + public final Brand brands; + private final Context mContext; private final Vibrator mVibrator; private PowerManager.WakeLock mWakeLock; @@ -95,6 +106,9 @@ public class Device { public Device(Context context) { mContext = context; mVibrator = context.getSystemService(Vibrator.class); + manufacturers = new Manufacturers(); + roms = RomUtils.INSTANCE; + brands = Brand.INSTANCE; imei = DeviceUtils.getIMEI(context); serial = DeviceUtils.getSerial(); } @@ -237,7 +251,7 @@ public class Device { } public boolean isScreenOn() { - return ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getState() == Display.STATE_ON; + return getDefaultDisplay().getState() == Display.STATE_ON; } public void wakeUpIfNeeded() { @@ -303,6 +317,30 @@ public class Device { mVibrator.cancel(); } + public int getOrientation() { + if (isScreenLandscape()) { + return Configuration.ORIENTATION_LANDSCAPE; + } + if (isScreenPortrait()) { + return Configuration.ORIENTATION_PORTRAIT; + } + return Configuration.ORIENTATION_UNDEFINED; + } + + public int getRotation() { + return getDefaultDisplay().getRotation(); + } + + public boolean isScreenPortrait() { + int rotation = getRotation(); + return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180; + } + + public boolean isScreenLandscape() { + int rotation = getRotation(); + return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; + } + private void checkWriteSettingsPermission() { if (SettingsCompat.canWriteSettings(mContext)) { return; @@ -311,12 +349,16 @@ public class Device { throw new SecurityException(mContext.getString(R.string.error_no_write_settings_permission)); } - private void checkReadPhoneStatePermission() { - if (mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + public void ensureReadPhoneStatePermission() { + if (!hasReadPhoneStatePermission()) { throw new SecurityException(mContext.getString(R.string.error_no_read_phone_state_permission)); } } + public boolean hasReadPhoneStatePermission() { + return mContext.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; + } + // just to avoid warning of null pointer to make android studio happy.. @NonNull @SuppressWarnings("unchecked") @@ -412,4 +454,140 @@ public class Device { '}'; } + private Display getDefaultDisplay() { + return ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); + } + + public boolean isManufacturer(String manufacturer) { + return manufacturers.is(manufacturer); + } + + public class Manufacturers { + + private boolean is(String manufacturer) { + return manufacturer.equalsIgnoreCase(Build.MANUFACTURER); + } + + /** + * HTC. + */ + public boolean isHtc() { + return is("htc"); + } + + /** + * LG. + */ + public boolean isLG() { + return is("lg"); + } + + /** + * 一加. + */ + public boolean isOnePlus() { + return is("oneplus"); + } + + /** + * 三星. + */ + public boolean isSamsung() { + return is("samsung"); + } + + /** + * 中兴. + */ + public boolean isZte() { + return is("zte"); + } + + /** + * 乐视. + */ + public boolean isLetv() { + return is("letv"); + } + + /** + * 华为. + */ + public boolean isHuawei() { + return is("huawei") || isNova() || isHonor(); + } + + /** + * Huawei (华为) Nova. + */ + public boolean isNova() { + return is("nova"); + } + + /** + * Huawei (华为) 荣耀. + */ + public boolean isHonor() { + return is("honor"); + } + + /** + * 小米. + */ + public boolean isXiaomi() { + return is("xiaomi") || brands.isRedmi() || brands.isMiMix(); + } + + /** + * 欧珀. + */ + public boolean isOppo() { + return is("oppo"); + } + + /** + * 索尼. + */ + public boolean isSony() { + return is("sony") || brands.isXperia(); + } + + /** + * 维沃. + */ + public boolean isVivo() { + return is("vivo"); + } + + /** + * 联想. + */ + public boolean isLenovo() { + return is("lenovo"); + } + + /** + * 酷派. + * 宇龙计算机通信科技 (深圳) 有限公司. + */ + public boolean isCoolpad() { + return is("yulong"); + } + + /** + * 锤子. + */ + public boolean isSmartisan() { + return is("smartisan"); + } + + /** + * 魅族. + */ + public boolean isMeizu() { + return is("meizu"); + } + + } + } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Events.java b/app/src/main/java/org/autojs/autojs/runtime/api/Events.java index d615fe25..8d01dfaf 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Events.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Events.java @@ -133,7 +133,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver return; ensureHandler(); mLoopers.waitWhenIdle(true); - mTouchObserver = new TouchObserver(InputEventObserver.getGlobal(mContext)); + mTouchObserver = new TouchObserver(InputEventObserver.initObserver(mContext)); mTouchObserver.setOnTouchEventListener(this); mTouchObserver.observe(); } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Floaty.kt b/app/src/main/java/org/autojs/autojs/runtime/api/Floaty.kt index 229e1318..2ca9a310 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Floaty.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Floaty.kt @@ -18,6 +18,7 @@ import org.autojs.autojs.runtime.exception.ScriptInterruptedException import org.autojs.autojs.tool.UiHandler import org.autojs.autojs.util.ViewUtils.setViewMeasure import org.autojs.autojs6.R +import java.lang.Exception import java.util.concurrent.CopyOnWriteArraySet /** @@ -73,6 +74,13 @@ class Floaty(private val mUiHandler: UiHandler, private val mRuntime: ScriptRunt @ScriptInterface fun requestPermission() = mDisplayOverOtherAppsPerm.request() + @ScriptInterface + fun ensurePermission() { + if (!hasPermission()) { + throw Exception(mContext.getString(R.string.error_no_display_over_other_apps_permission)) + } + } + @Synchronized fun closeAll() { mWindows.apply { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/IPermissionToggleable.kt b/app/src/main/java/org/autojs/autojs/runtime/api/IPermissionToggleable.kt new file mode 100644 index 00000000..5b0e0f86 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/IPermissionToggleable.kt @@ -0,0 +1,74 @@ +package org.autojs.autojs.runtime.api + +import java.lang.Exception + +interface IPermissionToggleable { + + val description: String + + fun has(): Boolean + + fun toggle(forcible: Boolean = false) { + try { + if (!has()) request(forcible) else revoke() + } catch (e: Exception) { + when (e) { + is PermissionRequestException -> { + throw PermissionToggleException("$description can't ..., may be not able to request") + } + is PermissionRevokeException -> { + throw PermissionToggleException("$description can't ..., may be not able to revoke") + } + else -> throw e + } + } + } + + fun request(forcible: Boolean) { + if (forcible) { + try { + revoke() + } catch (e: PermissionRevokeException) { + throw PermissionRequestException("$description can't ...") + } + } + request() + } + + fun request() { + try { + config() + } catch (e: PermissionConfigException) { + throw PermissionRequestException("$description can't ...") + } + } + + fun requestIfNeeded(forcible: Boolean = false) { + if (!has()) request(forcible) + } + + fun revoke() { + try { + config() + } catch (e: PermissionConfigException) { + throw PermissionRevokeException("$description can't ...") + } + } + + fun revokeIfNeeded() { + if (has()) revoke() + } + + fun config() { + throw PermissionConfigException("$description can't ...") + } + + class PermissionRequestException(e: String) : Exception(e) + + class PermissionRevokeException(e: String) : Exception(e) + + class PermissionConfigException(e: String) : Exception(e) + + class PermissionToggleException(e: String) : Exception(e) + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/MlKitOCR.kt b/app/src/main/java/org/autojs/autojs/runtime/api/OcrMLKit.kt similarity index 99% rename from app/src/main/java/org/autojs/autojs/runtime/api/MlKitOCR.kt rename to app/src/main/java/org/autojs/autojs/runtime/api/OcrMLKit.kt index 9a2df074..f9cf02f0 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/MlKitOCR.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/OcrMLKit.kt @@ -11,7 +11,7 @@ import org.autojs.autojs.core.image.ImageWrapper * Created by SuperMonster003 on Mar 18, 2023. */ // @Reference to TonyJiangWJ/Auto.js (https://github.com/TonyJiangWJ/Auto.js) on Mar 18, 2023. -class MlKitOCR { +class OcrMLKit { private var recognizer: TextRecognizer? = null diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/PaddleOCR.java b/app/src/main/java/org/autojs/autojs/runtime/api/OcrPaddle.java similarity index 99% rename from app/src/main/java/org/autojs/autojs/runtime/api/PaddleOCR.java rename to app/src/main/java/org/autojs/autojs/runtime/api/OcrPaddle.java index 0ae79161..6fb536c0 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/PaddleOCR.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/OcrPaddle.java @@ -6,6 +6,7 @@ import android.util.Log; import com.baidu.paddle.lite.ocr.OcrResult; import com.baidu.paddle.lite.ocr.Predictor; + import org.autojs.autojs.app.GlobalAppContext; import org.autojs.autojs.concurrent.VolatileDispose; import org.autojs.autojs.core.image.ImageWrapper; @@ -17,7 +18,7 @@ import java.util.List; * @author TonyJiangWJ * @since 2023-08-06 */ -public class PaddleOCR { +public class OcrPaddle { private final Predictor mPredictor = new Predictor(); public synchronized boolean init(boolean useSlim) { diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Permissions.kt b/app/src/main/java/org/autojs/autojs/runtime/api/Permissions.kt new file mode 100644 index 00000000..881c5cc3 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Permissions.kt @@ -0,0 +1,49 @@ +package org.autojs.autojs.runtime.api + +import android.content.Context +import android.content.Intent +import org.autojs.autojs.util.RomUtils + +class Permissions(private val context: Context) { + + var postNotifications: IPermissionToggleable? = null + var notificationAccess: IPermissionToggleable? = null + var usageStatesAccess: IPermissionToggleable? = null + var ignoreBatteryOptimizations: IPermissionToggleable? = null + var backgroundStart = object : IPermissionToggleable { + override val description = "后台弹出界面 / Start in background" + override fun has() = RomUtils.isBackgroundStartGranted(context) + override fun config() = when { + RomUtils.isMiui() -> { + Intent("miui.intent.action.APP_PERM_EDITOR").apply { + setClassName( + "com.miui.securitycenter", + "com.miui.permcenter.permissions.PermissionsEditorActivity", + ) + putExtra("extra_pkgname", context.packageName) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }.let { context.startActivity(it) } + } + else -> super.config() + } + } + var displayOverOtherApps: IPermissionToggleable? = null + var writeSystemSettings: IPermissionToggleable? = null + var writeSecuritySettings: IPermissionToggleable? = null + var projectMediaAccess: IPermissionToggleable? = null + val isBackgroundStartGranted: Boolean + get() = RomUtils.isBackgroundStartGranted(context) + + // TODO by SuperMonster003 on Aug 28, 2023. + + /* permissions.postNotifications */ + /* permissions.notificationAccess */ + /* permissions.usageStatesAccess */ + /* permissions.ignoreBatteryOptimizations */ + /* permissions.backgroundStart */ + /* permissions.displayOverOtherApps */ + /* permissions.writeSystemSettings */ + /* permissions.writeSecuritySettings */ + /* permissions.projectMediaAccess */ + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/ScreenMetrics.kt b/app/src/main/java/org/autojs/autojs/runtime/api/ScreenMetrics.kt index 1de0b497..158ac34f 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/ScreenMetrics.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/ScreenMetrics.kt @@ -1,15 +1,18 @@ package org.autojs.autojs.runtime.api import android.app.Activity -import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.content.res.Resources import android.graphics.Point import android.util.DisplayMetrics -import android.view.Display -import android.view.Surface +import android.view.Surface.ROTATION_0 +import android.view.WindowManager /** * Created by Stardust on 2017/4/26. */ +@Suppress("unused") class ScreenMetrics(private var designWidth: Int, private var designHeight: Int) { constructor() : this(0, 0) @@ -21,79 +24,111 @@ class ScreenMetrics(private var designWidth: Int, private var designHeight: Int) @JvmOverloads fun scaleX(x: Int, width: Int = designWidth) = when { - width == 0 || activity == null -> x + width == 0 || !isInitialized -> x else -> x * deviceScreenWidth / width } @JvmOverloads fun scaleY(y: Int, height: Int = designHeight) = when { - height == 0 || activity == null -> y + height == 0 || !isInitialized -> y else -> y * deviceScreenHeight / height } @JvmOverloads fun rescaleX(x: Int, width: Int = designWidth) = when { - width == 0 || activity == null -> x + width == 0 || !isInitialized -> x else -> x * width / deviceScreenWidth } @JvmOverloads fun rescaleY(y: Int, height: Int = designHeight) = when { - height == 0 || activity == null -> y + height == 0 || !isInitialized -> y else -> y * height / deviceScreenHeight } companion object { - private val metrics: DisplayMetrics = DisplayMetrics() + private var mWindowManager: WindowManager? = null + private var mResources: Resources? = null - private var activity: Activity? = null + private var isInitialized = false @Suppress("DEPRECATION") - private val defaultDisplay: Display? - get() = activity?.windowManager?.defaultDisplay - @JvmStatic val rotation: Int - get() = defaultDisplay?.rotation ?: 0 + get() = mWindowManager?.defaultDisplay?.rotation ?: ROTATION_0 @JvmStatic + val orientation: Int + get() = mResources?.configuration?.orientation ?: ORIENTATION_PORTRAIT + + @JvmStatic + val isScreenPortrait: Boolean + get() = orientation == ORIENTATION_PORTRAIT + + @JvmStatic + val isScreenLandscape: Boolean + get() = orientation == ORIENTATION_LANDSCAPE + + @JvmStatic + @Suppress("DEPRECATION") val deviceScreenWidth: Int - get() = toOriAwarePoint(metrics.widthPixels, metrics.heightPixels).x + get() { + mResources?.displayMetrics?.widthPixels?.takeIf { it > 0 }?.let { return it } - @JvmStatic - val deviceScreenHeight: Int - get() = toOriAwarePoint(metrics.widthPixels, metrics.heightPixels).y + val metricsLegacy = DisplayMetrics() + val display = mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) } + metricsLegacy.widthPixels.takeIf { it > 0 }?.let { return it } - @JvmStatic - val deviceScreenDensity: Int - get() = metrics.densityDpi + display?.width?.takeIf { it > 0 }?.let { return it } - @JvmStatic - fun initIfNeeded(activity: Activity) { - this.activity ?: let { - this.activity = activity - @Suppress("DEPRECATION") - defaultDisplay?.getRealMetrics(this.metrics) + return 0 } + + @JvmStatic + @Suppress("DEPRECATION") + val deviceScreenHeight: Int + get() { + mResources?.displayMetrics?.heightPixels?.takeIf { it > 0 }?.let { return it } + + val metricsLegacy = DisplayMetrics() + val display = mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) } + metricsLegacy.heightPixels.takeIf { it > 0 }?.let { return it } + + display?.height?.takeIf { it > 0 }?.let { return it } + + return 0 + } + + @JvmStatic + @Suppress("DEPRECATION") + val deviceScreenDensity: Int + get() { + val metricsLegacy = DisplayMetrics() + mWindowManager?.defaultDisplay?.apply { getRealMetrics(metricsLegacy) } + return metricsLegacy.densityDpi + } + + @JvmStatic + fun init(activity: Activity) { + mWindowManager = activity.windowManager + mResources = activity.resources + isInitialized = true } - @JvmStatic - fun isScreenLandscape() = listOf(Surface.ROTATION_90, Surface.ROTATION_270).contains(rotation) - private fun toOriAwarePoint(a: Int, b: Int) = arrayOf(minOf(a, b), maxOf(a, b)) - .apply { if (isScreenLandscape()) reverse() } + .apply { if (isScreenLandscape) reverse() } .let { Point(it[0], it[1]) } @JvmStatic fun getOrientationAwareScreenWidth(orientation: Int) = when (orientation) { - Configuration.ORIENTATION_LANDSCAPE -> deviceScreenHeight + ORIENTATION_LANDSCAPE -> deviceScreenHeight else -> deviceScreenWidth } @JvmStatic fun getOrientationAwareScreenHeight(orientation: Int) = when (orientation) { - Configuration.ORIENTATION_LANDSCAPE -> deviceScreenWidth + ORIENTATION_LANDSCAPE -> deviceScreenWidth else -> deviceScreenHeight } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/ScriptToast.java b/app/src/main/java/org/autojs/autojs/runtime/api/ScriptToast.java new file mode 100644 index 00000000..e5755735 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/ScriptToast.java @@ -0,0 +1,96 @@ +package org.autojs.autojs.runtime.api; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.widget.Toast; + +import org.autojs.autojs.runtime.ScriptRuntime; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by SuperMonster003 on Aug 3, 2023. + */ +public class ScriptToast { + + private static final String TAG = ScriptToast.class.getSimpleName(); + private final Context mContext; + private final ScriptRuntime mScriptRuntime; + private final Handler mUiHandler; + + private static final ConcurrentHashMap pool = new ConcurrentHashMap<>(); + + public ScriptToast(Context context, ScriptRuntime runtime) { + mContext = context; + mScriptRuntime = runtime; + mUiHandler = mScriptRuntime.uiHandler; + } + + // @Caution by SuperMonster003 on Oct 11, 2022. + // ! android.widget.ScriptToast.makeText() doesn't work well on Android API Level 28 (Android 9) [P]. + // ! There hasn't been a solution for this so far. + // ! Tested devices: + // ! 1. SONY XPERIA XZ1 Compact (G8441) + // ! 2. Android Studio AVD (Android 9.0 x86) + public void makeToast(String msg, boolean isLong, boolean isForcible) { + synchronized (ScriptToast.class) { + if (isForcible) { + dismissAll(); + } + Toast toast = Toast.makeText(mContext, msg, isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); + Log.d(TAG + " new toast", toast.toString()); + toast.show(); + Log.d(TAG + " before put", pool.toString()); + pool.put(toast, mScriptRuntime); + Log.d(TAG + " after put", pool.toString()); + addCallback(toast); + } + } + + private void addCallback(Toast toast) { + // FIXME by SuperMonster003 on Aug 3, 2023. + // ! A more graceful way is needed. + // @Hint by SuperMonster003 on Aug 3, 2023. + // ! It is proved that Toast.Callback for Android API Level 30+ + // ! was not a good replacement. + mUiHandler.postDelayed(() -> dismiss(toast), getAccumulatedDuration()); + } + + private long getAccumulatedDuration() { + var sum = 200L; + for (Map.Entry entry : pool.entrySet()) { + Toast toast = entry.getKey(); + sum += toast.getDuration() == Toast.LENGTH_SHORT ? 2_000L : 3_500L; + } + return sum; + } + + private void dismiss(Toast aim) { + Log.d(TAG + " before dismiss", pool.toString()); + for (Map.Entry entry : pool.entrySet()) { + Toast toast = entry.getKey(); + ScriptRuntime scriptRuntime = entry.getValue(); + if (scriptRuntime == mScriptRuntime && toast == aim) { + toast.cancel(); + pool.remove(toast); + } + } + Log.d(TAG + " after dismiss", pool.toString()); + } + + public void dismissAll() { + Log.d(TAG + " before disAll", pool.toString()); + for (Map.Entry entry : pool.entrySet()) { + Toast toast = entry.getKey(); + ScriptRuntime scriptRuntime = entry.getValue(); + if (scriptRuntime == mScriptRuntime) { + toast.cancel(); + pool.remove(toast); + } + } + Log.d(TAG + " after disAll", pool.toString()); + } + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java index 90c0e28d..87239cc9 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Sensors.java @@ -10,7 +10,6 @@ import androidx.annotation.NonNull; import org.autojs.autojs.core.eventloop.EventEmitter; import org.autojs.autojs.core.looper.Loopers; -import org.autojs.autojs.pref.Language; import org.autojs.autojs.runtime.ScriptBridges; import org.autojs.autojs.runtime.ScriptRuntime; import org.autojs.autojs.tool.MapBuilder; @@ -22,8 +21,10 @@ import java.util.Set; /** * Created by Stardust on 2018/2/5. + * Modified by SuperMonster003 as of Dec 5, 2021. + * Modified by aiselp as of Jun 10, 2023. */ -public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler { +public class Sensors extends EventEmitter { public class SensorEventEmitter extends EventEmitter implements SensorEventListener { @@ -83,6 +84,13 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler { private final ScriptBridges mScriptBridges; private final SensorEventEmitter mNoOpSensorEventEmitter; private final ScriptRuntime mScriptRuntime; + private final Loopers.AsyncTask mAsyncTask = new Loopers.AsyncTask("Sensors") { + @Override + public boolean onFinish(@NonNull Loopers loopers) { + return !mSensorEventEmitters.isEmpty(); + } + }; + public Sensors(Context context, ScriptRuntime runtime) { super(runtime.bridges); @@ -90,7 +98,7 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler { mScriptBridges = runtime.bridges; mNoOpSensorEventEmitter = new SensorEventEmitter(runtime.bridges); mScriptRuntime = runtime; - runtime.loopers.addLooperQuitHandler(this); + runtime.loopers.addAsyncTask(mAsyncTask); } public SensorEventEmitter register(String sensorName) { @@ -122,13 +130,8 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler { return emitter; } - @Override - public boolean shouldQuit() { - return mSensorEventEmitters.isEmpty(); - } - public Sensor getSensor(String sensorName) { - sensorName = sensorName.toUpperCase(Language.getPrefLanguage().getLocale()); + sensorName = sensorName.toUpperCase(); Integer type = SENSORS.get(sensorName); type = type == null ? getSensorTypeByReflect(sensorName) : type; return type == null ? null : mSensorManager.getDefaultSensor(type); @@ -159,6 +162,6 @@ public class Sensors extends EventEmitter implements Loopers.LooperQuitHandler { } mSensorEventEmitters.clear(); } - mScriptRuntime.loopers.removeLooperQuitHandler(this); + mScriptRuntime.loopers.removeAsyncTask(mAsyncTask); } } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Services.java b/app/src/main/java/org/autojs/autojs/runtime/api/Services.java new file mode 100644 index 00000000..be36eb03 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Services.java @@ -0,0 +1,20 @@ +package org.autojs.autojs.runtime.api; + +import android.content.Context; + +import org.autojs.autojs.util.RomUtils; + +public class Services { + + private final Context mContext; + + public Services(Context context) { + mContext = context; + } + + // TODO by SuperMonster003 on Aug 28, 2023. + + /* services.a11y (a11y) */ + /* services.foregroundService */ + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Settings.java b/app/src/main/java/org/autojs/autojs/runtime/api/Settings.java new file mode 100644 index 00000000..018608d2 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Settings.java @@ -0,0 +1,32 @@ +package org.autojs.autojs.runtime.api; + +import android.content.Context; + +import org.autojs.autojs.util.RomUtils; + +public class Settings { + + private final Context mContext; + + public Settings(Context context) { + mContext = context; + } + + // TODO by SuperMonster003 on Aug 28, 2023. + + /* settings.floatingButton */ + /* settings.clientMode */ + /* settings.serverMode */ + /* settings.autoNightMode */ + /* settings.nightMode */ + /* settings.themeColor */ + /* settings.language */ + /* settings.documentationSource */ + /* settings.isKeepScreenOnWhenInForeground */ + /* settings.isShowDocumentationLauncherIcon */ + /* settings.isShowHiddenFilesAndFolders */ + /* settings.workingDirectory */ + /* settings.forcibleRootCheck */ + /* ... ... */ + +} diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt index 6a850839..79cf2392 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Threads.kt @@ -1,28 +1,73 @@ package org.autojs.autojs.runtime.api import org.autojs.autojs.concurrent.VolatileDispose +import org.autojs.autojs.core.looper.Loopers import org.autojs.autojs.core.looper.MainThreadProxy import org.autojs.autojs.core.looper.TimerThread import org.autojs.autojs.runtime.ScriptRuntime +import org.autojs.autojs.runtime.exception.ScriptInterruptedException import org.autojs.autojs.util.StringUtils.str import org.autojs.autojs6.R +import org.mozilla.javascript.BaseFunction +import org.mozilla.javascript.Context +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.locks.ReentrantLock /** * Created by Stardust on 2017/12/3. + * Modified by aiselp as of Jun 10, 2023. + * Modified by SuperMonster003 as of Aug 28, 2023. */ class Threads(private val mRuntime: ScriptRuntime) { private val mThreads = HashSet() private val mMainThreadProxy = MainThreadProxy(Thread.currentThread(), mRuntime) private var mSpawnCount = 0 + private var mTaskCount = AtomicLong(0) private var mExit = false + private val looperTask = Loopers.AsyncTask("AsyncTaskThreadPool") + + private val threadPool = Executors.newFixedThreadPool(20, ThreadFactory { + val thread = Thread(fun() { + Context.enter() + try { + it.run() + } finally { + Context.exit() + } + }) + thread.name = mainThread.name + " (AsyncThread)" + thread + }) + val mainThread: Thread = Thread.currentThread() - fun currentThread(): Any = Thread.currentThread().let { thread -> - if (thread === mainThread) mMainThreadProxy else thread + fun currentThread(): Any { + val thread = Thread.currentThread() + return if (thread === mainThread) mMainThreadProxy else thread + } + + fun runTaskForThreadPool(runnable: BaseFunction) { + if (mTaskCount.addAndGet(1) == 1L) mRuntime.loopers.addAsyncTask(looperTask) + threadPool.execute { + try { + runnable.call( + Context.getCurrentContext(), runnable.parentScope, runnable, + emptyArray() + ) + } catch (e: Throwable) { + if (!ScriptInterruptedException.causedByInterrupted(e)) { + mRuntime.console.error("$this: ", e) + } + } finally { + if (mTaskCount.addAndGet(-1) == 0L) { + mRuntime.loopers.removeAsyncTask(looperTask) + } + } + } } fun start(runnable: Runnable): TimerThread { @@ -40,14 +85,11 @@ class Threads(private val mRuntime: ScriptRuntime) { } private fun createThread(runnable: Runnable): TimerThread { - val millis = mRuntime.timers.maxCallbackUptimeMillisForAllThreads - return object : TimerThread(mRuntime, millis, runnable) { - + return object : TimerThread(mRuntime, runnable) { override fun onExit() { synchronized(mThreads) { mThreads.remove(currentThread()) } super.onExit() } - } } @@ -60,6 +102,7 @@ class Threads(private val mRuntime: ScriptRuntime) { fun lock() = ReentrantLock() fun shutDownAll() { + threadPool.shutdownNow() synchronized(mThreads) { mThreads.apply { forEach { it.interrupt() } diff --git a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java index a8ee8b81..6e1db575 100644 --- a/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java +++ b/app/src/main/java/org/autojs/autojs/runtime/api/Timers.java @@ -1,36 +1,33 @@ package org.autojs.autojs.runtime.api; import android.os.Looper; -import android.os.SystemClock; -import org.autojs.autojs.concurrent.VolatileBox; import org.autojs.autojs.core.looper.Timer; import org.autojs.autojs.core.looper.TimerThread; import org.autojs.autojs.runtime.ScriptRuntime; /** * Created by Stardust on 2017/7/21. + * Modified by SuperMonster003 as of May 26, 2022. + * Modified by aiselp as of Jun 10, 2023. */ public class Timers { - private final VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); - private final Threads mThreads; - private final Timer mMainTimer; - private final Timer mUiTimer; + private static final String LOG_TAG = "Timers"; + // private VolatileBox mMaxCallbackUptimeMillisForAllThreads = new VolatileBox<>(0L); + private Threads mThreads; + private Timer mUiTimer; + private ScriptRuntime mRuntime; public Timers(ScriptRuntime runtime) { - mMainTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads); - mUiTimer = new Timer(runtime, mMaxCallbackUptimeMillisForAllThreads, Looper.getMainLooper()); + mUiTimer = new Timer(runtime, Looper.getMainLooper()); mThreads = runtime.threads; + mRuntime = runtime; } public Timer getMainTimer() { - return mMainTimer; - } - - VolatileBox getMaxCallbackUptimeMillisForAllThreads() { - return mMaxCallbackUptimeMillisForAllThreads; + return mRuntime.loopers.getTimer(); } public Timer getTimerForCurrentThread() { @@ -39,19 +36,27 @@ public class Timers { public Timer getTimerForThread(Thread thread) { if (thread == mThreads.getMainThread()) { - return mMainTimer; + return mRuntime.loopers.getTimer(); } Timer timer = TimerThread.getTimerForThread(thread); if (timer == null && Looper.myLooper() == Looper.getMainLooper()) { return mUiTimer; } - return timer; + if (timer == null) { + return mRuntime.loopers.getTimer(); + } else { + return timer; + } } public int setTimeout(Object callback, long delay, Object... args) { return getTimerForCurrentThread().setTimeout(callback, delay, args); } + public int setTimeout(Object callback) { + return setTimeout(callback, 1); + } + public boolean clearTimeout(int id) { return getTimerForCurrentThread().clearTimeout(id); } @@ -60,6 +65,10 @@ public class Timers { return getTimerForCurrentThread().setInterval(listener, interval, args); } + public int setInterval(Object listener) { + return setInterval(listener, 1); + } + public boolean clearInterval(int id) { return getTimerForCurrentThread().clearInterval(id); } @@ -72,17 +81,8 @@ public class Timers { return getTimerForCurrentThread().clearImmediate(id); } - public boolean hasPendingCallbacks() { - // 如果是脚本主线程,则检查所有子线程中的定时回调。mFutureCallbackUptimeMillis用来记录所有子线程中定时最久的一个。 - if (mThreads.getMainThread() == Thread.currentThread()) { - return mMaxCallbackUptimeMillisForAllThreads.get() > SystemClock.uptimeMillis(); - } - // 否则检查当前线程的定时回调 - return getTimerForCurrentThread().hasPendingCallbacks(); - } - public void recycle() { - mMainTimer.removeAllCallbacks(); + mRuntime.loopers.getTimer().removeAllCallbacks(); } } diff --git a/app/src/main/java/org/autojs/autojs/theme/widget/ThemeColorToolbar.java b/app/src/main/java/org/autojs/autojs/theme/widget/ThemeColorToolbar.java index f105191a..5d23b64b 100644 --- a/app/src/main/java/org/autojs/autojs/theme/widget/ThemeColorToolbar.java +++ b/app/src/main/java/org/autojs/autojs/theme/widget/ThemeColorToolbar.java @@ -34,8 +34,8 @@ public class ThemeColorToolbar extends Toolbar implements ThemeColorMutable { private void init() { ThemeColorManager.add(this); - setContentInsetStartWithNavigation(getContext().getResources().getDimensionPixelSize(R.dimen.toolbar_content_inset_start_with_navigation)); + setTitleTextAppearance(getContext(), R.style.TextAppearanceMainTitle); } @Override diff --git a/app/src/main/java/org/autojs/autojs/ui/codegeneration/CodeGenerateDialog.java b/app/src/main/java/org/autojs/autojs/ui/codegeneration/CodeGenerateDialog.java index 394352f5..46a842e4 100644 --- a/app/src/main/java/org/autojs/autojs/ui/codegeneration/CodeGenerateDialog.java +++ b/app/src/main/java/org/autojs/autojs/ui/codegeneration/CodeGenerateDialog.java @@ -91,7 +91,7 @@ public class CodeGenerateDialog extends AppLevelThemeDialogBuilder { DialogUtils.showDialog(builder .title(R.string.text_generated_code) .content(code) - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .positiveText(R.string.dialog_button_copy) .onPositive(((dialog, which) -> { ClipboardUtils.setClip(mServiceContext, code); @@ -102,7 +102,7 @@ public class CodeGenerateDialog extends AppLevelThemeDialogBuilder { DialogUtils.showDialog(builder .title(R.string.text_prompt) .content(R.string.text_failed_to_generate) - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_dismiss) .build()); } } diff --git a/app/src/main/java/org/autojs/autojs/ui/common/NotAskAgainDialog.java b/app/src/main/java/org/autojs/autojs/ui/common/NotAskAgainDialog.java index 3522d4cc..7ddb6d50 100644 --- a/app/src/main/java/org/autojs/autojs/ui/common/NotAskAgainDialog.java +++ b/app/src/main/java/org/autojs/autojs/ui/common/NotAskAgainDialog.java @@ -4,6 +4,7 @@ import android.content.Context; import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; import com.afollestad.materialdialogs.MaterialDialog; @@ -36,6 +37,7 @@ public class NotAskAgainDialog extends MaterialDialog { checkBoxPrompt(context.getString(R.string.text_do_not_show_again), false, (buttonView, isChecked) -> setRemindState(!isChecked)); } + @Nullable public MaterialDialog show() { return mRemind ? super.show() : null; } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt index b406e6d7..65c1ae93 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt @@ -162,7 +162,7 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc MaterialDialog.Builder(this) .title(R.string.text_prompt) .content(R.string.edit_exit_without_save_warn) - .neutralText(R.string.text_back) + .neutralText(R.string.dialog_button_back) .negativeText(R.string.text_exit_directly) .negativeColorRes(R.color.dialog_button_caution) .positiveText(R.string.text_save_and_exit) diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java index b9c26bde..a99993c2 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java @@ -232,7 +232,7 @@ public class EditorMenu { // .onNeutral((dialog, which) -> { // // Hint dialog. // }) - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .onNegative((dialog, which) -> dialog.dismiss()) .positiveText(R.string.dialog_button_confirm) .onPositive((dialog, which) -> dialog.dismiss()) diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt index a3c87095..8d29a5b5 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt @@ -8,6 +8,7 @@ import android.content.ContextWrapper import android.content.Intent import android.content.IntentFilter import android.net.Uri +import android.os.Build import android.os.Bundle import android.os.Parcelable import android.text.TextUtils @@ -167,9 +168,14 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + @SuppressLint("UnspecifiedRegisterReceiverFlag") override fun onAttachedToWindow() { super.onAttachedToWindow() - context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED), Context.RECEIVER_NOT_EXPORTED) + } else { + context.registerReceiver(mOnRunFinishedReceiver, IntentFilter(ACTION_ON_EXECUTION_FINISHED)) + } (context as? HostActivity)?.backPressedObserver?.registerHandler(mFunctionsKeyboardHelper) } @@ -595,7 +601,7 @@ open class EditorView : FrameLayout, OnHintClickListener, ClickCallback, Toolbar private fun showErrorMessage(msg: String) { Snackbar.make(this@EditorView, context.getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG) - .setAction(R.string.text_detail) { LogActivity.launch(context) } + .setAction(R.string.text_details) { LogActivity.launch(context) } .show() } diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyService.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyService.java index a455c299..1cc82cb0 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyService.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyService.java @@ -15,7 +15,6 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * Created by Stardust on 2017/5/1. */ - public class FloatyService extends Service { private static final CopyOnWriteArraySet windows = new CopyOnWriteArraySet<>(); diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyWindow.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyWindow.java index e5bfce6e..b8d5f7e6 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyWindow.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/FloatyWindow.java @@ -11,7 +11,6 @@ import org.opencv.core.Size; /** * Created by Stardust on 2017/5/1. */ - public abstract class FloatyWindow { private WindowManager mWindowManager; private FloatyService mFloatyService; diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloaty.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloaty.java index 8445435c..bf4b1e21 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloaty.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloaty.java @@ -7,7 +7,6 @@ import androidx.annotation.Nullable; /** * Created by Stardust on 2017/4/19. */ - public interface ResizableExpandableFloaty { diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloatyWindow.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloatyWindow.java index dfe20a2e..26efb65e 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloatyWindow.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableExpandableFloatyWindow.java @@ -17,7 +17,6 @@ import org.autojs.autojs6.R; /** * Created by Stardust on 2017/4/18. */ - public class ResizableExpandableFloatyWindow extends FloatyWindow { private static final int INITIAL_WINDOW_PARAM_FLAG = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloaty.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloaty.java index a520cd7f..90fd6550 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloaty.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloaty.java @@ -6,7 +6,6 @@ import android.widget.ImageView; /** * Created by Stardust on 2017/4/30. */ - public interface ResizableFloaty { View inflateView(FloatyService floatyService, ResizableFloatyWindow service); diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloatyWindow.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloatyWindow.java index b9db5a8a..53a8a936 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloatyWindow.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ResizableFloatyWindow.java @@ -17,7 +17,6 @@ import org.autojs.autojs6.R; /** * Created by Stardust on 2017/4/30. */ - public class ResizableFloatyWindow extends FloatyWindow { private View mView; diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ViewStack.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ViewStack.java index fbb4145d..8e1bfbdd 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ViewStack.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/ViewStack.java @@ -7,7 +7,6 @@ import java.util.Stack; /** * Created by Stardust on 2017/3/11. */ - public class ViewStack { public interface CurrentViewSetter { diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/WindowBridge.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/WindowBridge.java index a0916689..410a9bdc 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/WindowBridge.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/WindowBridge.java @@ -7,7 +7,6 @@ import android.view.WindowManager; /** * Created by Stardust on 2017/4/18. */ - public interface WindowBridge { int getX(); diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/DragGesture.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/DragGesture.java index 8dedea11..ce08a1fc 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/DragGesture.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/DragGesture.java @@ -10,7 +10,6 @@ import org.autojs.autojs.ui.enhancedfloaty.WindowBridge; /** * Created by Stardust on 2017/4/18. */ - public class DragGesture extends GestureDetector.SimpleOnGestureListener { protected WindowBridge mWindowBridge; diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/ResizeGesture.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/ResizeGesture.java index 730d50b3..d72d6fbf 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/ResizeGesture.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/gesture/ResizeGesture.java @@ -13,7 +13,6 @@ import org.autojs.autojs.ui.enhancedfloaty.WindowBridge; /** * Created by Stardust on 2017/4/18. */ - public class ResizeGesture extends GestureDetector.SimpleOnGestureListener { public static ResizeGesture enableResize(View resizer, @Nullable View resizableView, WindowBridge windowBridge) { diff --git a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/util/FloatingWindowPermissionUtil.java b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/util/FloatingWindowPermissionUtil.java index 40d86d50..50e7157e 100644 --- a/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/util/FloatingWindowPermissionUtil.java +++ b/app/src/main/java/org/autojs/autojs/ui/enhancedfloaty/util/FloatingWindowPermissionUtil.java @@ -11,7 +11,6 @@ import android.view.WindowManager; /** * Created by Stardust on 2017/3/10. */ - public class FloatingWindowPermissionUtil { public static void goToFloatingWindowPermissionSettingIfNeeded(Context context) { diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java index 9ad72e5d..48475d50 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenu.java @@ -2,6 +2,7 @@ package org.autojs.autojs.ui.floating; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.text.TextUtils; import android.view.ContextThemeWrapper; import android.view.View; @@ -43,6 +44,7 @@ import org.autojs.autojs6.databinding.CircularActionMenuBinding; import org.greenrobot.eventbus.EventBus; import org.jdeferred.Deferred; import org.jdeferred.impl.DeferredObject; +import org.jetbrains.annotations.NotNull; import java.text.MessageFormat; @@ -166,9 +168,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp }); binding.stopAllScripts.setOnClickListener(v -> { mWindow.collapse(); - if (AutoJs.getInstance().getScriptEngineManager().getEngines().size() > 0) { - AutoJs.getInstance().getScriptEngineService().stopAllAndToast(); - } else { + if (AutoJs.getInstance().getScriptEngineService().stopAllAndToast() <= 0) { ViewUtils.showToast(mContext, R.string.text_no_scripts_to_stop_running); } }); @@ -225,7 +225,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp } private void initFloaty() { - mWindow = new CircularMenuWindow(mContext, new CircularMenuFloaty() { + mWindow = new CircularMenuWindow(new CircularMenuFloaty() { @Override public CircularActionView inflateActionView(FloatyService service, CircularMenuWindow window) { CircularActionView actionView = (CircularActionView) View.inflate(service, R.layout.circular_action_view, null); @@ -283,8 +283,18 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp } public void closeAndSaveState() { + boolean state = FloatyWindowManger.isCircularMenuShowing(); + Pref.putBooleanSync(R.string.key_floating_menu_shown, state); + savePosition(); close(); - Pref.putBoolean(R.string.key_floating_menu_shown, false); + } + + public void savePosition() { + mWindow.savePosition(); + } + + public void savePosition(@NotNull Configuration newConfig) { + mWindow.savePosition(newConfig); } private AccessibilityTool getAccessibilityTool() { diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenuWindow.java b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenuWindow.java index 920e02e9..365272b4 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenuWindow.java +++ b/app/src/main/java/org/autojs/autojs/ui/floating/CircularMenuWindow.java @@ -1,32 +1,32 @@ package org.autojs.autojs.ui.floating; -import android.content.Context; -import android.content.SharedPreferences; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; + +import android.content.res.Configuration; import android.graphics.PixelFormat; import android.view.Gravity; -import android.view.OrientationEventListener; import android.view.View; import android.view.WindowManager; -import androidx.preference.PreferenceManager; - +import org.autojs.autojs.pref.Pref; +import org.autojs.autojs.runtime.api.ScreenMetrics; import org.autojs.autojs.ui.enhancedfloaty.FloatyService; import org.autojs.autojs.ui.enhancedfloaty.FloatyWindow; import org.autojs.autojs.ui.enhancedfloaty.WindowBridge; - -import org.autojs.autojs.runtime.api.ScreenMetrics; import org.autojs.autojs.ui.floating.gesture.BounceDragGesture; +import org.jetbrains.annotations.NotNull; public class CircularMenuWindow extends FloatyWindow { - private static final String KEY_POSITION_X = CircularMenuWindow.class.getName() + ".position.x"; - private static final String KEY_POSITION_Y = CircularMenuWindow.class.getName() + ".position.y"; + private static final String KEY_POSITION_X_PERCENT = CircularMenuWindow.class.getName() + ".position.x_percent"; + private static final String KEY_POSITION_Y_PERCENT = CircularMenuWindow.class.getName() + ".position.y_percent"; + private static final String KEY_LAST_ORIENTATION = "key_$_last_orientation"; protected CircularMenuFloaty mFloaty; protected CircularActionMenu mCircularActionMenu; protected CircularActionView mCircularActionView; protected BounceDragGesture mDragGesture; - protected OrientationAwareWindowBridge mActionViewWindowBridge; + protected WindowBridge.DefaultImpl mActionViewWindowBridge; protected WindowBridge mMenuWindowBridge; protected WindowManager.LayoutParams mActionViewWindowLayoutParams; protected WindowManager.LayoutParams mMenuWindowLayoutParams; @@ -34,12 +34,11 @@ public class CircularMenuWindow extends FloatyWindow { protected float mKeepToSideHiddenWidthRadio; protected float mActiveAlpha = 1.0F; protected float mInactiveAlpha = 0.4F; - private final Context mContext; - private OrientationEventListener mOrientationEventListener; - public CircularMenuWindow(Context context, CircularMenuFloaty floaty) { + // private OrientationEventListener mOrientationEventListener; + + public CircularMenuWindow(CircularMenuFloaty floaty) { mFloaty = floaty; - mContext = context; } @Override @@ -51,17 +50,21 @@ public class CircularMenuWindow extends FloatyWindow { initGestures(); setListeners(); setInitialState(); - mOrientationEventListener = new OrientationEventListener(mContext) { - @Override - public void onOrientationChanged(int orientation) { - if (mActionViewWindowBridge.isOrientationChanged(mContext.getResources().getConfiguration().orientation)) { - keepToSide(); - } - } - }; - if (mOrientationEventListener.canDetectOrientation()) { - mOrientationEventListener.enable(); - } + + // @Comment by SuperMonster003 on Aug 2, 2023. + // ! Seems useless. + // ! + // mOrientationEventListener = new OrientationEventListener(mContext) { + // @Override + // public void onOrientationChanged(int orientation) { + // if (mActionViewWindowBridge.isOrientationChanged(mContext.getResources().getConfiguration().orientation)) { + // keepToSide(); + // } + // } + // }; + // if (mOrientationEventListener.canDetectOrientation()) { + // mOrientationEventListener.enable(); + // } } @Override @@ -79,9 +82,9 @@ public class CircularMenuWindow extends FloatyWindow { } private void setInitialState() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); - int y = preferences.getInt(KEY_POSITION_Y, ScreenMetrics.getDeviceScreenHeight() / 2); - mActionViewWindowBridge.updatePosition(mActionViewWindowBridge.getX(), y); + int x = (int) (ScreenMetrics.getDeviceScreenWidth() * Pref.getFloat(KEY_POSITION_X_PERCENT, 0f)); + int y = (int) (ScreenMetrics.getDeviceScreenHeight() * Pref.getFloat(KEY_POSITION_Y_PERCENT, 1 - 0.618f)); + mActionViewWindowBridge.updatePosition(x, y); keepToSide(); } @@ -93,7 +96,7 @@ public class CircularMenuWindow extends FloatyWindow { } private void initWindowBridge() { - mActionViewWindowBridge = new OrientationAwareWindowBridge(mActionViewWindowLayoutParams, getWindowManager(), mCircularActionView, mContext); + mActionViewWindowBridge = new WindowBridge.DefaultImpl(mActionViewWindowLayoutParams, getWindowManager(), mCircularActionView); mMenuWindowBridge = new WindowBridge.DefaultImpl(mMenuWindowLayoutParams, getWindowManager(), mCircularActionMenu); } @@ -213,14 +216,24 @@ public class CircularMenuWindow extends FloatyWindow { close(); } + public void savePosition() { + int x = mActionViewWindowBridge.getX(); + Pref.putFloatSync(KEY_POSITION_X_PERCENT, x / (float) ScreenMetrics.getDeviceScreenWidth()); + int y = mActionViewWindowBridge.getY(); + Pref.putFloatSync(KEY_POSITION_Y_PERCENT, y / (float) ScreenMetrics.getDeviceScreenHeight()); + } + + public void savePosition(@NotNull Configuration configuration) { + int orientation = configuration.orientation; + if (Pref.getInt(KEY_LAST_ORIENTATION, ORIENTATION_PORTRAIT) != orientation) { + Pref.putIntSync(KEY_LAST_ORIENTATION, orientation); + savePosition(); + } + } + public void close() { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); - preferences.edit() - .putInt(KEY_POSITION_X, mActionViewWindowBridge.getX()) - .putInt(KEY_POSITION_Y, mActionViewWindowBridge.getY()) - .apply(); try { - mOrientationEventListener.disable(); + // mOrientationEventListener.disable(); FloatyService.removeWindow(this); FloatyWindowManger.clearCircularMenu(); if (mCircularActionMenu.isAttachedToWindow()) { diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/FloatyWindowManger.java b/app/src/main/java/org/autojs/autojs/ui/floating/FloatyWindowManger.java index dfd82e61..2d8529f6 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/FloatyWindowManger.java +++ b/app/src/main/java/org/autojs/autojs/ui/floating/FloatyWindowManger.java @@ -8,7 +8,6 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.autojs.autojs.core.console.GlobalConsole; import org.autojs.autojs.ui.enhancedfloaty.FloatyService; import org.autojs.autojs.ui.enhancedfloaty.FloatyWindow; @@ -28,6 +27,7 @@ public class FloatyWindowManger { private static DisplayOverOtherAppsPermission sDisplayOverOtherAppsPerm; private static boolean sCircularMenuShown; + public static boolean addWindow(Context context, FloatyWindow window) { context.startService(new Intent(context, FloatyService.class)); getDisplayOverOtherAppsPerm(context).requestIfNeeded(); @@ -78,7 +78,7 @@ public class FloatyWindowManger { context.startService(new Intent(context, FloatyService.class)); setCircularMenuContext(context); } else { - ViewUtils.showToast(context, R.string.error_no_draw_overlays_permission); + ViewUtils.showToast(context, R.string.error_no_display_over_other_apps_permission); getDisplayOverOtherAppsPerm(context).config(); } sCircularMenuShown = true; @@ -94,24 +94,23 @@ public class FloatyWindowManger { public static void hideCircularMenuIfNeeded() { if (isCircularMenuShowing()) { - hideCircularMenu(false); + hideCircularMenu(); } } public static void hideCircularMenu(boolean isSaveState) { - sCircularMenuShown = false; - if (sCircularMenu == null) { - return; - } - CircularMenu menu = sCircularMenu.get(); - if (menu != null) { - if (isSaveState) { - menu.closeAndSaveState(); - } else { - menu.close(); + if (sCircularMenu != null) { + CircularMenu menu = sCircularMenu.get(); + if (menu != null) { + if (isSaveState) { + menu.closeAndSaveState(); + } else { + menu.close(); + } } } clearCircularMenu(); + sCircularMenuShown = false; } public static void clearCircularMenu() { diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/LayoutFloatyWindow.kt b/app/src/main/java/org/autojs/autojs/ui/floating/LayoutFloatyWindow.kt index 884f92c8..cedbf927 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/LayoutFloatyWindow.kt +++ b/app/src/main/java/org/autojs/autojs/ui/floating/LayoutFloatyWindow.kt @@ -4,17 +4,15 @@ import android.content.Context import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup -import org.autojs.autojs.ui.enhancedfloaty.FloatyService import org.autojs.autojs.app.AppLevelThemeDialogBuilder import org.autojs.autojs.app.DialogUtils import org.autojs.autojs.core.accessibility.NodeInfo import org.autojs.autojs.ui.codegeneration.CodeGenerateDialog +import org.autojs.autojs.ui.enhancedfloaty.FloatyService import org.autojs.autojs.ui.floating.layoutinspector.LayoutBoundsFloatyWindow import org.autojs.autojs.ui.floating.layoutinspector.LayoutHierarchyFloatyWindow import org.autojs.autojs.ui.floating.layoutinspector.NodeInfoView import org.autojs.autojs.ui.widget.BubblePopupMenu -import org.autojs.autojs.util.ClipboardUtils -import org.autojs.autojs.util.ViewUtils import org.autojs.autojs6.R abstract class LayoutFloatyWindow(private val rootNode: NodeInfo?, private val context: Context, private val isServiceRelied: Boolean) : FullScreenFloatyWindow() { diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/OrientationAwareWindowBridge.java b/app/src/main/java/org/autojs/autojs/ui/floating/OrientationAwareWindowBridge.java deleted file mode 100644 index a630bd9a..00000000 --- a/app/src/main/java/org/autojs/autojs/ui/floating/OrientationAwareWindowBridge.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.autojs.autojs.ui.floating; - -import android.content.Context; -import android.content.res.Configuration; -import android.view.View; -import android.view.WindowManager; - -import org.autojs.autojs.ui.enhancedfloaty.WindowBridge; - -public class OrientationAwareWindowBridge extends WindowBridge.DefaultImpl { - - - private final Context mContext; - private int mOrientation; - - public OrientationAwareWindowBridge(WindowManager.LayoutParams windowLayoutParams, WindowManager windowManager, View windowView, Context context) { - super(windowLayoutParams, windowManager, windowView); - mContext = context; - mOrientation = mContext.getResources().getConfiguration().orientation; - } - - public boolean isOrientationChanged(int newOrientation) { - if (mOrientation != newOrientation) { - mOrientation = newOrientation; - return true; - } - return false; - } - - @Override - public int getScreenHeight() { - if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - return super.getScreenWidth(); - } else { - return super.getScreenHeight(); - } - } - - @Override - public int getScreenWidth() { - if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - return super.getScreenHeight(); - } else { - return super.getScreenWidth(); - } - } -} diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/gesture/BounceDragGesture.java b/app/src/main/java/org/autojs/autojs/ui/floating/gesture/BounceDragGesture.java index b0fc4f7f..16d9af46 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/gesture/BounceDragGesture.java +++ b/app/src/main/java/org/autojs/autojs/ui/floating/gesture/BounceDragGesture.java @@ -8,6 +8,7 @@ import android.view.animation.BounceInterpolator; import androidx.annotation.NonNull; import org.autojs.autojs.ui.enhancedfloaty.WindowBridge; +import org.autojs.autojs6.R; /** * Created by Stardust on 2017/9/26. @@ -37,11 +38,19 @@ public class BounceDragGesture extends DragGesture { @Override public void keepToEdge() { - int y = Math.min(mWindowBridge.getScreenHeight() - mView.getHeight() - MIN_DY_TO_SCREEN_BOTTOM, Math.max(MIN_DY_TO_SCREEN_TOP, mWindowBridge.getY())); + int side = mView.getContext().getResources().getDimensionPixelSize(R.dimen.side_circular_menu_icon); + int screenHeight = mWindowBridge.getScreenHeight(); + int screenWidth = mWindowBridge.getScreenWidth(); + int hiddenWidth = (int) (getKeepToSideHiddenWidthRadio() * (float) side); + int x = mWindowBridge.getX(); - int hiddenWidth = (int) (getKeepToSideHiddenWidthRadio() * (float) mView.getWidth()); - if (x > mWindowBridge.getScreenWidth() / 2) { - bounce(x, mWindowBridge.getScreenWidth() - mView.getWidth() + hiddenWidth, y); + int y = Math.min( + screenHeight - side - MIN_DY_TO_SCREEN_BOTTOM, + Math.max(MIN_DY_TO_SCREEN_TOP, mWindowBridge.getY()) + ); + + if (x > screenWidth / 2) { + bounce(x, screenWidth - side + hiddenWidth, y); } else { bounce(x, -hiddenWidth, y); } diff --git a/app/src/main/java/org/autojs/autojs/ui/floating/layoutinspector/LayoutBoundsFloatyWindow.kt b/app/src/main/java/org/autojs/autojs/ui/floating/layoutinspector/LayoutBoundsFloatyWindow.kt index fbfe1ca5..14a4135f 100644 --- a/app/src/main/java/org/autojs/autojs/ui/floating/layoutinspector/LayoutBoundsFloatyWindow.kt +++ b/app/src/main/java/org/autojs/autojs/ui/floating/layoutinspector/LayoutBoundsFloatyWindow.kt @@ -13,7 +13,11 @@ import org.autojs.autojs6.R * Created by Stardust on 2017/3/12. * Modified by SuperMonster003 as of Aug 31, 2022. */ -open class LayoutBoundsFloatyWindow @JvmOverloads constructor(private val rootNode: NodeInfo?, private val context: Context, isServiceRelied: Boolean = false) : LayoutFloatyWindow(rootNode, context, isServiceRelied) { +open class LayoutBoundsFloatyWindow @JvmOverloads constructor( + private val rootNode: NodeInfo?, + private val context: Context, + isServiceRelied: Boolean = false, +) : LayoutFloatyWindow(rootNode, context, isServiceRelied) { private lateinit var mLayoutBoundsView: LayoutBoundsView diff --git a/app/src/main/java/org/autojs/autojs/ui/log/LogActivity.java b/app/src/main/java/org/autojs/autojs/ui/log/LogActivity.java index bb84cc34..a31e67b1 100644 --- a/app/src/main/java/org/autojs/autojs/ui/log/LogActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/log/LogActivity.java @@ -1,5 +1,6 @@ package org.autojs.autojs.ui.log; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -23,6 +24,8 @@ public class LogActivity extends BaseActivity { private GlobalConsole mConsoleImpl; + private final int RESULT_CODE_EXPORT = 33128; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -36,6 +39,7 @@ public class LogActivity extends BaseActivity { mConsoleImpl = AutoJs.getInstance().getGlobalConsole(); mConsoleView.setConsole(mConsoleImpl); + mConsoleView.setLogActivity(this); mConsoleView.setPinchToZoomEnabled(true); ConsoleViewBinding consoleViewBinding = ConsoleViewBinding.inflate(getLayoutInflater()); @@ -55,10 +59,30 @@ public class LogActivity extends BaseActivity { return true; } + public void export(String fileName) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + startActivityForResult(intent, RESULT_CODE_EXPORT); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == RESULT_CODE_EXPORT && resultCode == Activity.RESULT_OK) { + mConsoleImpl.export(data.getData()); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_copy) { mConsoleImpl.copyAll(); + } else if (item.getItemId() == R.id.action_send) { + mConsoleImpl.send(); } else if (item.getItemId() == R.id.action_export) { mConsoleImpl.export(); } else if (item.getItemId() == R.id.action_clear) { diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt index b96757c8..9791c873 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt @@ -1,8 +1,7 @@ package org.autojs.autojs.ui.main -import android.Manifest.permission +import android.Manifest import android.annotation.SuppressLint -import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -20,6 +19,7 @@ import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener import com.google.android.material.floatingactionbutton.FloatingActionButton +import org.autojs.autojs.App import org.autojs.autojs.AutoJs import org.autojs.autojs.app.FragmentPagerAdapterBuilder import org.autojs.autojs.app.FragmentPagerAdapterBuilder.StoredFragmentPagerAdapter @@ -62,6 +62,7 @@ import org.autojs.autojs.util.WorkingDirectoryUtils import org.autojs.autojs6.R import org.autojs.autojs6.databinding.ActivityMainBinding import org.greenrobot.eventbus.EventBus +import rikka.shizuku.Shizuku /** * Modified by SuperMonster003 as of Dec 1, 2021. @@ -86,12 +87,51 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { val requestMultiplePermissionsLauncher = registerForActivityResult(RequestMultiplePermissions()) { it.forEach { (key: String, isGranted: Boolean) -> Log.d(TAG, "$key: $isGranted") - if (key == "android.permission.POST_NOTIFICATIONS") { + if (key == Manifest.permission.POST_NOTIFICATIONS) { Pref.putBoolean(R.string.key_post_notification_permission_requested, true) } } } + private val shizukuBinderReceivedListener = { + // if (Shizuku.isPreV11()) { + // binding.text1.setText("Shizuku pre-v11 is not supported") + // } else { + // binding.text1.setText("Binder received") + // } + } + private val shizukuBinderDeadListener = { + // binding.text1.setText("Binder dead") + } + private val shizukuRequestPermissionResultListener: (requestCode: Int, grantResult: Int) -> Unit = { /* requestCode: */ _: Int, /* grantResult */ _: Int -> + // if (grantResult == PackageManager.PERMISSION_GRANTED) { + // switch (requestCode) { + // case REQUEST_CODE_BUTTON1: { + // getUsers(); + // break; + // } + // case REQUEST_CODE_BUTTON4: { + // getSystemProperty(); + // break; + // } + // case REQUEST_CODE_BUTTON7: { + // bindUserService(); + // break; + // } + // case REQUEST_CODE_BUTTON8: { + // unbindUserService(); + // break; + // } + // case REQUEST_CODE_BUTTON9: { + // peekUserService(); + // break; + // } + // } + // } else { + // binding.text1.setText("User denied permission"); + // } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -122,6 +162,11 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { setUpTabViewPager() registerBackPressHandlers() addViewBackground(binding.appBar) + + Log.d("Shizuku", "${App::class.java.simpleName} onCreate | Process=${App.getProcessNameCompat()}") + Shizuku.addBinderReceivedListenerSticky(this.shizukuBinderReceivedListener) + Shizuku.addBinderDeadListener(shizukuBinderDeadListener) + Shizuku.addRequestPermissionResultListener(shizukuRequestPermissionResultListener) } override fun onPostResume() { @@ -206,16 +251,15 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { } fun rebirth(view: View) { - val context = view.context as Activity - context.finish() + val context = view.context as MainActivity context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { context.startActivity(Intent.makeRestartActivityTask(it.component)) } - Process.killProcess(Process.myPid()) + context.exitCompletely() } fun exitCompletely() { - FloatyWindowManger.hideCircularMenu(!isFinishing) + FloatyWindowManger.hideCircularMenuAndSaveState() ForegroundServiceUtils.stopServiceIfNeeded(this, MainActivityForegroundService::class.java) stopService(Intent(this, FloatyService::class.java)) AutoJs.instance.scriptEngineService.stopAll() @@ -240,7 +284,7 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { } private fun getGrantResult(permissions: Array, grantResults: IntArray): Int { - val i = listOf(*permissions).indexOf(permission.READ_EXTERNAL_STORAGE) + val i = listOf(*permissions).indexOf(Manifest.permission.READ_EXTERNAL_STORAGE) return if (i < 0) 2 else grantResults[i] } @@ -255,7 +299,6 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { } } if (!mBackPressObserver.onBackPressed(this)) { - @Suppress("DEPRECATION") super.onBackPressed() } } @@ -341,6 +384,10 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity { override fun onDestroy() { super.onDestroy() EventBus.getDefault().unregister(this) + + Shizuku.removeBinderReceivedListener(this.shizukuBinderReceivedListener) + Shizuku.removeBinderDeadListener(shizukuBinderDeadListener) + Shizuku.removeRequestPermissionResultListener(shizukuRequestPermissionResultListener) } override fun onResume() { diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/CommandBasedPermissionItemHelper.kt b/app/src/main/java/org/autojs/autojs/ui/main/drawer/CommandBasedPermissionItemHelper.kt index 20ba9713..240c4b44 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/CommandBasedPermissionItemHelper.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/CommandBasedPermissionItemHelper.kt @@ -103,7 +103,7 @@ interface CommandBasedPermissionItemHelper : PermissionItemHelper, IPermissionRo ViewUtils.showToast(context, resultRes) } } - .negativeText(R.string.text_back) + .negativeText(R.string.dialog_button_cancel) .onNegative { dialog, _ -> dialog.dismiss() } .positiveText(R.string.text_copy_command) .onPositive { dialog, _ -> diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt index c3d1314b..cd4f992a 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerMenuToggleableItem.kt @@ -68,8 +68,8 @@ open class DrawerMenuToggleableItem : DrawerMenuItem, IToggleableItem { NotAskAgainDialog.Builder(mContext, key) .title(title) .content(it) - .negativeText(R.string.text_back) - .positiveText(R.string.text_continue) + .negativeText(R.string.dialog_button_cancel) + .positiveText(R.string.dialog_button_continue) .onPositive { _, _ -> toggle(aimState) } .dismissListener { sync() } .show() diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt index 49c419e3..6b78f774 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/settings/AboutActivity.kt @@ -27,7 +27,6 @@ import org.autojs.autojs6.databinding.ActivityAboutItemsBinding * Created by Stardust on 2017/2/2. * Modified by SuperMonster003 as of Dec 1, 2021. */ - open class AboutActivity : BaseActivity() { private lateinit var activityBinding: ActivityAboutBinding @@ -121,13 +120,13 @@ open class AboutActivity : BaseActivity() { MaterialDialog.Builder(this) .title(R.string.text_app_and_device_info) .content(DeviceUtils.getDeviceSummaryWithSimpleAppInfo(this)) - .negativeText(R.string.text_back) - .neutralText(R.string.text_copy) + .neutralText(R.string.dialog_button_copy) .onNeutral { d, _ -> ClipboardUtils.setClip(this, d.contentView?.text) ViewUtils.showToast(this, R.string.text_already_copied_to_clip) } .neutralColorRes(R.color.dialog_button_hint) + .negativeText(R.string.dialog_button_dismiss) .build() .apply { window?.setBackgroundDrawableResource(R.color.about_app_dev_info_dialog_background) } .show() @@ -149,7 +148,7 @@ open class AboutActivity : BaseActivity() { LicenseResolver.registerLicense(MozillaPublicLicense20.instance) LicensesDialog.Builder(this) .setTitle(R.string.text_licenses) - .setCloseText(R.string.dialog_button_back) + .setCloseText(R.string.dialog_button_dismiss) .setNotices(R.raw.licenses) .setIncludeOwnLicense(true) .setEnableDarkMode(true) @@ -168,8 +167,8 @@ open class AboutActivity : BaseActivity() { NotAskAgainDialog.Builder(this, key) .title(R.string.text_prompt) .content(R.string.content_github_feedback) - .negativeText(R.string.text_back) - .positiveText(R.string.text_continue) + .negativeText(R.string.dialog_button_cancel) + .positiveText(R.string.dialog_button_continue) .onNegative { d, _ -> d.dismiss() } .onPositive { _, _ -> launchGithubIssuesPage() } .cancelable(false) diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt index 09dfdcf3..9d8df1bb 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt +++ b/app/src/main/java/org/autojs/autojs/ui/settings/CheckForUpdatesPreference.kt @@ -35,6 +35,6 @@ class CheckForUpdatesPreference : MaterialPreference, OnSharedPreferenceChangeLi super.onClick() } - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = notifyChanged() + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) = notifyChanged() } \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/WorkingDirectoryPreference.kt b/app/src/main/java/org/autojs/autojs/ui/settings/WorkingDirectoryPreference.kt index 952c7b4a..15b3453c 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/WorkingDirectoryPreference.kt +++ b/app/src/main/java/org/autojs/autojs/ui/settings/WorkingDirectoryPreference.kt @@ -119,11 +119,11 @@ class WorkingDirectoryPreference : MaterialPreference { mContentView.setText(text) } } - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .show() } } - .negativeText(R.string.dialog_button_back) + .negativeText(R.string.dialog_button_cancel) .negativeColorRes(R.color.dialog_button_default) .onNegative { dHistories, _ -> dHistories.dismiss() } .autoDismiss(false) diff --git a/app/src/main/java/org/autojs/autojs/ui/widget/ViewSwitcher.java b/app/src/main/java/org/autojs/autojs/ui/widget/ViewSwitcher.java index fedd1505..4568049c 100644 --- a/app/src/main/java/org/autojs/autojs/ui/widget/ViewSwitcher.java +++ b/app/src/main/java/org/autojs/autojs/ui/widget/ViewSwitcher.java @@ -7,7 +7,6 @@ import android.view.View; /** * Created by Stardust on 2017/3/11. */ - public class ViewSwitcher extends android.widget.ViewSwitcher { private View mCurrentView; diff --git a/app/src/main/java/org/autojs/autojs/util/JavaUtils.java b/app/src/main/java/org/autojs/autojs/util/JavaUtils.java index ee2a6de3..ec2448b3 100644 --- a/app/src/main/java/org/autojs/autojs/util/JavaUtils.java +++ b/app/src/main/java/org/autojs/autojs/util/JavaUtils.java @@ -11,8 +11,8 @@ import java.lang.reflect.Field; public class JavaUtils { @NonNull - public static Class getClass(@NonNull Class Clazz) { - return Clazz; + public static Class getClass(@NonNull Class clazz) { + return clazz; } @NonNull diff --git a/app/src/main/java/org/autojs/autojs/util/RomUtils.kt b/app/src/main/java/org/autojs/autojs/util/RomUtils.kt new file mode 100644 index 00000000..62171a20 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/util/RomUtils.kt @@ -0,0 +1,126 @@ +@file:Suppress("unused") + +package org.autojs.autojs.util + +import android.app.AppOpsManager +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Process +import android.provider.Settings +import org.autojs.autojs.app.GlobalAppContext +import org.autojs.autojs.pref.Language +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader + + +/** + * Created by SuperMonster003 on Aug 17, 2023. + */ +// @Reference to https://blog.csdn.net/liao_fu_yun/article/details/114971424 +object RomUtils { + + @JvmStatic + @JvmOverloads + fun isBackgroundStartGranted(context: Context = GlobalAppContext.get()) = when { + isMiui() -> isMiuiBgStartPermissionGranted(context) + isVivo() -> isVivoBgStartPermissionGranted(context) + isOppo() -> Settings.canDrawOverlays(context) + else -> true + } + + private fun isMiuiBgStartPermissionGranted(context: Context): Boolean { + val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + try { + val op = 10021 + val method = ops.javaClass.getMethod( + "checkOpNoThrow", + Int::class.javaPrimitiveType, + Int::class.javaPrimitiveType, + String::class.java, + ) + val result = method.invoke(ops, op, Process.myUid(), context.packageName) as Int + return result == AppOpsManager.MODE_ALLOWED + } catch (e: Exception) { + e.printStackTrace() + } + return false + } + + private fun isVivoBgStartPermissionGranted(context: Context): Boolean { + return getVivoBgStartPermissionStatus(context) == 0 + } + + /** + * 判断 Vivo 后台弹出界面状态. 1: 无权限; 0: 有权限. + */ + private fun getVivoBgStartPermissionStatus(context: Context): Int { + val uri = Uri.parse("content://com.vivo.permissionmanager.provider.permission/start_bg_activity") + val selection = "pkgname = ?" + val selectionArgs = arrayOf(context.packageName) + var state = 1 + try { + context.contentResolver.query(uri, null, selection, selectionArgs, null)?.use { + if (it.moveToFirst()) { + val columnIndex = it.getColumnIndex("currentstate") + if (columnIndex >= 0) { + state = it.getInt(columnIndex) + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return state + } + + /* -=-=- --------------------------------------- -=-=- */ + /* -=-=- Android-based OS, including custom ROMs -=-=- */ + /* -=-=- --------------------------------------- -=-=- */ + + fun isMiui() = getSystemProperty("ro.miui.ui.version.name").isNotEmpty() + + fun isEmui() = getSystemProperty("ro.build.version.emui").isNotEmpty() + + fun isOppo() = getSystemProperty("ro.build.version.opporom").isNotEmpty() + + fun isSmartisan() = getSystemProperty("ro.smartisan.version").isNotEmpty() + + fun isVivo() = getSystemProperty("ro.vivo.os.version").isNotEmpty() + + /** 金立. */ + fun isGionee() = getSystemProperty("ro.gn.sv.version").isNotEmpty() + + fun isLenovo() = getSystemProperty("ro.lenovo.lvp.version").isNotEmpty() + + fun isFlyme() = Build.DISPLAY.lowercase(Language.getPrefLanguage().locale).contains("flyme") + + private fun getSystemProperty(propName: String): String = try { + BufferedReader( + InputStreamReader( + Runtime.getRuntime().exec("getprop $propName").inputStream + ), 1024 + ).use { it.readLine() } + } catch (ex: IOException) { + "" + } + + /* -=-=- --------------- -=-=- */ + /* -=-=- Brand / Product -=-=- */ + /* -=-=- --------------- -=-=- */ + + object Brand { + + /** Sony (索尼) Xperia. */ + fun isXperia() = getSystemProperty("ro.semc.product.name").contains("xperia", true) + + /** Redmi (红米). */ + fun isRedmi() = getSystemProperty("ro.product.model").contains("redmi", true) + + /** Mi Mix. */ + fun isMiMix() = Build.MODEL.contains("mi mix", true) + + } + +} diff --git a/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt b/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt index 193eedc8..da83bea0 100644 --- a/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/UpdateUtils.kt @@ -127,12 +127,12 @@ object UpdateUtils { .onNeutral { dVersionInfo, _ -> showRemoveIgnoredVersionPrompt(context, dialog, dVersionInfo, text) } - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_cancel) .onPositive { dVersionInfo, _ -> dVersionInfo.dismiss() } .autoDismiss(false) .show() } - .positiveText(R.string.dialog_button_back) + .positiveText(R.string.dialog_button_cancel) .onPositive { dialog, _ -> dialog.dismiss() } .autoDismiss(false) .build() diff --git a/app/src/main/java/org/autojs/autojs/util/WebViewUtils.kt b/app/src/main/java/org/autojs/autojs/util/WebViewUtils.kt index e501d91f..a9278896 100644 --- a/app/src/main/java/org/autojs/autojs/util/WebViewUtils.kt +++ b/app/src/main/java/org/autojs/autojs/util/WebViewUtils.kt @@ -5,8 +5,7 @@ import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.TIRAMISU import android.webkit.WebView import androidx.webkit.WebSettingsCompat -import androidx.webkit.WebSettingsCompat.FORCE_DARK_OFF -import androidx.webkit.WebSettingsCompat.FORCE_DARK_ON +import androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING import androidx.webkit.WebViewFeature.FORCE_DARK import androidx.webkit.WebViewFeature.isFeatureSupported @@ -26,13 +25,17 @@ class WebViewUtils { if (isFeatureSupported(FORCE_DARK) && SDK_INT < TIRAMISU) { webView.settings.let { webSettings -> webSettings.setSupportMultipleWindows(true) - @Suppress("DEPRECATION") - WebSettingsCompat.setForceDark( - webSettings, when (ViewUtils.isNightModeYes(context)) { - true -> FORCE_DARK_ON - else -> FORCE_DARK_OFF - } - ) + // @Comment by SuperMonster003 on Aug 14, 2023. + // @Suppress("DEPRECATION") + // WebSettingsCompat.setForceDark( + // webSettings, when (ViewUtils.isNightModeYes(context)) { + // true -> FORCE_DARK_ON + // else -> FORCE_DARK_OFF + // } + // ) + if (isFeatureSupported(ALGORITHMIC_DARKENING)) { + WebSettingsCompat.setAlgorithmicDarkeningAllowed(webSettings, ViewUtils.isNightModeYes(context)) + } } } } diff --git a/app/src/main/java/pxb/android/axml/DumpEditor.java b/app/src/main/java/pxb/android/axml/DumpEditor.java index caa7175a..fa9416d5 100644 --- a/app/src/main/java/pxb/android/axml/DumpEditor.java +++ b/app/src/main/java/pxb/android/axml/DumpEditor.java @@ -5,7 +5,6 @@ import java.util.Map; /** * Created by Stardust on 2017/10/23. */ - public class DumpEditor extends DumpAdapter { private Map mValueModifications; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cc5836fb..302794f5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -35,6 +35,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:tabGravity="fill" + app:tabMaxWidth="0dp" app:tabIndicatorColor="@color/tab_indicator" app:tabMode="fixed" app:tabSelectedTextColor="@color/tab_selected_text" diff --git a/app/src/main/res/layout/circular_action_view.xml b/app/src/main/res/layout/circular_action_view.xml index 6f9a1c15..e1ff3d07 100644 --- a/app/src/main/res/layout/circular_action_view.xml +++ b/app/src/main/res/layout/circular_action_view.xml @@ -7,8 +7,8 @@ + + يجعل الوضع المستقر أكثر استقرارًا عند الحصول على حدود تخطيط ، ولكن قد يتم تجاهل بعض النتائج.\nإعادة تشغيل خدمة الوصول المطلوبة. يتم تطبيق لون النسق على عناصر واجهة المستخدم بما في ذلك على سبيل المثال لا الحصر العناصر التالية:\nشريط الحالة\nAppbar\nرمز الملف\nرمز عنصر المهمة\nفاب\nعنوان فئة الإعدادات\nزر التغيير\n\nملاحظة: اعتبارًا من الإصدار 6.2.0 من AutoJs6 ، لم يكن هناك فرق بين اللون الأساسي واللون الأساسي الداكن ولون التمييز حتى الآن. @string/text_back + @string/text_continue يلغي يلغي نعم @@ -132,7 +133,7 @@ يتم تعطيل خدمة إمكانية الوصول وتوقف البرنامج النصي لم يتم تنشيط خدمة إمكانية الوصول لا توجد خدمة إمكانية الوصول - لا يوجد إذن \"عرض عبر التطبيقات الأخرى\" + لا يوجد إذن \"عرض عبر التطبيقات الأخرى\" لا يوجد إذن \"نشر الإخطارات\" لا يوجد إذن \"اقرأ حالة الهاتف\" لا يوجد إذن التقاط الشاشة @@ -325,7 +326,7 @@ خطوة أكثر حذف حذف الخط - تفاصيل + تفاصيل تفاصيل المطور قيد التطوير خيارات للمطور العلامة التجارية للجهاز @@ -678,6 +679,19 @@ إعدادات النظام الآمنة ، التي تحتوي على تفضيلات النظام التي يمكن أن تقرأها التطبيقات ولكن لا يُسمح لها بالكتابة.\nهذه هي لتفضيلات يجب على المستخدم تعديلها بشكل صريح من خلال واجهة المستخدم لتطبيق النظام.\nمع إذن إعدادات النظام الآمن ، يمكن للتطبيقات العادية تعديل الإعدادات الآمنة مباشرة (مثل خدمة إمكانية الوصول). كتابة إعدادات النظام تقصير + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 المجموع: عنصر واحد diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 0eb356d9..10cd67c6 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -38,6 +38,7 @@ Stable mode makes it more stable when getting layout bounds, but some results may be ignored.\nA11y service\'s restart required. Theme color is applied to widgets including but not limited to the following ones:\nStatus bar\nAppbar\nFile icon\nTask item icon\nFab\nSettings category title\nSwitch button\n\nNote: As of AutoJs6 version 6.2.0, there has been no difference between primary color, primary dark color and accent color yet. @string/text_back + @string/text_continue Cancel Cancel OK @@ -130,7 +131,7 @@ Accessibility service is disabled and the script has stopped Accessibility service is not activated No accessibility service - No \"display over other apps\" permission + No \"display over other apps\" permission No \"post notifications\" permission No \"read phone state\" permission No screen capture permission @@ -323,7 +324,7 @@ Step over Delete Delete line - Details + Details Developer details is under development Developer options Device brand @@ -676,6 +677,19 @@ Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service). Write system settings Default + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 Total: %d item diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 02fc8052..9ec5b935 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -40,6 +40,7 @@ El modo estable hace que sea más estable al obtener los límites de diseño, pero algunos resultados pueden ser ignorados.\nEs necesario reiniciar el servicio de accesibilidad. El color del tema se aplica a los widgets, incluidos, entre otros, los siguientes\nBarra de estado\nBarra de aplicaciones\nIcono de archivo\nIcono de elemento de tarea\nFab\nTítulo de la categoría de ajustes\nBotón de cambio\n\nNota: A partir de la versión 6.2.0 de AutoJs6, todavía no hay diferencia entre el color primario, el color primario oscuro y el color de acento. @string/text_back + @string/text_continue Cancelar Cancelar OK @@ -132,7 +133,7 @@ El servicio de accesibilidad está desactivado y el script se ha detenido El servicio de accesibilidad no está activado No hay servicio de accesibilidad - No hay permiso de \"mostrar sobre otras aplicaciones\". + No hay permiso de \"mostrar sobre otras aplicaciones\". Sin permiso para \"enviar notificaciones\" No hay permiso de \"lectura del estado del teléfono\". No hay permiso de captura de pantalla @@ -325,7 +326,7 @@ Pasar por encima Borrar Borrar línea - Detalles + Detalles Los detalles del desarrollador están en desarrollo Opciones del desarrollador Marca del dispositivo @@ -678,6 +679,19 @@ Ajustes de seguridad del sistema, que contienen preferencias del sistema que las aplicaciones pueden leer pero no pueden escribir.\nSe trata de preferencias que el usuario debe modificar explícitamente a través de la interfaz de usuario de una aplicación del sistema.\nCon el permiso de configuración segura del sistema, las aplicaciones normales pueden modificar directamente la configuración segura (como el servicio de accesibilidad). Escribir la configuración del sistema Por defecto + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 Total: %d artículo diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e31cc4d6..7c58c816 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -40,6 +40,7 @@ Le mode stable le rend plus stable lors de l\'obtention des limites de mise en page, mais certains résultats peuvent être ignorés.\nA11y service\'s restart required. La couleur du thème est appliquée aux widgets, y compris, mais sans s\'y limiter, aux widgets suivants :\nBarre d\'état\nBarre d\'applications\nIcône de fichier\nIcône d\'élément de tâche\nFab\nTitre de la catégorie Paramètres\nBouton de commutation\n\nRemarque: Depuis la version 6.2.0 d\'AutoJs6, il n\'y a pas encore de différence entre la couleur primaire, la couleur primaire foncée et la couleur d\'accentuation. @string/text_back + @string/text_continue Cancel Annulation OK @@ -132,7 +133,7 @@ Le service d\'accessibilité est désactivé et le script s\'est arrêté. Le service d\'accessibilité n\'est pas activé. No accessibility service - Aucune permission \"display over other apps\" + Aucune permission \"display over other apps\" Pas d\'autorisation pour les \"notifications d\'envoi\" Aucune autorisation de \"lire l\'état du téléphone\" Aucune autorisation de capture d\'écran @@ -325,7 +326,7 @@ Step over Suppression Supprimer la ligne - Détails + Détails Les détails du développeur sont en cours de développement Les options du développeur Marque de l\'appareil @@ -678,6 +679,19 @@ Paramètres de sécurité du système, contenant les préférences du système que les applications peuvent lire mais ne sont pas autorisées à écrire.\nIl s\'agit des préférences que l\'utilisateur doit explicitement modifier par le biais de l\'interface utilisateur d\'une application système.\nAvec l\'autorisation de paramètres de sécurité du système, les applications normales peuvent directement modifier les paramètres de sécurité (comme le service d\'accessibilité). Écrire les paramètres système Default + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 Total: %d article diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6ac9c8ec..f6eddcb6 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -40,6 +40,7 @@ 安定モードにするとレイアウト境界を取得する際に安定しますが、一部の結果が無視されることがあります.\nアクセシビリティサービスの再起動が必要です テーマカラーは、以下のウィジェットに適用されますが、これに限定されるものではありません.\nステータスバー\nアプリバー\nファイルアイコン\nタスクアイテムアイコン\nファブ\n設定カテゴリのタイトル\nスイッチボタン\n\n注: AutoJs6 バージョン 6.2.0 では、原色、原色暗色、アクセントカラーの区別はまだありません @string/text_back + @string/text_continue 取消 取消 OK @@ -132,7 +133,7 @@ アクセシビリティサービスが無効で、スクリプトが停止しています アクセシビリティサービスが有効になっていない アクセシビリティ・サービスがありません - 他のアプリの上に表示する」権限がない + 他のアプリの上に表示する」権限がない \"投稿通知\" 許可なし 電話の状態を読む」権限がない 画面キャプチャ許可がありません @@ -325,7 +326,7 @@ ステップオーバー 削除 行削除 - 詳細 + 詳細 デベロッパーの詳細については、現在開発中です デベロッパーオプション デバイスブランド @@ -678,6 +679,19 @@ アプリケーションが読み取ることはできるが、書き込むことはできないシステム環境設定を含む、安全なシステム設定です.\nこれは、ユーザーがシステムアプリのUIを通じて明示的に変更する必要がある環境設定のためのものです.\nセキュアなシステム設定を許可すると、通常のアプリケーションはセキュアな設定 (アクセシビリティサービスなど) を直接変更できるようになります システム設定の書き込み デフォルト + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 合計: %d 件 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f4bb3fd6..cf5383f3 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -40,6 +40,7 @@ 안정 모드는 레이아웃 경계를 얻을 때 더 안정적이지만 일부 결과는 무시할 수 있습니다.\n접근성 서비스의 재시작이 필요합니다. 테마 색상은 다음을 포함하되 이에 국한되지 않는 위젯에 적용됩니다.\n상태 표시 줄\n앱바\n파일 아이콘\n작업 항목 아이콘\n팹\n설정 카테고리 제목\n스위치 버튼\n\n참고: AutoJs6 버전 6.2.0 현재 기본 색상, 기본 어두운 색상 및 강조 색상 사이에는 아직 차이가 없습니다. @string/text_back + @string/text_continue 취소 취소 확인 @@ -132,7 +133,7 @@ 접근성 서비스가 비활성화되고 스크립트가 중지되었습니다 접근성 서비스는 활성화되지 않습니다 접근성 서비스가 없습니다 - \"다른 앱 위에 표시\"권한이 없습니다 + \"다른 앱 위에 표시\"권한이 없습니다 \"게시물 알림\" 권한 없음 \"전화 상태 읽기\"권한이 없습니다 화면 캡처 허가가 없습니다 @@ -325,7 +326,7 @@ 한 걸음 더 삭제 라인 삭제 - 세부 + 세부 개발자 세부 사항이 개발 중입니다 개발자 옵션 기기 브랜드 @@ -678,6 +679,19 @@ 애플리케이션이 읽을 수 있지만 쓸 수없는 시스템 환경 설정을 포함하는 보안 시스템 설정.\n이들은 사용자가 시스템 앱의 UI를 통해 명시 적으로 수정 해야하는 선호도입니다.\n보안 시스템 설정 권한을 사용하면 일반 애플리케이션이 보안 설정 (예: 접근성 서비스)을 직접 수정할 수 있습니다. 시스템 설정을 작성하십시오 기본 + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 총: %d 항목 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b7572df3..f1957707 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -40,6 +40,7 @@ Стабильный режим повышает стабильность при получении границ макета, но некоторые результаты могут быть проигнорированы.\nТребуется перезапуск службы доступности. Цвет темы применяется к виджетам, включая следующие, но не ограничиваясь ими:\nСтрока состояния\nПанель приложений\nЗначок файла\nЗначок элемента задачи\nFab\nЗаголовок каталога настроек\nКнопка переключения\n\nПримечание: Начиная с версии AutoJs6 6.2.0, пока не существует различий между основным цветом, основным темным цветом и цветом акцента. @string/text_back + @string/text_continue Отменить Отменить OK @@ -132,7 +133,7 @@ Служба доступности отключена, и сценарий остановлен Служба доступности не активирована Нет службы доступности - Нет разрешения \"отображать поверх других приложений\" + Нет разрешения \"отображать поверх других приложений\" Нет разрешения на \"уведомления о сообщениях\" Нет разрешения \"читать состояние телефона\" Нет разрешения на захват экрана @@ -325,7 +326,7 @@ Шаг за Удалить Удалить строку - Детали + Детали Детали разработчика находятся в стадии разработки Параметры разработчика Бренд устройства @@ -678,6 +679,19 @@ Настройки безопасности системы, содержащие системные предпочтения, которые приложения могут читать, но не имеют права записывать.\nОни предназначены для параметров, которые пользователь должен явно изменить через пользовательский интерфейс системного приложения.\nПри наличии разрешения на безопасные системные настройки обычные приложения могут напрямую изменять безопасные настройки (например, служба доступности). Запись системных настроек По умолчанию + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 Всего: %d элемент diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 2ae65c6b..ba7401d6 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -40,6 +40,7 @@ 穩定模式省略佈局細節, 腳本分析佈局時更穩定, 但可能影響獲取的控件總量.\n需重啓無障礙服務. 主題色的應用範圍包括但不限於以下部件:\n系統通知欄\n應用欄 (AppBar)\n文件圖標\n任務項圖標\n浮動操作按鈕 (Fab)\n設置頁面類別標題\n按鈕開關\n\n注: 截至 6.2.0 版本, AutoJs6 主題色暫未對 [ 主要色 (Primary Color) / 主要暗色 (Primary Dark Color) / 強調色 (Accent Color) ] 作出區分. @string/text_back + @string/text_continue 取消 取消下載 確定 @@ -83,6 +84,7 @@ 二進制文件 (不可編輯) JavaScript 文件 (可編輯) 運行時已存在 + 方法 %s 已被廢棄, 應避免使用 應用未安裝: \"%s\" Bitmap 與 Mat 不能同時為 null %s 不能為 null @@ -94,11 +96,13 @@ 密碼長度不足 密碼不一致 檢查更新失敗 + 冒號需跟隨有效的 IP 地址 羅盤參數不能為 null 連接失敗: %s 方法 resume() 調用前需先調用 suspend() 方法 suspend() 只能調用一次 對應的 GitHub 發行版可能還未發佈 + 點分十進制數超過 255 下載中斷 郵箱已被佔用 需至少選擇一個廣播事件 @@ -118,16 +122,18 @@ 圖像資源已被回收 無效的羅盤參數: %s 無效的 GitHub 倉庫: %s + 無效的 IP 地址 無效的登錄憑證 無效的剩餘羅盤參數: %s 不合法的選擇器集合元素: { %s: %s (%s) } + IP 地址不可為空 超出最大監聽器數量限制: %d AutoJs6 可能因缺少 Root 權限而無法運行 \"auto\" 文件 %s() 傳入的 %s 參數為空 無障礙服務未啓用 無障礙服務未啓用 沒有無障礙服務 - 缺少 \"顯示在其他應用上層\" 權限 + 缺少 \"顯示在其他應用上層\" 權限 缺少 \"發佈通知\" 權限 缺少 \"讀取手機狀態\" 權限 缺少屏幕捕獲權限 @@ -139,13 +145,17 @@ 解析 GitHub 發行版資源信息失敗 無法解析版本信息 正則表達式句法錯誤 + 端口號超過 65535 無法將值 %s 存入 JSON 正則表達式不支持向前查找 + 重複的分號符號 + 重複的句點符號 腳本正在退出中 選擇器方法需調用而不可作為參數直接傳入: %s 結果類型為 selector 時, 無法同時使用羅盤參數 不支持同時設置 iconRes 與 icon 線程處於非活動狀態 + 不接受的字符 未知錯誤 未知的 algorithm 選擇器參數: %s 未知顏色名稱: %s @@ -277,19 +287,21 @@ 操作頻率過快 客户端模式 關閉 + 關閉浮動按鈕 代碼 格式化代碼 指令已複製到剪貼板 + 註釋 兼容性 Java 類名 4.x 版本兼容 配置 是否確定刪除 確定移除嗎? 連接到計算機 + 無法建立連接 控制枱 繼續 複製 - 導出 複製全部 複製原目錄文件到新目錄 複製指令 @@ -297,6 +309,7 @@ 複製行 複製到剪貼板 複製值 + 當前版本 每日任務 日期 @@ -313,7 +326,7 @@ 單步 刪除 刪除行 - 詳情 + 詳情 \"開發者詳情\" 正在開發中... 開發者選項 設備品牌 @@ -363,7 +376,7 @@ [%s] 運行結束 (用時 %s 秒)\n 退出 直接退出 - 關閉浮動按鈕 + 導出 JavaScript 內置對象擴展 擴展性 失敗 @@ -374,6 +387,7 @@ 下載失敗 使用 root 權限啓用無障礙服務失敗 使用修改安全設置權限啓用無障礙服務失敗 + 格式化失敗 生成失敗 授權失敗 顯示在其他應用上層權限授予失敗 @@ -381,6 +395,7 @@ 登錄失敗 註冊失敗 提交失敗 + 無法保存遠程項目至本地 文件寫入失敗 文件 文件已存在 @@ -396,6 +411,8 @@ 強制停止 前台服務 前台服務執行一些用户能注意到的操作.\n前台服務必須顯示通知, 確保用户知道應用正在執行的任務.\n前台服務通知無法移除, 除非服務停止運行.\n可通過長按通知將通知優先級降低 (部分系統支持關閉前台服務通知), 以減少通知對用户的影響. + 格式化已完成 + 函數 多功能鍵盤 生成 生成代碼 @@ -450,10 +467,13 @@ 延遲執行 (秒) 循環間隔 (秒) 循環次數 + 低於 主腳本文件名 管理 管理無障礙服務 管理已忽略更新 + 最低版本 + VSCode 插件最低版本 未安裝 \"手機QQ\" 更多 瞭解詳情 @@ -478,7 +498,6 @@ 未授予 不顯示主界面 通知 - 發佈通知權限 通知讀取權限 通知服務未運行 @@ -514,6 +533,7 @@ 請輸入名稱 指針位置 (root) 切換 \"指針位置\" 顯示狀態失敗\n可能缺少 root 權限 + 發佈通知權限 預執行腳本 準備中... 再按一次關閉對話框 @@ -524,8 +544,6 @@ 項目位置 投影媒體權限 被授予 \"投影媒體權限\" 後, 錄製屏幕的安全提示窗口將不再彈出. - 無法保存遠程項目至本地 - 項目已保存 提示 放棄 錄製完成 @@ -542,11 +560,14 @@ 註冊中 更新日誌 遠程 + 遠程文件已保存至本地 + 遠程項目已保存至本地 刪除所有斷點 重命名 替換 全部替換 替換 + VSCode 插件項目地址 提交成功 要求最低安卓系統版本 %s (API %s), 當前為 %s (API %s) 重置密碼 @@ -585,7 +606,6 @@ 保存到 錄製腳本 腳本運行 - 遠程項目已保存至本地 搜索 選擇 判斷控件存在 (exists) @@ -651,6 +671,7 @@ 版本信息 版本名稱 查看文檔 + VSCode 插件版本不符合要求 每週任務 需至少選擇一天 工作路徑 @@ -658,26 +679,19 @@ 安全設置包含應用程序可讀但不可寫入的設置選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設置權限\" 後, 普通應用可直接修改上述安全設置 (例如無障礙服務). 修改系統設置 默認 - 方法 %s 已被廢棄, 應避免使用 - 無效的 IP 地址 - IP 地址不可為空 - 不接受的字符 - 端口號超過 65535 - 點分十進制數超過 255 - 冒號需跟隨有效的 IP 地址 - 重複的句點符號 - 重複的分號符號 - VSCode 插件版本不符合要求 - 最低版本 - 當前版本 - 低於 - VSCode 插件項目地址 - 無法建立連接 - VSCode 插件最低版本 - 函數 - 格式化已完成 - 格式化失敗 - 註釋 + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 共計: %d 項 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6fb492bd..da436771 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -40,6 +40,7 @@ 穩定模式省略佈局細節, 指令碼分析佈局時更穩定, 但可能影響獲取的控制元件總量.\n需重啟無障礙服務. 主題色的應用範圍包括但不限於以下部件:\n系統通知欄\n應用欄 (AppBar)\n檔案圖示\n任務項圖示\n浮動操作按鈕 (Fab)\n設定頁面類別標題\n按鈕開關\n\n注: 截至 6.2.0 版本, AutoJs6 主題色暫未對 [ 主要色 (Primary Color) / 主要暗色 (Primary Dark Color) / 強調色 (Accent Color) ] 作出區分. @string/text_back + @string/text_continue 取消 取消下載 確定 @@ -132,7 +133,7 @@ 無障礙服務未啟用 無障礙服務未啟用 沒有無障礙服務 - 缺少 \"顯示在其他應用上層\" 許可權 + 缺少 \"顯示在其他應用上層\" 許可權 缺少 \"釋出通知\" 許可權 缺少 \"讀取手機狀態\" 許可權 缺少螢幕捕獲許可權 @@ -325,7 +326,7 @@ 單步 刪除 刪除行 - 詳情 + 詳情 \"開發者詳情\" 正在開發中... 開發者選項 裝置品牌 @@ -678,6 +679,19 @@ 安全設定包含應用程式可讀但不可寫入的設定選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設定許可權\" 後, 普通應用可直接修改上述安全設定 (例如無障礙服務). 修改系統設定 預設 + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 共計: %d 項 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index bd7946a3..b211c184 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -36,6 +36,7 @@ 稳定模式省略布局细节, 脚本分析布局时更稳定, 但可能影响获取的控件总量.\n需重启无障碍服务. 主题色的应用范围包括但不限于以下部件:\n系统通知栏\n应用栏 (AppBar)\n文件图标\n任务项图标\n浮动操作按钮 (Fab)\n设置页面类别标题\n按钮开关\n\n注: 截至 6.2.0 版本, AutoJs6 主题色暂未对 [ 主要色 (Primary Color) / 主要暗色 (Primary Dark Color) / 强调色 (Accent Color) ] 作出区分. @string/text_back + @string/text_continue 取消 取消下载 确定 @@ -128,7 +129,7 @@ 无障碍服务未启用 无障碍服务未启用 没有无障碍服务 - 缺少 \"显示在其他应用上层\" 权限 + 缺少 \"显示在其他应用上层\" 权限 缺少 \"发布通知\" 权限 缺少 \"读取手机状态\" 权限 缺少屏幕捕获权限 @@ -321,7 +322,7 @@ 单步 删除 删除行 - 详情 + 详情 \"开发者详情\" 正在开发中... 开发者选项 设备品牌 @@ -390,7 +391,7 @@ 登录失败 注册失败 提交失败 - 远程项目已保存至本地 + 无法保存远程项目至本地 文件写入失败 文件 文件已存在 @@ -556,7 +557,7 @@ 更新日志 远程 远程文件已保存至本地 - 无法保存远程项目至本地 + 远程项目已保存至本地 删除所有断点 重命名 替换 @@ -674,6 +675,19 @@ 安全设置包含应用程序可读但不可写入的设置选项, 这些选项只能由 UI 或系统级别应用修改.\n被授予 \"修改安全设置权限\" 后, 普通应用可直接修改上述安全设置 (例如无障碍服务). 修改系统设置 默认 + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 共计: %d 项 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index bc054511..3fe4d614 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -23,11 +23,14 @@ 12dp 14dp 14sp + 18sp + 20sp 14sp 2dp 280dp 518dp 64dp + 25dp 2 2 @@ -40,5 +43,6 @@ 20dp -10dp + 42dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 629ad6e4..844da8b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,7 @@ SuperMonster003 SuperMonster003 @string/text_back + @string/text_continue Cancel Cancel OK @@ -179,7 +180,7 @@ Accessibility service is disabled and the script has stopped Accessibility service is not activated No accessibility service - No \"display over other apps\" permission + No \"display over other apps\" permission No \"post notifications\" permission No \"read phone state\" permission No screen capture permission @@ -247,7 +248,9 @@ key_$_client_socket_normally_closed key_$_compatibility_with_classes_for_ver_4_x key_$_dialog_check_display_over_other_apps + key_$_num_of_log_entries_exceeds_limit_for_sending key_$_dialog_foreground_svc + key_$_dialog_log_entries_exported key_$_dialog_manage_all_files_permission key_$_documentation_launcher_icon key_$_documentation_launcher_icon_hide @@ -508,7 +511,7 @@ Step over Delete Delete line - Details + Details Developer details is under development Developer options Device brand @@ -884,6 +887,19 @@ EN Auto ZH_CN + 仅最新的 %d 个条目将被发送 + 当前日志总条目数 %d 超出可发送限制 + 日志条目发送失败 + 无可发送的日志条目 + 无可导出的日志条目 + 无可复制的日志条目 + 复制失败 + 已复制到剪贴板 (仅最新的 %d 个条目) + 日志条目导出失败 + 关闭 + 查看文件 + 已导出 + 已导出 %d 个条目 Total: %d item diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 44385de3..3797c119 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -116,11 +116,17 @@ + + diff --git a/app/src/main/res/xml/fragment_preferences.xml b/app/src/main/res/xml/fragment_preferences.xml index 85519230..9778bb1b 100644 --- a/app/src/main/res/xml/fragment_preferences.xml +++ b/app/src/main/res/xml/fragment_preferences.xml @@ -233,7 +233,7 @@ app:title="@string/text_about_app_tips" app:summary="@string/summary_about_app_tips" app:dialogContent="@string/content_about_app_tips" - app:positiveText="@string/dialog_button_back" + app:positiveText="@string/dialog_button_cancel" app:longClickPrompt="@string/description_about_app_tips_preference" /> platform.substring(platformIdentifierForAS.length) - isPlatformIdea -> platform.substring(platformIdentifierForIdea.length) - else -> "Unknown" - } - - val platformType = when { - isPlatformAS -> "as" - isPlatformIdea -> "idea" - else -> throw Exception("Unknown platform: $platform") - } - - val platformNicknameForAS = mapOf( - "A" to "Arctic Fox", - "B" to "Bumblebee", - "C" to "Chipmunk", - "D" to "Dolphin", - "E" to "Electric Eel", - "F" to "Flamingo", - "G" to "Giraffe", - "H" to "Hedgehog", - ) - - /* --== functions ==-- */ - - fun printCurrentPlatformInfo() = when { - isPlatformAS -> { - val platformNickAbbr = gradlePluginVersionList["as"]!!["abbr"]!![platformVersion] - val platformNick = platformNickAbbr?.let { abbr -> platformNicknameForAS[abbr]?.let { " $it" } } ?: "" - - val previewIdentifier = "Preview" - val isPreview = platformVersion.contains(previewIdentifier, true) - val previewSuffix = if (isPreview) " ($previewIdentifier)" else "" - val niceVersion = if (isPreview) platformVersion.substring(previewIdentifier.length) else platformVersion - - "Android Studio$platformNick$previewSuffix | $niceVersion" - } - isPlatformIdea -> "IntelliJ IDEA $platformVersion" - else -> "Unknown" - }.let { println("Platform: $it") } - - /* --== repositories ==-- */ - - repositories { - mavenCentral() - google() - maven("https://maven.aliyun.com/repository/central") - maven("https://maven.aliyun.com/repository/google") - maven("https://maven.aliyun.com/repository/gradle-plugin") - maven("https://maven.aliyun.com/repository/jcenter") - maven("https://maven.aliyun.com/repository/public") - } - - /* --== dependencies ==-- */ - - dependencies /* Print Information. */ { - printCurrentPlatformInfo() - } - - dependencies /* Android/Kotlin Gradle Plugin. */ { - val android = gradlePluginVersionList[platformType]!!["android"]!! - val kotlin = gradlePluginVersionList[platformType]!!["kotlin"]!! - - arrayOf( - arrayOf("com.android.tools.build:gradle", android[platformVersion], android["fallback"]), - arrayOf("org.jetbrains.kotlin:kotlin-gradle-plugin", kotlin[platformVersion], kotlin["fallback"]), - ).forEach { data -> - val classpathNotation = "${data[0]}:${data[1] ?: data[2]}" - val suffix = data[1]?.let { "" } ?: " [fallback]" - println("Classpath: \"$classpathNotation\"$suffix") - classpath(classpathNotation) - } - } - -} - allprojects { repositories { mavenCentral() @@ -160,6 +23,7 @@ allprojects { tasks { register("clean").configure { - delete(rootProject.buildDir) + // delete(rootProject.buildDir) + delete(rootProject.layout.buildDirectory) } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5e14f97e..b808343c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,8 +16,6 @@ org.gradle.jvmargs=-Xms4g -Xmx4g -Dkotlin.daemon.jvm.options\="-Xmx4g" -Dfile.en org.gradle.daemon=true # Temporarily stops AGP version checking android.injected.studio.version.check=false -# Added since Gradle Build Tool version 8.0.0-alpha09 -android.defaults.buildfeatures.buildconfig=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -36,4 +34,8 @@ android.enableJetifier=true # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false +# -- archived fields -- +# Added since Gradle Build Tool version 8.0.0-alpha09 +# Removed since Gradle Build Tool version 8.2.0-alpha15 +# android.defaults.buildfeatures.buildconfig=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6881fc8a..f6352a35 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jul 21 20:25:37 CST 2023 +#Mon Oct 02 14:59:53 CST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-rc-3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/libs/org.mozilla.rhino-1.7.15-snapshot.jar b/libs/org.mozilla.rhino-1.7.15-snapshot.jar index 8ea11378..f96448a2 100644 Binary files a/libs/org.mozilla.rhino-1.7.15-snapshot.jar and b/libs/org.mozilla.rhino-1.7.15-snapshot.jar differ diff --git a/libs/paddleocr/build.gradle b/libs/paddleocr/build.gradle index 50ce1e86..f9128b5b 100644 --- a/libs/paddleocr/build.gradle +++ b/libs/paddleocr/build.gradle @@ -1,11 +1,38 @@ -plugins { - id 'com.android.library' -} import java.security.MessageDigest +/** + * PaddleOCR (https://github.com/PaddlePaddle/PaddleOCR) build script (Groovy). + * + * Created by TonyJiangWJ (https://github.com/TonyJiangWJ) on Aug 7, 2023. + * Modified by TonyJiangWJ (https://github.com/TonyJiangWJ) as of Aug 11, 2023. + * Modified by SuperMonster003 as of Sep 4, 2023. + */ + +plugins { + id "com.android.library" +} + +def versions = new Versions("$rootDir/version.properties") + +def versionOpencv = versions.opencv +def versionNdk = versions.ndk +def versionCmake = versions.cmake + +def zipArchives = [ + // @Hint by TonyJiangWJ on Aug 7, 2023. + // ! 下载 OpenCV 源码包 (默认为 4.2.0). + // ! 和 Auto.js 中的版本 (如 4.5.5) 不匹配会产生冲突, 可按需修改. + [ + "src" : "https://github.com/opencv/opencv/releases/download" + + "/${versionOpencv}/opencv-${versionOpencv}-android-sdk.zip", + "dest": "OpenCV", + ], +] + android { namespace = "com.baidu.paddleocr" - ndkVersion '21.1.6352462' + + ndkVersion versionNdk compileSdk 33 defaultConfig { minSdkVersion 24 @@ -14,7 +41,7 @@ android { externalNativeBuild { cmake { cppFlags "-std=c++11 -frtti -fexceptions -Wno-format" - arguments '-DANDROID_PLATFORM=android-23', '-DANDROID_STL=c++_shared' ,"-DANDROID_ARM_NEON=TRUE" + arguments "-DANDROID_PLATFORM=android-23", "-DANDROID_STL=c++_shared", "-DANDROID_ARM_NEON=TRUE" } } ndk { @@ -30,30 +57,23 @@ android { externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" - version "3.10.2" + version versionCmake } } } dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation project(path: ':libs:org.opencv-4.5.5') + implementation fileTree(include: ["*.jar"], dir: "libs") + implementation project(path: ":libs:org.opencv-${versionOpencv}") } -/** - * 下载opencv4.5.5源码包 默认为4.2.0 和 AutoJS中的版本不匹配会产生冲突 这里进行修改 - */ -def zipArchives = [ - [ - 'src' : 'https://github.com/opencv/opencv/releases/download/4.5.5/opencv-4.5.5-android-sdk.zip', - 'dest': 'OpenCV' - ] -] +ext { + versions.showInfo() +} -/** - * 首次导入最好手动运行一下这个task下载依赖的包 - */ -tasks.register('downloadAndExtractArchives', DefaultTask) { +// @Hint by TonyJiangWJ on Aug 7, 2023. +// ! 首次导入最好手动运行一下这个 task 下载依赖的包. +tasks.register("downloadAndExtractArchives", DefaultTask) { doFirst { println "Downloading and extracting archives including libs and models" } @@ -63,15 +83,16 @@ tasks.register('downloadAndExtractArchives', DefaultTask) { if (!file("${cachePath}").exists()) { mkdir "${cachePath}" } - zipArchives.eachWithIndex { archive, index -> - MessageDigest messageDigest = MessageDigest.getInstance('MD5') + zipArchives.each { archive -> + MessageDigest messageDigest = MessageDigest.getInstance("MD5") messageDigest.update(archive.src.bytes) String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) // Download the target archive if not exists boolean copyFiles = !file("${archive.dest}").exists() if (!file("${cachePath}/${cacheName}.zip").exists()) { ant.get(src: archive.src, dest: file("${cachePath}/${cacheName}.zip")) - copyFiles = true; // force to copy files from the latest archive files + // force to copy files from the latest archive files + copyFiles = true } // Extract the target archive if its dest path does not exists if (copyFiles) { @@ -83,4 +104,52 @@ tasks.register('downloadAndExtractArchives', DefaultTask) { } } } + preBuild.dependsOn downloadAndExtractArchives + +class Versions { + + String opencv + String ndk + String cmake + + Versions(String filePath) { + File file = new File(filePath) + if (!file.canRead()) { + throw FileNotFoundException("Can't read file '$filePath'") + } + Properties properties = new Properties() + properties.load(new FileInputStream(file)) + opencv = properties["OPENCV_VERSION"] as String + ndk = properties["NDK_VERSION"] as String + cmake = properties["CMAKE_VERSION"] as String + } + + void showInfo() { + def title = "Version information for paddleocr library" + + def infoOpencv = "OpenCV version: $opencv" + def infoNdk = "NDK version: $ndk" + def infoCmake = "CMake version: $cmake" + + def maxLength = maxOf([title, infoOpencv, infoNdk, infoCmake].collect { it.length() }) + + [ + "=".repeat(maxLength), + title, + "-".repeat(maxLength), + infoOpencv, + infoNdk, + infoCmake, + "=".repeat(maxLength), + "", + ].each { println(it) } + } + + private static int maxOf(List integers) { + int max = 0 + integers.each { max = Math.max(max, it) } + return max + } + +} \ No newline at end of file diff --git a/libs/paddleocr/src/main/java/com/baidu/paddle/lite/ocr/Predictor.java b/libs/paddleocr/src/main/java/com/baidu/paddle/lite/ocr/Predictor.java index 8d27c617..1e49bc4b 100644 --- a/libs/paddleocr/src/main/java/com/baidu/paddle/lite/ocr/Predictor.java +++ b/libs/paddleocr/src/main/java/com/baidu/paddle/lite/ocr/Predictor.java @@ -164,6 +164,7 @@ public class Predictor { return true; } + /** @noinspection deprecation*/ protected boolean loadModel(Context appCtx, String modelPath, int cpuThreadNum, String cpuPowerMode) { // Release model if exists releaseModel(); diff --git a/settings.gradle.kts b/settings.gradle.kts index 5a2a838f..38558bf9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,10 @@ +@file:Suppress("SpellCheckingInspection") + enableFeaturePreview("STABLE_CONFIGURATION_CACHE") include( ":app", + ":libs:android-job-simplified-1.4.3", ":libs:androidx.appcompat-1.0.2", ":libs:com.tencent.bugly.crashreport-4.0.4", @@ -15,12 +18,207 @@ include( ":libs:android-spackle-9.0.0", ":libs:android-assertion-9.0.0", ":libs:android-plugin-client-sdk-for-locale-9.0.0", +) - // ":libs:android-plugin-client-sdk-for-locale-9.0.0", - // ":libs:com.android.dx-1.7.0", - // ":libs:github-api-1.306", - // ":libs:org.mozilla.rhino-1.7.15-snapshot", +pluginManagement { - // ":libs:com.afollestad.material-dialogs-core-0.9.6.0", - // ":libs:com.afollestad.material-dialogs-commons-0.9.6.0", -) \ No newline at end of file + // @Hint by SuperMonster003 on May 3, 2023. + // ! To download archives of Android Studio, + // ! visit https://developer.android.com/studio/archive?hl=en + // ! + // ! To check the releases for AGP (Android Gradle Plugin), + // ! visit https://developer.android.com/reference/tools/gradle-api + // ! + // ! To check the compatibility and released date of kotlin plugins, + // ! visit https://plugins.jetbrains.com/plugin/6954-kotlin/versions/eap + // ! + // ! To check the releases for KSP (Kotlin Symbol Processing) plugin, + // ! visit https://github.com/google/ksp/releases + + val gradlePluginVersionMap = mapOf( + "as" to mapOf( + "abbr" to mapOf( + "Preview2023.2" to "I", /* Aug 25, 2023. */ + "Preview2023.1" to "H", /* May 13, 2023. */ + "2022.3" to "G", /* Jul 3, 2023. */ + "Preview2022.3" to "G", /* May 3, 2023. */ + "2022.2" to "F", /* May 3, 2023. */ + "Preview2022.2" to "F", /* May 3, 2023. */ + "2022.1" to "E", /* May 3, 2023. */ + ), + "android" to mapOf( + "Preview2023.2" to "8.3.0-alpha04", /* Sep 16, 2023. */ + "Preview2023.1" to "8.2.0-beta03", /* Sep 11, 2023. */ + "2022.3" to "8.1.2", /* Sep 29, 2023. */ + "Preview2022.3" to "8.1.0-beta05", /* Jun 14 2023. */ + "2022.2" to "8.0.2", /* May 26, 2023. */ + "Preview2022.2" to "8.0.0-beta05", /* Mar 25, 2023. */ + "2022.1" to "7.4.2", /* Mar 25, 2023. */ + "fallback" to "7.4.2", /* May 3, 2023. */ + ), + "kotlin" to mapOf( + "Preview2023.2" to "1.9.20-Beta", /* Sep 12, 2023. */ + "Preview2023.1" to "1.9.20-Beta", /* Sep 12, 2023. */ + "2022.3" to "1.9.0-RC", /* Jul 3, 2023. */ + "Preview2022.3" to "1.8.0", /* May 13, 2023. */ + "2022.2" to "1.8.20-RC2", /* Mar 23, 2023. */ + "Preview2022.2" to "1.8.0", /* Mar 23, 2023. */ + "2022.1" to "1.8.0-RC2", /* Dec 20, 2022. */ + "fallback" to "1.8.0", /* Aug 17, 2023. */ + ) + ), + "idea" to mapOf( + "android" to mapOf( + "2023.2" to "8.1.0", /* Sep 15, 2023. */ + "2023.1" to "7.4.2", /* May 26, 2023. */ + "2022.3" to "7.4.0-beta02", /* Mar 25, 2023. */ + "fallback" to "7.4.0", /* May 3, 2023. */ + ), + "kotlin" to mapOf( + "2023.2" to "1.9.20-Beta", /* Sep 12, 2023. */ + "2023.1" to "1.8.21", /* Apr 25, 2023. */ + "2022.3" to "1.8.21", /* Apr 25, 2023. */ + "fallback" to "1.8.21", /* May 3, 2023. */ + ), + ), + ) + + val kspPluginVersionMap = mapOf( + "1.9.20-Beta2" to "1.0.13", /* Sep 29, 2023. */ + "1.9.20-Beta" to "1.0.13", /* Sep 12, 2023. */ + "1.9.10" to "1.0.13", /* Sep 11, 2023. */ + "1.9.0" to "1.0.13", /* Aug 16, 2023. */ + "1.9.0-RC" to "1.0.11", /* Aug 16, 2023. */ + "1.8.21" to "1.0.11", /* Aug 16, 2023. */ + "1.8.20-RC2" to "1.0.9", /* Aug 16, 2023. */ + "1.8.0" to "1.0.9", /* Aug 16, 2023. */ + "1.8.0-RC2" to "1.0.8", /* Aug 16, 2023. */ + "fallback" to "1.8.0-1.0.9", /* Aug 16, 2023. */ + ) + + val platformNicknameForAS = mapOf( + "I" to "Iguana", + "H" to "Hedgehog", + "G" to "Giraffe", + "F" to "Flamingo", + "E" to "Electric Eel", + "D" to "Dolphin", + "C" to "Chipmunk", + "B" to "Bumblebee", + "A" to "Arctic Fox", + ) + + val platform = System.getProperty("idea.paths.selector") + val platformIdentifierForAS = "AndroidStudio" + val platformIdentifierForIdea = "IntelliJIdea" + + val isPlatformAS = platform.startsWith(platformIdentifierForAS) + val isPlatformIdea = platform.startsWith(platformIdentifierForIdea) + + val platformVersion = when { + isPlatformAS -> platform.substring(platformIdentifierForAS.length) + isPlatformIdea -> platform.substring(platformIdentifierForIdea.length) + else -> "Unknown" + } + + val platformType = when { + isPlatformAS -> "as" + isPlatformIdea -> "idea" + else -> throw Exception("Unknown platform: $platform") + } + + val kotlinVersion = gradlePluginVersionMap[platformType]!!["kotlin"]!!.let { kotlinMap -> + kotlinMap[platformVersion] ?: kotlinMap["fallback"]!! + } + + var infoList = emptyArray() + + gradle.extra.apply { + set("platformVersion", platformVersion) + set("platformType", platformType) + } + + buildscript { + + repositories { + mavenCentral() + google() + maven("https://maven.aliyun.com/repository/central") + maven("https://maven.aliyun.com/repository/google") + maven("https://maven.aliyun.com/repository/gradle-plugin") + maven("https://maven.aliyun.com/repository/jcenter") + maven("https://maven.aliyun.com/repository/public") + } + + dependencies /* Gather platform information. */ { + when { + isPlatformAS -> { + val platformNickAbbr = gradlePluginVersionMap["as"]!!["abbr"]!![platformVersion] + val platformNick = platformNickAbbr?.let { abbr -> platformNicknameForAS[abbr]?.let { " $it" } } ?: "" + + val previewIdentifier = "Preview" + val isPreview = platformVersion.contains(previewIdentifier, true) + val previewSuffix = if (isPreview) " ($previewIdentifier)" else "" + val niceVersion = if (isPreview) platformVersion.substring(previewIdentifier.length) else platformVersion + + "Android Studio$platformNick$previewSuffix | $niceVersion" + } + isPlatformIdea -> "IntelliJ IDEA $platformVersion" + else -> "Unknown" + }.let { infoList += "Platform: $it" } + } + + dependencies /* Android/Kotlin Gradle Plugin. */ { + val androidMap = gradlePluginVersionMap[platformType]!!["android"]!! + val kotlinMap = gradlePluginVersionMap[platformType]!!["kotlin"]!! + + arrayOf( + arrayOf("com.android.tools.build:gradle", androidMap[platformVersion], androidMap["fallback"]), + arrayOf("org.jetbrains.kotlin:kotlin-gradle-plugin", kotlinMap[platformVersion], kotlinMap["fallback"]), + ).forEach { data -> + val classpath = data[0] + val version = data[1] + val versionFallback = data[2] + + val classpathNotation = "$classpath:${version ?: versionFallback}" + val suffix = version?.let { "" } ?: " [fallback]" + + infoList += "Classpath: \"$classpathNotation\"$suffix" + classpath(classpathNotation) + } + } + + } + + plugins { + arrayOf( + arrayOf("com.google.devtools.ksp", kspPluginVersionMap[kotlinVersion], kspPluginVersionMap["fallback"], false), + ).forEach { data -> + val id = data[0] as String + val kspVersion = data[1] as String? + val kspVersionFallback = data[2] as String + val isApply = data[3] as Boolean + + val pluginVersion = kspVersion?.let { "$kotlinVersion-$it" } ?: kspVersionFallback + val suffix = kspVersion?.let { "" } ?: " [fallback]" + + infoList += "Plugin: \"$id:$pluginVersion\"$suffix" + id(id) version pluginVersion apply isApply + } + } + + extra /* Print information. */ { + val title = "Version information for IDE platform and Gradle plugins" + val maxLength = infoList.maxOf { it.length } + + arrayOf( + "=".repeat(maxLength), + title, + "-".repeat(maxLength), + *infoList, + "=".repeat(maxLength), + "", + ).forEach { println(it) } + } + +} \ No newline at end of file diff --git a/version.properties b/version.properties index 2e1a73ec..2464bced 100644 --- a/version.properties +++ b/version.properties @@ -1,9 +1,14 @@ -#Thu Jul 27 13:17:33 CST 2023 -BUILD_TIME=1690435053022 -COMPILE_SDK_VERSION=33 -JAVA_VERSION=20 +#Tue Oct 03 00:07:32 CST 2023 +BUILD_TIME=1696262852412 +CMAKE_VERSION=3.10.2 +COMPILE_SDK_VERSION=34 +JAVA_VERSION=22 +JAVA_VERSION_MIN_SUGGESTED=19 +JAVA_VERSION_MIN_SUPPORTED=17 MIN_SDK_VERSION=24 -TARGET_SDK_VERSION=33 -VERSION_BUILD=2053 -VERSION_NAME=6.3.4 Alpha +NDK_VERSION=21.1.6352462 +OPENCV_VERSION=4.5.5 +TARGET_SDK_VERSION=34 +VERSION_BUILD=2130 +VERSION_NAME=6.3.4 Alpha2 VSCODE_EXT_REQUIRED_VERSION=1.0.7