Added: Root automator, custom theme color, some helps
This commit is contained in:
@@ -75,7 +75,8 @@
|
||||
</provider>
|
||||
|
||||
<activity android:name=".ui.edit.EditActivity"/>
|
||||
<activity android:name=".ui.help.DocumentActivity"/>
|
||||
<activity android:name=".ui.help.DocumentationActivity"/>
|
||||
<activity android:name=".ui.help.HelpCatalogueActivity"/>
|
||||
<activity android:name=".ui.settings.AboutActivity"/>
|
||||
<activity android:name=".ui.settings.SettingsActivity"/>
|
||||
<activity android:name=".ui.console.ConsoleActivity"/>
|
||||
|
||||
13
app/src/main/assets/help/catalogue.json
Normal file
13
app/src/main/assets/help/catalogue.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"main" : [
|
||||
{"title":"语法"},
|
||||
{"title":"JavaScript教程"},
|
||||
{"title":"自动操作函数"},
|
||||
{"title":"一般常用函数"},
|
||||
{"title":"控制台"},
|
||||
{"title":"UI(用户界面)"},
|
||||
{"title":"驻留模式与无障碍服务委托"},
|
||||
{"title":"shell命令"},
|
||||
{"title":"需要Root权限的自动操作函数"}
|
||||
]
|
||||
}
|
||||
358
app/src/main/assets/help/documentation/shell命令.md
Normal file
358
app/src/main/assets/help/documentation/shell命令.md
Normal file
@@ -0,0 +1,358 @@
|
||||
shell命令是通过shell函数运行的命令。等同于"adb shell"。shell函数的参数如下:
|
||||
## shell(cmd[, root])
|
||||
* cmd \<String\> 要执行的命令
|
||||
* root \<Boolean\> 是否以root权限运行,默认为false。
|
||||
返回运行一个执行结果。该返回值一般不会用到,其属性如下:
|
||||
* code \<Number\> 错误码。执行成功时为1,失败时为0。
|
||||
* result \<String\> 运行结果。
|
||||
* error \<String\> 运行的错误信息。例如执行需要root权限的命令但没有授予root权限会返回错误信息"Permission denied"。
|
||||
|
||||
例如``。(强制停止微信)
|
||||
示例:
|
||||
```
|
||||
var result = shell("am force-stop com.tencent.mm", true);
|
||||
if(result.succeed){
|
||||
toast("执行成功");
|
||||
}else{
|
||||
toast("执行失败!请到控制台查看错误信息");
|
||||
err(result.error);
|
||||
openConsole();
|
||||
}
|
||||
```
|
||||
|
||||
以下关于shell命令的资料来自[AndroidStudio用户指南: 发出shell命令](https://developer.android.com/studio/command-line/adb.html#shellcommands)。
|
||||
|
||||
目录:
|
||||
* [am命令](#am)
|
||||
* [intent 参数的规范](#IntentSpec)
|
||||
* [应用包名](#PackageName)
|
||||
* [pm命令](#pm)
|
||||
* [其他命令](#others)
|
||||
|
||||
<h2 id="am">am命令</h2>
|
||||
|
||||
am命令即Activity Manager命令,用于管理应用程序活动、服务等。
|
||||
|
||||
**以下命令均以"am "开头,例如"shell(\"am start -p com.tencent.mm\");"(启动微信)**
|
||||
|
||||
### start [options] intent
|
||||
启动 intent 指定的 Activity(应用程序活动)。
|
||||
请参阅 [intent 参数的规范](#IntentSpec)。
|
||||
|
||||
选项包括:
|
||||
|
||||
* -D:启用调试。
|
||||
* -W:等待启动完成。
|
||||
* --start-profiler file:启动分析器并将结果发送到 file。
|
||||
* -P file:类似于 --start-profiler,但当应用进入空闲状态时分析停止。
|
||||
* -R count:重复 Activity 启动 count 次数。在每次重复前,将完成顶部 Activity。
|
||||
* -S:启动 Activity 前强行停止目标应用。
|
||||
* --opengl-trace:启用 OpenGL 函数的跟踪。
|
||||
* --user user_id | current:指定要作为哪个用户运行;如果未指定,则作为当前用户运行。
|
||||
|
||||
### startservice [options] intent
|
||||
启动 intent 指定的 Service(服务)。
|
||||
请参阅 [intent 参数的规范](#IntentSpec)。
|
||||
选项包括:
|
||||
|
||||
* --user user_id | current:指定要作为哪个用户运行;如果未指定,则作为当前用户运行。
|
||||
|
||||
### force-stop package
|
||||
强行停止与 package([应用包名](#PackageName))关联的所有应用。
|
||||
|
||||
### kill [options] package
|
||||
终止与 package([应用包名](#PackageName))关联的所有进程。此命令仅终止可安全终止且不会影响用户体验的进程。
|
||||
选项包括:
|
||||
|
||||
* --user user_id | all | current:指定将终止其进程的用户;如果未指定,则终止所有用户的进程。
|
||||
* kill-all 终止所有后台进程。
|
||||
|
||||
### broadcast [options] intent
|
||||
发出广播 intent。
|
||||
请参阅 [intent 参数的规范](#IntentSpec)。
|
||||
|
||||
选项包括:
|
||||
|
||||
* [--user user_id | all | current]:指定要发送到的用户;如果未指定,则发送到所有用户。
|
||||
|
||||
### instrument [options] component
|
||||
使用 Instrumentation 实例启动监控。通常,目标 component 是表单 test_package/runner_class。
|
||||
选项包括:
|
||||
|
||||
* -r:输出原始结果(否则对 report_key_streamresult 进行解码)。与 [-e perf true] 结合使用以生成性能测量的原始输出。
|
||||
* -e name value:将参数 name 设为 value。对于测试运行器,通用表单为 -e testrunner_flag value[,value...]。
|
||||
* -p file:将分析数据写入 file。
|
||||
* -w:先等待仪器完成,然后再返回。测试运行器需要使用此选项。
|
||||
* --no-window-animation:运行时关闭窗口动画。
|
||||
* --user user_id | current:指定仪器在哪个用户中运行;如果未指定,则在当前用户中运行。
|
||||
* profile start process file 启动 process 的分析器,将结果写入 file。
|
||||
* profile stop process 停止 process 的分析器。
|
||||
|
||||
### dumpheap [options] process file
|
||||
转储 process 的堆,写入 file。
|
||||
|
||||
选项包括:
|
||||
|
||||
* --user [user_id|current]:提供进程名称时,指定要转储的进程用户;如果未指定,则使用当前用户。
|
||||
* -n:转储原生堆,而非托管堆。
|
||||
* set-debug-app [options] package 将应用 package 设为调试。
|
||||
|
||||
选项包括:
|
||||
|
||||
* -w:应用启动时等待调试程序。
|
||||
* --persistent:保留此值。
|
||||
* clear-debug-app 使用 set-debug-app 清除以前针对调试用途设置的软件包。
|
||||
|
||||
### monitor [options] 启动对崩溃或 ANR 的监控。
|
||||
选项包括:
|
||||
|
||||
* --gdb:在崩溃/ANR 时在给定端口上启动 gdbserv。
|
||||
### screen-compat {on|off} package
|
||||
控制 package 的屏幕兼容性模式。
|
||||
|
||||
### display-size [reset|widthxheight]
|
||||
替换模拟器/设备显示尺寸。此命令对于在不同尺寸的屏幕上测试您的应用非常有用,它支持使用大屏设备模仿小屏幕分辨率(反之亦然)。
|
||||
示例:
|
||||
```
|
||||
shell("am display-size 1280x800", true);
|
||||
|
||||
```
|
||||
### display-density dpi
|
||||
替换模拟器/设备显示密度。此命令对于在不同密度的屏幕上测试您的应用非常有用,它支持使用低密度屏幕在高密度环境环境上进行测试(反之亦然)。
|
||||
示例:
|
||||
```
|
||||
shell("am display-density 480", true);
|
||||
```
|
||||
### to-uri intent
|
||||
将给定的 intent 规范以 URI 的形式输出。
|
||||
请参阅 [intent 参数的规范](#IntentSpec)。
|
||||
|
||||
### to-intent-uri intent
|
||||
将给定的 intent 规范以 intent:URI 的形式输出。
|
||||
请参阅 intent 参数的规范。
|
||||
|
||||
<h2 id="IntentSpec"> intent 参数的规范 </h2>
|
||||
对于采用 intent 参数的 am 命令,您可以使用以下选项指定 intent:
|
||||
|
||||
* -a action
|
||||
指定 intent 操作,如“android.intent.action.VIEW”。此指定只能声明一次。
|
||||
* -d data_uri
|
||||
指定 intent 数据 URI,如“content://contacts/people/1”。此指定只能声明一次。
|
||||
* -t mime_type
|
||||
指定 intent MIME 类型,如“image/png”。此指定只能声明一次。
|
||||
* -c category
|
||||
指定 intent 类别,如“android.intent.category.APP_CONTACTS”。
|
||||
* -n component
|
||||
指定带有软件包名称前缀的组件名称以创建显式 intent,如“com.example.app/.ExampleActivity”。
|
||||
* -f flags
|
||||
将标志添加到 setFlags() 支持的 intent。
|
||||
* --esn extra_key
|
||||
添加一个 null extra。URI intent 不支持此选项。
|
||||
* -e|--es extra_key extra_string_value
|
||||
添加字符串数据作为键值对。
|
||||
* --ez extra_key extra_boolean_value
|
||||
添加布尔型数据作为键值对。
|
||||
* --ei extra_key extra_int_value
|
||||
添加整数型数据作为键值对。
|
||||
* --el extra_key extra_long_value
|
||||
添加长整型数据作为键值对。
|
||||
* --ef extra_key extra_float_value
|
||||
添加浮点型数据作为键值对。
|
||||
* --eu extra_key extra_uri_value
|
||||
添加 URI 数据作为键值对。
|
||||
* --ecn extra_key extra_component_name_value
|
||||
添加组件名称,将其作为 ComponentName 对象进行转换和传递。
|
||||
* --eia extra_key extra_int_value[,extra_int_value...]
|
||||
添加整数数组。
|
||||
* --ela extra_key extra_long_value[,extra_long_value...]
|
||||
添加长整型数组。
|
||||
* --efa extra_key extra_float_value[,extra_float_value...]
|
||||
添加浮点型数组。
|
||||
* --grant-read-uri-permission
|
||||
包含标志 FLAG_GRANT_READ_URI_PERMISSION。
|
||||
* --grant-write-uri-permission
|
||||
包含标志 FLAG_GRANT_WRITE_URI_PERMISSION。
|
||||
* --debug-log-resolution
|
||||
包含标志 FLAG_DEBUG_LOG_RESOLUTION。
|
||||
* --exclude-stopped-packages
|
||||
包含标志 FLAG_EXCLUDE_STOPPED_PACKAGES。
|
||||
* --include-stopped-packages
|
||||
包含标志 FLAG_INCLUDE_STOPPED_PACKAGES。
|
||||
* --activity-brought-to-front
|
||||
包含标志 FLAG_ACTIVITY_BROUGHT_TO_FRONT。
|
||||
* --activity-clear-top
|
||||
包含标志 FLAG_ACTIVITY_CLEAR_TOP。
|
||||
* --activity-clear-when-task-reset
|
||||
包含标志 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET。
|
||||
* --activity-exclude-from-recents
|
||||
包含标志 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS。
|
||||
* --activity-launched-from-history
|
||||
包含标志 FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY。
|
||||
* --activity-multiple-task
|
||||
包含标志 FLAG_ACTIVITY_MULTIPLE_TASK。
|
||||
* --activity-no-animation
|
||||
包含标志 FLAG_ACTIVITY_NO_ANIMATION。
|
||||
* --activity-no-history
|
||||
包含标志 FLAG_ACTIVITY_NO_HISTORY。
|
||||
* --activity-no-user-action
|
||||
包含标志 FLAG_ACTIVITY_NO_USER_ACTION。
|
||||
* --activity-previous-is-top
|
||||
包含标志 FLAG_ACTIVITY_PREVIOUS_IS_TOP。
|
||||
* --activity-reorder-to-front
|
||||
包含标志 FLAG_ACTIVITY_REORDER_TO_FRONT。
|
||||
* --activity-reset-task-if-needed
|
||||
包含标志 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED。
|
||||
* --activity-single-top
|
||||
包含标志 FLAG_ACTIVITY_SINGLE_TOP。
|
||||
* --activity-clear-task
|
||||
包含标志 FLAG_ACTIVITY_CLEAR_TASK。
|
||||
* --activity-task-on-home
|
||||
包含标志 FLAG_ACTIVITY_TASK_ON_HOME。
|
||||
* --receiver-registered-only
|
||||
包含标志 FLAG_RECEIVER_REGISTERED_ONLY。
|
||||
* --receiver-replace-pending
|
||||
包含标志 FLAG_RECEIVER_REPLACE_PENDING。
|
||||
* --selector
|
||||
需要使用 -d 和 -t 选项以设置 intent 数据和类型。
|
||||
#### URI component package
|
||||
如果不受上述某一选项的限制,您可以直接指定 URI、软件包名称和组件名称。当参数不受限制时,如果参数包含一个“:”(冒号),则此工具假定参数是一个 URI;如果参数包含一个“/”(正斜杠),则此工具假定参数是一个组件名称;否则,此工具假定参数是一个软件包名称。
|
||||
|
||||
<h2 id="PackageName"> 应用包名 </h2>
|
||||
所谓应用包名,是唯一确定应用的标识。例如微信的包名是"com.tencent.mm", QQ的包名是"com.tencent.mobileqq"。
|
||||
要获取一个应用的包名,可以通过函数`getPackageName(appName)`获取。参见帮助->其他一般函数。
|
||||
|
||||
|
||||
<h2 id="pm">pm命令</h2>
|
||||
|
||||
pm命令用于管理应用程序,例如卸载应用、冻结应用等。
|
||||
**以下命令均以"pm "开头,例如"shell(\"pm disable com.tencent.mm\");"(冻结微信)**
|
||||
|
||||
### list packages [options] filter
|
||||
输出所有软件包,或者,仅输出包名称包含 filter 中的文本的软件包。
|
||||
选项:
|
||||
|
||||
* -f:查看它们的关联文件。
|
||||
* -d:进行过滤以仅显示已停用的软件包。
|
||||
* -e:进行过滤以仅显示已启用的软件包。
|
||||
* -s:进行过滤以仅显示系统软件包。
|
||||
* -3:进行过滤以仅显示第三方软件包。
|
||||
* -i:查看软件包的安装程序。
|
||||
* -u:也包括卸载的软件包。
|
||||
* --user user_id:要查询的用户空间。
|
||||
|
||||
### list permission-groups
|
||||
输出所有已知的权限组。
|
||||
### list permissions [options] group
|
||||
输出所有已知权限,或者,仅输出 group 中的权限。
|
||||
选项:
|
||||
|
||||
* -g:按组加以组织。
|
||||
* -f:输出所有信息。
|
||||
* -s:简短摘要。
|
||||
* -d:仅列出危险权限。
|
||||
* -u:仅列出用户将看到的权限。
|
||||
|
||||
### list instrumentation [options]
|
||||
列出所有测试软件包。
|
||||
选项:
|
||||
|
||||
* -f:列出用于测试软件包的 APK 文件。
|
||||
* target_package:列出仅用于此应用的测试软件包。
|
||||
|
||||
### list features
|
||||
输出系统的所有功能。
|
||||
### list libraries
|
||||
输出当前设备支持的所有库。
|
||||
### list users
|
||||
输出系统上的所有用户。
|
||||
### path package
|
||||
输出给定 package 的 APK 的路径。
|
||||
### install [options] path
|
||||
将软件包(通过 path 指定)安装到系统。
|
||||
选项:
|
||||
|
||||
* -l:安装具有转发锁定功能的软件包。
|
||||
* -r:重新安装现有应用,保留其数据。
|
||||
* -t:允许安装测试 APK。
|
||||
* -i installer_package_name:指定安装程序软件包名称。
|
||||
* -s:在共享的大容量存储(如 sdcard)上安装软件包。
|
||||
* -f:在内部系统内存上安装软件包。
|
||||
* -d:允许版本代码降级。
|
||||
* -g:授予应用清单文件中列出的所有权限。
|
||||
|
||||
### uninstall [options] package
|
||||
|
||||
从系统中卸载软件包。
|
||||
选项:
|
||||
|
||||
* -k:移除软件包后保留数据和缓存目录。
|
||||
### clear package
|
||||
删除与软件包关联的所有数据。
|
||||
|
||||
### enable package_or_component
|
||||
启用给定软件包或组件(作为“package/class”写入)。
|
||||
|
||||
### disable package_or_component
|
||||
停用给定软件包或组件(作为“package/class”写入)。
|
||||
|
||||
### disable-user [options] package_or_component
|
||||
|
||||
选项:
|
||||
|
||||
* --user user_id:要停用的用户。
|
||||
### grant package_name permission
|
||||
向应用授予权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。
|
||||
|
||||
### revoke package_name permission
|
||||
从应用中撤销权限。在运行 Android 6.0(API 级别 23)及更高版本的设备上,可以是应用清单中声明的任何权限。在运行 Android 5.1(API 级别 22)和更低版本的设备上,必须是应用定义的可选权限。
|
||||
|
||||
### set-install-location location
|
||||
更改默认安装位置。位置值:
|
||||
* 0:自动—让系统决定最佳位置。
|
||||
* 1:内部—安装在内部设备存储上。
|
||||
* 2:外部—安装在外部介质上。
|
||||
|
||||
> 注:此命令仅用于调试目的;使用此命令会导致应用中断和其他意外行为。
|
||||
|
||||
### get-install-location
|
||||
返回当前安装位置。返回值:
|
||||
* 0 [auto]:让系统决定最佳位置。
|
||||
* 1 [internal]:安装在内部设备存储上
|
||||
* 2 [external]:安装在外部介质上
|
||||
|
||||
### set-permission-enforced permission [true|false]
|
||||
指定是否应强制执行给定的权限。
|
||||
|
||||
### trim-caches desired_free_space
|
||||
减少缓存文件以达到给定的可用空间。
|
||||
|
||||
### create-user user_name
|
||||
使用给定的 user_name 创建新用户,输出新用户的标识符。
|
||||
|
||||
### remove-user user_id
|
||||
移除具有给定的 user_id 的用户,删除与该用户关联的所有数据。
|
||||
|
||||
### get-max-users
|
||||
输出设备支持的最大用户数。
|
||||
|
||||
<h2 id="others">其他命令</h2>
|
||||
|
||||
### 进行屏幕截图
|
||||
screencap 命令是一个用于对设备显示屏进行屏幕截图的 shell 实用程序。在 shell 中,此语法为:
|
||||
```
|
||||
screencap filename
|
||||
```
|
||||
|
||||
例如:
|
||||
```
|
||||
$ shell("screencap /sdcard/screen.png");
|
||||
```
|
||||
|
||||
### 列表文件
|
||||
```
|
||||
ls filepath
|
||||
```
|
||||
例如:
|
||||
```
|
||||
log(shell("ls /system/bin"));
|
||||
```
|
||||
24
app/src/main/assets/help/documentation/控制台.md
Normal file
24
app/src/main/assets/help/documentation/控制台.md
Normal file
@@ -0,0 +1,24 @@
|
||||
控制台通常用来输出一些调试信息和运算结果。
|
||||
|
||||
### openConsole()
|
||||
显示控制台。
|
||||
### clearConsole()
|
||||
清空控制台。
|
||||
### log(text)
|
||||
* text \<String\> | \<Object\> 要打印到控制台的信息
|
||||
|
||||
在控制台中输出日志,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。
|
||||
|
||||
### err(error)
|
||||
* error \<String> | \<Object\>
|
||||
|
||||
在控制台中输出错误信息,以红色字体显示,例如:
|
||||
```
|
||||
try{
|
||||
//do something
|
||||
}catch(e){
|
||||
err("错误");
|
||||
err(e);
|
||||
openConsole();
|
||||
}
|
||||
```
|
||||
48
app/src/main/assets/help/documentation/自动操作函数.md
Normal file
48
app/src/main/assets/help/documentation/自动操作函数.md
Normal file
@@ -0,0 +1,48 @@
|
||||
**自动操作的函数都需要开启"自动操作服务"才能执行,否则执行到相应函数时脚本会停止运行。**
|
||||
|
||||
您也可以在设置中开启"使用Root权限自动开启自动操作服务",从而在运行到相应语句时可以自动开启。
|
||||
### click(text[, i])
|
||||
* text \<String\> 要点击的区域的文本
|
||||
* i \<Number\> 如果相同的文本在屏幕中出现多次,则i表示要点击第几个文本
|
||||
|
||||
返回是否点击成功。当屏幕中并未包含该文本,或者该文本所在区域不能点击时返回false,否则返回true。
|
||||
|
||||
该函数可以点击大部分包含文字的按钮。例如微信主界面下方的"微信", "联系人", "发现", "我"的按钮。
|
||||
通常与while同时使用以便点击按钮直至成功。例如:
|
||||
```
|
||||
while(!click("扫一扫"));
|
||||
```
|
||||
可选参数i通常不必指定,但当屏幕中出现相同的文字时,例如
|
||||
* `click(text, i)` 点击第i个文本text所在的区域,并返回是否点击成功。当不指定时点击所有区域。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。
|
||||
> 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。
|
||||
### click(left, top, bottom, right)
|
||||
* left 要点击的长方形区域左边与屏幕左边的像素距离
|
||||
* top 要点击的长方形区域上边与屏幕上边的像素距离
|
||||
* bottom 要点击的长方形区域下边与屏幕上边的像素距离
|
||||
* right 要点击的长方形区域右边与屏幕左边的像素距离
|
||||
|
||||
当屏幕中并未包含与该区域严格匹配的区域,或者该区域不能点击时返回false,否则返回true。
|
||||
有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text[, i])`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。
|
||||
至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。
|
||||
也可以通过录制脚本会生成类似语句。
|
||||
|
||||
**以下的longClick、select、scrollUp、scrollDown的参数均与click类似,不再赘述。**
|
||||
|
||||
### longClick
|
||||
长按
|
||||
### select
|
||||
选择
|
||||
### scrollUp
|
||||
上滑。另外不加参数时`scrollUp();`会寻找"最大"的可滑动的控件上滑,例如微信消息列表等。
|
||||
参数为一个整数i时会找到第i + 1个可滑动控件滑动。例如`scrollUp(0);`。
|
||||
### scrollDown
|
||||
下滑。不加参数时与scrollUp类似。
|
||||
### input([i, ]text)
|
||||
* i \<Number\> 表示要输入的为第i + 1个输入框
|
||||
* text \<String\> 要输入的文本
|
||||
|
||||
返回是否输入成功。当找不到对应的文本框时返回false。
|
||||
这里的输入文本的意思是,把输入框的文本置为text,而不是在原来的文本上追加。
|
||||
不加参数i则会把所有输入框的文本都置为text。例如`input("测试")`。
|
||||
### input(i, text)
|
||||
把第i + 1个输入框的文本设为text。
|
||||
0
app/src/main/assets/help/documentation/语法.md
Normal file
0
app/src/main/assets/help/documentation/语法.md
Normal file
143
app/src/main/assets/help/documentation/需要Root权限的自动操作函数.md
Normal file
143
app/src/main/assets/help/documentation/需要Root权限的自动操作函数.md
Normal file
@@ -0,0 +1,143 @@
|
||||
以下函数均需要root权限,可以实现任意位置的点击、滑动、长按、模拟物理按键等。
|
||||
|
||||
这些函数通常首字母大写以表示其特殊的权限。
|
||||
函数是返回值均是一个布尔值。true表示成功,false表示失败。一般而言,只有没有root权限时才会运行失败。
|
||||
|
||||
### Tap(x, y)
|
||||
点击位置(x, y), 您可以通过"开发者选项"开启指针位置来确定点击坐标。
|
||||
|
||||
### Swipe(x1, y1, x2, y2)
|
||||
滑动。从(x1, y1)位置滑动到(x2, y2)位置。
|
||||
|
||||
### Home()
|
||||
按下Home键。
|
||||
|
||||
### Back()
|
||||
按下返回键。
|
||||
|
||||
### Power()
|
||||
按下电源键。
|
||||
|
||||
### Menu()
|
||||
按下菜单键。
|
||||
|
||||
### Up()
|
||||
模拟按下物理按键上。
|
||||
|
||||
### Down()
|
||||
模拟按下物理按键上。
|
||||
|
||||
### Left()
|
||||
模拟按下物理按键上。
|
||||
|
||||
### Right()
|
||||
模拟按下物理按键上。
|
||||
|
||||
### OK()
|
||||
模拟按下物理按键确定。
|
||||
|
||||
### VolumeUp()
|
||||
按下音量上键。
|
||||
|
||||
### VolumeDown()
|
||||
按键音量上键。
|
||||
|
||||
### Text(text)
|
||||
* text <String> 要输入的文字
|
||||
输入文字text。例如`Text("测试");`
|
||||
|
||||
### Camera()
|
||||
模拟按下照相键。
|
||||
|
||||
### KeyCode(code)
|
||||
* code <Number> | <String> 要按下的按键的数字代码或名称。参见下表。
|
||||
模拟物理按键。例如`KeyCode(29)`和`KeyCode("KEYCODE_A")`是按下A键。
|
||||
|
||||
KeyCode KeyEvent Value
|
||||
* KEYCODE_MENU 1
|
||||
* KEYCODE_SOFT_RIGHT 2
|
||||
* KEYCODE_HOME 3
|
||||
* KEYCODE_BACK 4
|
||||
* KEYCODE_CALL 5
|
||||
* KEYCODE_ENDCALL 6
|
||||
* KEYCODE_0 7
|
||||
* KEYCODE_1 8
|
||||
* KEYCODE_2 9
|
||||
* KEYCODE_3 10
|
||||
* KEYCODE_4 11
|
||||
* KEYCODE_5 12
|
||||
* KEYCODE_6 13
|
||||
* KEYCODE_7 14
|
||||
* KEYCODE_8 15
|
||||
* KEYCODE_9 16
|
||||
* KEYCODE_STAR 17
|
||||
* KEYCODE_POUND 18
|
||||
* KEYCODE_DPAD_UP 19
|
||||
* KEYCODE_DPAD_DOWN 20
|
||||
* KEYCODE_DPAD_LEFT 21
|
||||
* KEYCODE_DPAD_RIGHT 22
|
||||
* KEYCODE_DPAD_CENTER 23
|
||||
* KEYCODE_VOLUME_UP 24
|
||||
* KEYCODE_VOLUME_DOWN 25
|
||||
* KEYCODE_POWER 26
|
||||
* KEYCODE_CAMERA 27
|
||||
* KEYCODE_CLEAR 28
|
||||
* KEYCODE_A 29
|
||||
* KEYCODE_B 30
|
||||
* KEYCODE_C 31
|
||||
* KEYCODE_D 32
|
||||
* KEYCODE_E 33
|
||||
* KEYCODE_F 34
|
||||
* KEYCODE_G 35
|
||||
* KEYCODE_H 36
|
||||
* KEYCODE_I 37
|
||||
* KEYCODE_J 38
|
||||
* KEYCODE_K 39
|
||||
* KEYCODE_L 40
|
||||
* KEYCODE_M 41
|
||||
* KEYCODE_N 42
|
||||
* KEYCODE_O 43
|
||||
* KEYCODE_P 44
|
||||
* KEYCODE_Q 45
|
||||
* KEYCODE_R 46
|
||||
* KEYCODE_S 47
|
||||
* KEYCODE_T 48
|
||||
* KEYCODE_U 49
|
||||
* KEYCODE_V 50
|
||||
* KEYCODE_W 51
|
||||
* KEYCODE_X 52
|
||||
* KEYCODE_Y 53
|
||||
* KEYCODE_Z 54
|
||||
* KEYCODE_COMMA 55
|
||||
* KEYCODE_PERIOD 56
|
||||
* KEYCODE_ALT_LEFT 57
|
||||
* KEYCODE_ALT_RIGHT 58
|
||||
* KEYCODE_SHIFT_LEFT 59
|
||||
* KEYCODE_SHIFT_RIGHT 60
|
||||
* KEYCODE_TAB 61
|
||||
* KEYCODE_SPACE 62
|
||||
* KEYCODE_SYM 63
|
||||
* KEYCODE_EXPLORER 64
|
||||
* KEYCODE_ENVELOPE 65
|
||||
* KEYCODE_ENTER 66
|
||||
* KEYCODE_DEL 67
|
||||
* KEYCODE_GRAVE 68
|
||||
* KEYCODE_MINUS 69
|
||||
* KEYCODE_EQUALS 70
|
||||
* KEYCODE_LEFT_BRACKET 71
|
||||
* KEYCODE_RIGHT_BRACKET 72
|
||||
* KEYCODE_BACKSLASH 73
|
||||
* KEYCODE_SEMICOLON 74
|
||||
* KEYCODE_APOSTROPHE 75
|
||||
* KEYCODE_SLASH 76
|
||||
* KEYCODE_AT 77
|
||||
* KEYCODE_NUM 78
|
||||
* KEYCODE_HEADSETHOOK 79
|
||||
* KEYCODE_FOCUS 80
|
||||
* KEYCODE_PLUS 81
|
||||
* KEYCODE_MENU 82
|
||||
* KEYCODE_NOTIFICATION 83
|
||||
* KEYCODE_SEARCH 84
|
||||
* TAG_LAST_ KEYCODE 85
|
||||
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
|
||||
if(__engine__ == "rhino"){
|
||||
__importClassOld__ = importClass;
|
||||
var importClass = function(pack){
|
||||
if(typeof(pack) == "string"){
|
||||
__importClassOld__(Packages[pack]);
|
||||
}else{
|
||||
__importClassOld__(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var toast = function(text){
|
||||
droid.toast(text);
|
||||
}
|
||||
@@ -22,8 +33,10 @@ var launchApp = function(appName){
|
||||
function performAction(action, args){
|
||||
if(args.length == 4){
|
||||
return action(droid.bounds(args[0], args[1], args[2], args[3]));
|
||||
}else{
|
||||
return action(droid.text(args[0]));
|
||||
}else if(args.length == 2){
|
||||
return action(droid.text(args[0], args[1]));
|
||||
}else {
|
||||
return action(droid.text(args[0], -1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +52,10 @@ var longClick = function(a, b, c, d){
|
||||
}, arguments);
|
||||
}
|
||||
|
||||
var clickId = function(id){
|
||||
return droid.click(droid.id(id));
|
||||
}
|
||||
|
||||
var scrollUp = function(a, b, c, d){
|
||||
if(arguments.length == 0)
|
||||
return droid.scrollAllUp();
|
||||
@@ -136,4 +153,76 @@ var getActivityName = function(){
|
||||
|
||||
var setClip = function(text){
|
||||
droid.setClip(text);
|
||||
}
|
||||
}
|
||||
|
||||
var Tap = function(x, y){
|
||||
return shell("input tap " + x + " " + y, true).code == 1;
|
||||
}
|
||||
|
||||
var Swipe = function(x1, y1, x2, y2){
|
||||
return shell("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2, true).code == 1;
|
||||
}
|
||||
|
||||
var KeyCode = function(keyCode){
|
||||
return shell("input keyevent " + keyCode, true).code == 1;
|
||||
}
|
||||
|
||||
var Home = function(){
|
||||
return KeyCode(3);
|
||||
}
|
||||
|
||||
var Back = function(){
|
||||
return KeyCode(4);
|
||||
}
|
||||
|
||||
var Power = function(){
|
||||
return KeyCode(26);
|
||||
}
|
||||
|
||||
var Up = function(){
|
||||
return KeyCode(19);
|
||||
}
|
||||
|
||||
var Down = function(){
|
||||
return KeyCode(20);
|
||||
}
|
||||
|
||||
var Left = function(){
|
||||
return KeyCode(21);
|
||||
}
|
||||
|
||||
var Right = function(){
|
||||
return KeyCode(22);
|
||||
}
|
||||
|
||||
var OK = function(){
|
||||
return KeyCode(23);
|
||||
}
|
||||
|
||||
var VolumeUp = function(){
|
||||
return KeyCode(24);
|
||||
}
|
||||
|
||||
var VolumeDown = function(){
|
||||
return KeyCode(25);
|
||||
}
|
||||
|
||||
var Menu = function(){
|
||||
return KeyCode(1);
|
||||
}
|
||||
|
||||
var Camera = function(){
|
||||
return KeyCode(27);
|
||||
}
|
||||
|
||||
var Text = function(text){
|
||||
return shell("input text " + text, true).code == 1;
|
||||
}
|
||||
|
||||
/*
|
||||
importClass("com.stardust.scriptdroid.service.AccessibilityDelegate");
|
||||
|
||||
var addAccessibilityDelegate = function(delegate){
|
||||
droid.addAccessibilityDelegate(delegate);
|
||||
}
|
||||
*/
|
||||
@@ -3,7 +3,6 @@
|
||||
importClass("android.widget.EditText");
|
||||
importClass("android.widget.Button");
|
||||
importClass("android.widget.LinearLayout");
|
||||
importClass("android.view.View.OnClickListener");
|
||||
importClass("android.content.Intent");
|
||||
importClass("android.net.Uri");
|
||||
|
||||
@@ -16,7 +15,7 @@ container.addView(qq);
|
||||
container.addView(btnOk);
|
||||
activity.setContentView(container);
|
||||
|
||||
btnOk.setOnClickListener(new OnClickListener(function(view){
|
||||
btnOk.setOnClickListener(function(view){
|
||||
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("mqqwpa://im/chat?chat_type=wpa&uin=" + qq.getText())));
|
||||
activity.finish();
|
||||
}));
|
||||
});
|
||||
@@ -14,9 +14,9 @@ function openRunningServices(){
|
||||
}
|
||||
|
||||
|
||||
importClass("android.os.Build.VERSION");
|
||||
importClass("android.os.Build");
|
||||
|
||||
if(VERSION.SDK_INT < 23){
|
||||
if(Build.VERSION.SDK_INT < 23){
|
||||
toast("本代码只适用于Android6.0以上");
|
||||
}else{
|
||||
openRunningServices();
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
"ui";
|
||||
|
||||
importClass("com.afollestad.materialdialogs.MaterialDialog.Builder");
|
||||
importClass("com.afollestad.materialdialogs.MaterialDialog.InputCallback");
|
||||
importClass("android.content.DialogInterface.OnDismissListener");
|
||||
importClass("com.afollestad.materialdialogs.MaterialDialog");
|
||||
|
||||
new Builder(activity)
|
||||
new ThemeColorMaterialDialogBuilder(activity)
|
||||
.title("简单计算器")
|
||||
.input("请输入算式", "1+1", new InputCallback(function(dialog, input){
|
||||
.input("请输入算式", "1+1", function(dialog, input){
|
||||
eval("var ans = (" + input + ")");
|
||||
toast(ans);
|
||||
}))
|
||||
})
|
||||
.positiveText("计算")
|
||||
.dismissListener(new OnDismissListener(function(dialog){
|
||||
.dismissListener(function(dialog){
|
||||
activity.finish();
|
||||
}))
|
||||
})
|
||||
.show();
|
||||
@@ -8,6 +8,7 @@ import android.widget.CompoundButton;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import static com.jecelyin.common.utils.StringUtils.md5;
|
||||
|
||||
@@ -22,7 +23,7 @@ public class NotRemindAgainDialog extends MaterialDialog {
|
||||
}
|
||||
|
||||
|
||||
public static class Builder extends MaterialDialog.Builder {
|
||||
public static class Builder extends ThemeColorMaterialDialogBuilder {
|
||||
|
||||
private String mKeyRemind;
|
||||
private boolean mRemind;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.stardust.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/5.
|
||||
*/
|
||||
|
||||
public interface OnActivityResultDelegate {
|
||||
|
||||
|
||||
void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
|
||||
interface DelegateHost {
|
||||
Manager getDelegateManger();
|
||||
}
|
||||
|
||||
class Manager implements OnActivityResultDelegate {
|
||||
|
||||
private SparseArray<OnActivityResultDelegate> specialDelegate = new SparseArray<>();
|
||||
private List<OnActivityResultDelegate> mDelegates = new ArrayList<>();
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
OnActivityResultDelegate delegate = specialDelegate.get(requestCode);
|
||||
if (delegate != null) {
|
||||
delegate.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
for (OnActivityResultDelegate d : mDelegates) {
|
||||
d.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void addDelegate(OnActivityResultDelegate delegate) {
|
||||
mDelegates.add(delegate);
|
||||
}
|
||||
|
||||
public void addDelegate(int requestCode, OnActivityResultDelegate delegate) {
|
||||
specialDelegate.put(requestCode, delegate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,9 +11,13 @@ import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityD
|
||||
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.scriptdroid.ui.error.ErrorReportActivity;
|
||||
import com.stardust.theme.ThemeColorManager;
|
||||
import com.stardust.util.CrashHandler;
|
||||
import com.stardust.util.StateObserver;
|
||||
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
@@ -37,6 +41,7 @@ public class App extends Application {
|
||||
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
ThemeColorManager.init(this);
|
||||
if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class Pref {
|
||||
private static final SharedPreferences DISPOSABLE_BOOLEAN = App.getApp().getSharedPreferences("DISPOSABLE_BOOLEAN", Context.MODE_PRIVATE);
|
||||
public static final String SAMPLE_SCRIPTS_COPIED = "SAMPLE_SCRIPTS_COPIED";
|
||||
private static final String KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION = "KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION";
|
||||
public static final String KEY_DRAWER_HEADER_IMAGE_PATH = "KEY_DRAWER_HEADER_IMAGE_PATH";
|
||||
|
||||
public static SharedPreferences def() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(App.getApp());
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.service.AccessibilityDelegate;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
|
||||
/**
|
||||
@@ -38,7 +39,7 @@ public class BoundsAssistant implements AccessibilityDelegate {
|
||||
}
|
||||
|
||||
private static void showAssistModeInfoDialog() {
|
||||
new MaterialDialog.Builder(App.currentActivity())
|
||||
new ThemeColorMaterialDialogBuilder(App.currentActivity())
|
||||
.title(R.string.text_alert)
|
||||
.content(R.string.assist_mode_notice)
|
||||
.positiveText(R.string.ok)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.stardust.scriptdroid.droid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.jraska.console.timber.ConsoleTree;
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
|
||||
import com.stardust.scriptdroid.droid.script.DuktapeJavaScriptEngine;
|
||||
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
|
||||
import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
|
||||
import com.stardust.scriptdroid.droid.script.RhinoJavaScriptEngine;
|
||||
import com.stardust.scriptdroid.droid.script.ScriptExecuteActivity;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
|
||||
@@ -23,6 +24,8 @@ import timber.log.Timber;
|
||||
|
||||
public class Droid {
|
||||
|
||||
public static final java.lang.String STAY = "\"stay\";";
|
||||
|
||||
static {
|
||||
Timber.plant(new ConsoleTree.Builder()
|
||||
.infoColor(0xcc000000)
|
||||
@@ -32,15 +35,30 @@ public class Droid {
|
||||
public static final String UI = "\"ui\";";
|
||||
|
||||
public interface OnRunFinishedListener extends Serializable {
|
||||
void onRunFinished(Object result, Exception e);
|
||||
void onRunFinished(Object result);
|
||||
|
||||
void onException(@NonNull Exception e);
|
||||
}
|
||||
|
||||
public static class SimpleOnRunFinishedListener implements OnRunFinishedListener {
|
||||
|
||||
@Override
|
||||
public void onRunFinished(Object result) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(@NonNull Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime();
|
||||
public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = new DuktapeJavaScriptEngine(RUNTIME);
|
||||
private static final OnRunFinishedListener DEFAULT_LISTENER = new OnRunFinishedListener() {
|
||||
public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = new RhinoJavaScriptEngine(RUNTIME);
|
||||
private static final OnRunFinishedListener DEFAULT_LISTENER = new SimpleOnRunFinishedListener() {
|
||||
@Override
|
||||
public void onRunFinished(Object result, Exception e) {
|
||||
if (e != null) {
|
||||
public void onException(@NonNull Exception e) {
|
||||
if (!(e instanceof ScriptStopException)) {
|
||||
RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage());
|
||||
Timber.e(e, App.getApp().getString(R.string.text_error));
|
||||
}
|
||||
@@ -65,7 +83,7 @@ public class Droid {
|
||||
try {
|
||||
checkFile(file);
|
||||
} catch (Exception e) {
|
||||
listener.onRunFinished(null, e);
|
||||
listener.onException(e);
|
||||
return;
|
||||
}
|
||||
runScript(FileUtils.readString(file), listener, RunningConfig.getDefault());
|
||||
@@ -125,15 +143,11 @@ public class Droid {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mOnRunFinishedListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(mScript), null);
|
||||
mOnRunFinishedListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(mScript));
|
||||
} catch (Exception e) {
|
||||
mOnRunFinishedListener.onRunFinished(null, e);
|
||||
mOnRunFinishedListener.onException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RunScriptRunnable setActivity(Activity a) {
|
||||
JAVA_SCRIPT_ENGINE.set("activity", Activity.class, a);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.stardust.scriptdroid.droid.runtime;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
@@ -7,14 +8,17 @@ import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.jraska.console.Console;
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.droid.runtime.action.Action;
|
||||
import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
|
||||
@@ -22,10 +26,15 @@ import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityD
|
||||
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
|
||||
import com.stardust.scriptdroid.droid.runtime.action.GetTextAction;
|
||||
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
import com.stardust.scriptdroid.service.AccessibilityDelegate;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.scriptdroid.tool.Shell;
|
||||
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
import com.stardust.view.accessibility.AccessibilityServiceUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
@@ -81,8 +90,8 @@ public class DroidRuntime implements IDroidRuntime {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionTarget text(String text) {
|
||||
return new ActionTarget.TextActionTarget(text);
|
||||
public ActionTarget text(String text, int i) {
|
||||
return new ActionTarget.TextActionTarget(text, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,6 +104,10 @@ public class DroidRuntime implements IDroidRuntime {
|
||||
return new ActionTarget.EditableActionTarget(i);
|
||||
}
|
||||
|
||||
public ActionTarget id(String id) {
|
||||
return new ActionTarget.IdActionTarget(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean click(ActionTarget target) {
|
||||
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
|
||||
@@ -194,8 +207,22 @@ public class DroidRuntime implements IDroidRuntime {
|
||||
|
||||
private void ensureAccessibilityServiceEnable() {
|
||||
if (AccessibilityWatchDogService.getInstance() == null) {
|
||||
toast(App.getApp().getString(R.string.text_no_accessibility_permission));
|
||||
throw new ScriptStopException(App.getApp().getString(R.string.text_no_accessibility_permission));
|
||||
String errorMessage = null;
|
||||
if (AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class)) {
|
||||
errorMessage = App.getApp().getString(R.string.text_auto_operate_service_enabled_but_not_running);
|
||||
} else {
|
||||
if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
|
||||
if (!AccessibilityServiceUtils.enableAccessibilityServiceByRootAndWaitFor(App.getApp(), AccessibilityWatchDogService.class, 3000)) {
|
||||
errorMessage = App.getApp().getString(R.string.text_enable_accessibility_service_by_root_timeout);
|
||||
}
|
||||
} else {
|
||||
errorMessage = App.getApp().getString(R.string.text_no_accessibility_permission);
|
||||
}
|
||||
}
|
||||
if (errorMessage != null) {
|
||||
toast(errorMessage);
|
||||
throw new ScriptStopException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,8 +249,20 @@ public class DroidRuntime implements IDroidRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
public String shell(String cmd, int root) {
|
||||
return Shell.execCommand(cmd, root != 0).toString();
|
||||
public void addAccessibilityDelegate(final Object delegate) {
|
||||
if (delegate == null)
|
||||
return;
|
||||
AccessibilityWatchDogService.addDelegate(new AccessibilityDelegate() {
|
||||
@Override
|
||||
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
|
||||
|
||||
return false;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
public Shell.CommandResult shell(String cmd, int root) {
|
||||
return Shell.execCommand(cmd, root != 0);
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
@@ -246,14 +285,17 @@ public class DroidRuntime implements IDroidRuntime {
|
||||
Thread.interrupted();
|
||||
}
|
||||
|
||||
public String readFile(String path) {
|
||||
return FileUtils.readString(new File(Environment.getExternalStorageDirectory() + "/" + path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialDialog.Builder dialog() {
|
||||
public ThemeColorMaterialDialogBuilder dialog() {
|
||||
if (App.currentActivity() == null) {
|
||||
Toast.makeText(App.getApp(), R.string.text_cannot_create_dialog_when_app_invisible, Toast.LENGTH_SHORT).show();
|
||||
throw new ScriptStopException(App.getApp().getString(R.string.text_cannot_create_dialog_when_app_invisible));
|
||||
}
|
||||
return new MaterialDialog.Builder(App.currentActivity()) {
|
||||
return new ThemeColorMaterialDialogBuilder(App.currentActivity()) {
|
||||
|
||||
public MaterialDialog show() {
|
||||
mUIHandler.post(new Runnable() {
|
||||
|
||||
@@ -24,11 +24,11 @@ public class ActionFactory {
|
||||
.entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
|
||||
.map();
|
||||
|
||||
public static Action createActionWithTextFilter(int action, String text) {
|
||||
public static Action createActionWithTextFilter(int action, String text, int index) {
|
||||
if (searchUpAction.containsKey(action))
|
||||
return new SearchUpTargetAction(action, new FilterAction.TextFilter(text));
|
||||
return new SearchUpTargetAction(action, new FilterAction.TextFilter(text, index));
|
||||
else
|
||||
return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text));
|
||||
return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text, index));
|
||||
}
|
||||
|
||||
public static Action createActionWithBoundsFilter(int action, Rect rect) {
|
||||
@@ -54,7 +54,11 @@ public class ActionFactory {
|
||||
return new ScrollMaxAction(action);
|
||||
}
|
||||
|
||||
public static Action createScrollAction(int action, int i){
|
||||
public static Action createScrollAction(int action, int i) {
|
||||
return new ScrollAction(action, i);
|
||||
}
|
||||
|
||||
public static Action createActionWithIdFilter(int action, String id) {
|
||||
return new FilterAction.SimpleFilterAction(action, new FilterAction.IdFilter(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,22 +43,24 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate
|
||||
|
||||
@Override
|
||||
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
|
||||
setLatestComponent(event.getPackageName(), event.getClassName());
|
||||
if (action == NO_ACTION)
|
||||
synchronized (ActionPerformAccessibilityDelegate.class) {
|
||||
setLatestComponent(event.getPackageName(), event.getClassName());
|
||||
if (action == NO_ACTION)
|
||||
return false;
|
||||
AccessibilityNodeInfo root = service.getRootInActiveWindow();
|
||||
if (root == null) {
|
||||
Log.v(TAG, "root = null");
|
||||
}
|
||||
Log.i(TAG, "perform action:" + action);
|
||||
if (action.perform(root)) {
|
||||
action.setResult(true);
|
||||
onActionPerformed();
|
||||
} else if (!action.performUtilSucceed()) {
|
||||
action.setResult(false);
|
||||
onActionPerformed();
|
||||
}
|
||||
return false;
|
||||
AccessibilityNodeInfo root = service.getRootInActiveWindow();
|
||||
if (root == null) {
|
||||
Log.v(TAG, "root = null");
|
||||
}
|
||||
Log.i(TAG, "perform action:" + action);
|
||||
if (action.perform(root)) {
|
||||
action.setResult(true);
|
||||
onActionPerformed();
|
||||
} else if (!action.performUtilSucceed()) {
|
||||
action.setResult(false);
|
||||
onActionPerformed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,18 +71,18 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized static void setLatestComponent(CharSequence latestPackage, CharSequence latestClass) {
|
||||
private static void setLatestComponent(CharSequence latestPackage, CharSequence latestClass) {
|
||||
if (latestPackage == null)
|
||||
return;
|
||||
ActionPerformAccessibilityDelegate.latestPackage = latestPackage.toString();
|
||||
if(latestClass == null)
|
||||
if (latestClass == null)
|
||||
return;
|
||||
try {
|
||||
ComponentName componentName = new ComponentName(latestPackage.toString(), latestClass.toString());
|
||||
ActivityInfo activityInfo = App.getApp().getPackageManager().getActivityInfo(componentName, 0);
|
||||
ActionPerformAccessibilityDelegate.latestActivity = activityInfo.name;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ public interface ActionTarget {
|
||||
class TextActionTarget implements ActionTarget {
|
||||
|
||||
String mText;
|
||||
int mIndex;
|
||||
|
||||
public TextActionTarget(String text) {
|
||||
public TextActionTarget(String text, int i) {
|
||||
mText = text;
|
||||
mIndex = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action createAction(int action, Object... params) {
|
||||
return ActionFactory.createActionWithTextFilter(action, mText);
|
||||
return ActionFactory.createActionWithTextFilter(action, mText, mIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +52,17 @@ public interface ActionTarget {
|
||||
return ActionFactory.createActionWithEditableFilter(action, mIndex, params[0].toString());
|
||||
}
|
||||
}
|
||||
|
||||
class IdActionTarget implements ActionTarget {
|
||||
private String mId;
|
||||
|
||||
public IdActionTarget(String id) {
|
||||
mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action createAction(int action, Object... params) {
|
||||
return ActionFactory.createActionWithIdFilter(action, mId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,22 @@ public abstract class FilterAction extends Action {
|
||||
public static class TextFilter implements Filter {
|
||||
|
||||
String mText;
|
||||
int mIndex;
|
||||
|
||||
public TextFilter(String text) {
|
||||
public TextFilter(String text, int index) {
|
||||
mText = text;
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
|
||||
return root.findAccessibilityNodeInfosByText(mText);
|
||||
|
||||
List<AccessibilityNodeInfo> list = root.findAccessibilityNodeInfosByText(mText);
|
||||
if (mIndex == -1)
|
||||
return list;
|
||||
if (mIndex >= list.size())
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.singletonList(list.get(mIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,21 +76,6 @@ public abstract class FilterAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Filter mFilter;
|
||||
|
||||
public FilterAction(Filter filter) {
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
public boolean perform(AccessibilityNodeInfo root) {
|
||||
if (root == null)
|
||||
return false;
|
||||
return perform(mFilter.filter(root));
|
||||
}
|
||||
|
||||
public abstract boolean perform(List<AccessibilityNodeInfo> nodes);
|
||||
|
||||
public static class EditableFilter implements Filter {
|
||||
|
||||
private int mIndex;
|
||||
@@ -116,4 +109,57 @@ public abstract class FilterAction extends Action {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdFilter implements Filter {
|
||||
|
||||
private final String mId;
|
||||
|
||||
public IdFilter(String id) {
|
||||
this.mId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityNodeInfo> filter(AccessibilityNodeInfo root) {
|
||||
return root.findAccessibilityNodeInfosByViewId(mId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Filter mFilter;
|
||||
|
||||
public FilterAction(Filter filter) {
|
||||
mFilter = filter;
|
||||
}
|
||||
|
||||
public boolean perform(AccessibilityNodeInfo root) {
|
||||
if (root == null)
|
||||
return false;
|
||||
return perform(mFilter.filter(root));
|
||||
}
|
||||
|
||||
public abstract boolean perform(List<AccessibilityNodeInfo> nodes);
|
||||
|
||||
public static class SimpleFilterAction extends FilterAction {
|
||||
|
||||
private int mAction;
|
||||
|
||||
public SimpleFilterAction(int action, Filter filter) {
|
||||
super(filter);
|
||||
mAction = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean perform(List<AccessibilityNodeInfo> nodes) {
|
||||
if(nodes == null || nodes.isEmpty())
|
||||
return false;
|
||||
boolean succeed = true;
|
||||
for(AccessibilityNodeInfo nodeInfo : nodes){
|
||||
if(!nodeInfo.performAction(mAction)){
|
||||
succeed = false;
|
||||
}
|
||||
}
|
||||
return succeed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.stardust.scriptdroid.droid.runtime.api;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/27.
|
||||
@@ -16,7 +17,7 @@ public interface IDroidRuntime {
|
||||
|
||||
String[] getPackageName(String appName);
|
||||
|
||||
ActionTarget text(String text);
|
||||
ActionTarget text(String text, int i);
|
||||
|
||||
ActionTarget bounds(int left, int top, int right, int bottom);
|
||||
|
||||
@@ -50,5 +51,5 @@ public interface IDroidRuntime {
|
||||
|
||||
void stop();
|
||||
|
||||
MaterialDialog.Builder dialog();
|
||||
ThemeColorMaterialDialogBuilder dialog();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.stardust.scriptdroid.droid.script;
|
||||
|
||||
import com.efurture.script.JSTransformer;
|
||||
import com.furture.react.DuktapeEngine;
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.droid.Droid;
|
||||
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
@@ -17,46 +17,40 @@ import java.util.TimerTask;
|
||||
/**
|
||||
* Created by Stardust on 2017/1/27.
|
||||
*/
|
||||
|
||||
public class DuktapeJavaScriptEngine implements JavaScriptEngine {
|
||||
/*
|
||||
public class DuktapeJavaScriptEngine extends JavaScriptEngine {
|
||||
|
||||
|
||||
private final Map<Thread, DuktapeEngine> mThreadDuktapeEngineMap = new Hashtable<>();
|
||||
private static final String INIT_SCRIPT;
|
||||
private static final String INIT_SCRIPT = parse(Init.getInitScript());
|
||||
private Map<String, Object> mVariableMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
INIT_SCRIPT = JSTransformer.parse(new StringReader(Init.INIT_SCRIPT));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DuktapeJavaScriptEngine(IDroidRuntime runtime) {
|
||||
setRuntime(runtime);
|
||||
}
|
||||
|
||||
private void setRuntime(IDroidRuntime runtime) {
|
||||
set("droid", IDroidRuntime.class, runtime);
|
||||
set("droid", runtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(String script) throws IOException {
|
||||
public Object execute(String script) {
|
||||
DuktapeEngine duktapeEngine = new DuktapeEngine();
|
||||
init(duktapeEngine);
|
||||
add(duktapeEngine, Thread.currentThread());
|
||||
Object result;
|
||||
Object code = duktapeEngine.execute(parse(script));
|
||||
if (!script.startsWith(Droid.UI) && !script.startsWith(Droid.STAY))
|
||||
removeAndDestroy();
|
||||
return code;
|
||||
}
|
||||
|
||||
private static String parse(String script) {
|
||||
try {
|
||||
result = duktapeEngine.execute(JSTransformer.parse(new StringReader(script)));
|
||||
return JSTransformer.parse(new StringReader(script));
|
||||
} catch (IOException e) {
|
||||
removeAndStop(Thread.currentThread());
|
||||
throw e;
|
||||
//Should not happen
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!script.startsWith(Droid.UI))
|
||||
removeAndDestroy(Thread.currentThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,18 +63,11 @@ public class DuktapeJavaScriptEngine implements JavaScriptEngine {
|
||||
|
||||
}
|
||||
|
||||
public void removeAndStop(Thread thread) {
|
||||
synchronized (mThreadDuktapeEngineMap) {
|
||||
DuktapeEngine engine = mThreadDuktapeEngineMap.remove(thread);
|
||||
forceStop(engine, thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeAndDestroy(Thread thread) {
|
||||
public void removeAndDestroy() {
|
||||
synchronized (mThreadDuktapeEngineMap) {
|
||||
DuktapeEngine engine = mThreadDuktapeEngineMap.remove(thread);
|
||||
DuktapeEngine engine = mThreadDuktapeEngineMap.remove(Thread.currentThread());
|
||||
if (engine != null)
|
||||
engine.destory();
|
||||
}
|
||||
@@ -109,10 +96,6 @@ public class DuktapeJavaScriptEngine implements JavaScriptEngine {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void set(String varName, Class<T> c, T value) {
|
||||
mVariableMap.put(varName, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stopAll() {
|
||||
@@ -127,4 +110,25 @@ public class DuktapeJavaScriptEngine implements JavaScriptEngine {
|
||||
return n;
|
||||
}
|
||||
|
||||
private static final Field ptrField;
|
||||
|
||||
static {
|
||||
Field field = null;
|
||||
try {
|
||||
field = DuktapeEngine.class.getDeclaredField("ptr");
|
||||
field.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ptrField = field;
|
||||
}
|
||||
|
||||
public static boolean isDestroyed(DuktapeEngine engine) {
|
||||
try {
|
||||
return ((long) ptrField.get(engine)) == 0;
|
||||
} catch (IllegalAccessException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,29 +1,47 @@
|
||||
package com.stardust.scriptdroid.droid.script;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.BuildConfig;
|
||||
import com.stardust.scriptdroid.droid.script.file.AssetScript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/27.
|
||||
*/
|
||||
|
||||
public interface JavaScriptEngine {
|
||||
public abstract class JavaScriptEngine {
|
||||
|
||||
Object execute(String script) throws IOException;
|
||||
Map<String, Object> mVariableMap = new HashMap<>();
|
||||
|
||||
<T> void set(String varName, Class<T> c, T value);
|
||||
protected String preprocess(String script) {
|
||||
return script;
|
||||
}
|
||||
|
||||
int stopAll();
|
||||
public abstract Object execute(String script);
|
||||
|
||||
void removeAndDestroy(Thread thread);
|
||||
public void set(String varName, Object value) {
|
||||
mVariableMap.put(varName, value);
|
||||
}
|
||||
|
||||
class Init {
|
||||
public static final String INIT_SCRIPT = readInitScript();
|
||||
public abstract void removeAndDestroy();
|
||||
|
||||
static String readInitScript() {
|
||||
public abstract int stopAll();
|
||||
|
||||
static class Init {
|
||||
private static final String INIT_SCRIPT = readInitScript();
|
||||
|
||||
private static String readInitScript() {
|
||||
return AssetScript.read(App.getApp(), "javasccript_engine_init.js");
|
||||
}
|
||||
|
||||
static String getInitScript() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return readInitScript();
|
||||
} else {
|
||||
return INIT_SCRIPT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.stardust.scriptdroid.droid.script;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.droid.Droid;
|
||||
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
|
||||
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
|
||||
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.ContextFactory;
|
||||
import org.mozilla.javascript.ImporterTopLevel;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/1.
|
||||
*/
|
||||
|
||||
public class RhinoJavaScriptEngine extends JavaScriptEngine {
|
||||
|
||||
static {
|
||||
ContextFactory.initGlobal(new InterruptibleContextFactory());
|
||||
}
|
||||
|
||||
private final Set<Thread> mThreads = new HashSet<>();
|
||||
|
||||
public RhinoJavaScriptEngine(IDroidRuntime runtime) {
|
||||
set("droid", runtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(String script) {
|
||||
Context context = createContext();
|
||||
Scriptable scope = createScope(context);
|
||||
init(context, scope);
|
||||
Object result = context.evaluateString(scope, script, "<script>", 1, null);
|
||||
if (!script.startsWith(Droid.UI) && !script.startsWith(Droid.STAY))
|
||||
removeAndDestroy();
|
||||
return result;
|
||||
}
|
||||
|
||||
private Scriptable createScope(Context context) {
|
||||
ImporterTopLevel importerTopLevel = new ImporterTopLevel();
|
||||
importerTopLevel.initStandardObjects(context, false);
|
||||
return importerTopLevel;
|
||||
}
|
||||
|
||||
private Context createContext() {
|
||||
Context context = Context.enter();
|
||||
context.setOptimizationLevel(-1);
|
||||
context.setInstructionObserverThreshold(10000);
|
||||
return context;
|
||||
}
|
||||
|
||||
private void init(Context context, Scriptable scope) {
|
||||
ScriptableObject.putProperty(scope, "context", App.getApp());
|
||||
ScriptableObject.putProperty(scope, "__engine__", "rhino");
|
||||
context.evaluateString(scope, Init.getInitScript(), "<init>", 1, null);
|
||||
for (Map.Entry<String, Object> variable : mVariableMap.entrySet()) {
|
||||
ScriptableObject.putProperty(scope, variable.getKey(), variable.getValue());
|
||||
}
|
||||
mThreads.add(Thread.currentThread());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int stopAll() {
|
||||
int n;
|
||||
synchronized (mThreads) {
|
||||
for (Thread thread : mThreads) {
|
||||
thread.interrupt();
|
||||
}
|
||||
n = mThreads.size();
|
||||
mThreads.clear();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAndDestroy() {
|
||||
synchronized (mThreads) {
|
||||
mThreads.remove(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
public static class InterruptibleContextFactory extends ContextFactory {
|
||||
|
||||
@Override
|
||||
protected void observeInstructionCount(Context cx, int instructionCount) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
Context.exit();
|
||||
throw new ScriptStopException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context makeContext() {
|
||||
Context cx = super.makeContext();
|
||||
cx.setInstructionObserverThreshold(10000);
|
||||
return cx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,25 +43,25 @@ public class ScriptExecuteActivity extends Activity {
|
||||
}
|
||||
|
||||
private void runScript() {
|
||||
JAVA_SCRIPT_ENGINE.set("activity", Activity.class, this);
|
||||
JAVA_SCRIPT_ENGINE.set("activity", this);
|
||||
try {
|
||||
mResult = JAVA_SCRIPT_ENGINE.execute(mScript);
|
||||
} catch (Exception e) {
|
||||
mOnRunFinishedListener.onRunFinished(null, e);
|
||||
mOnRunFinishedListener.onException(e);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
mOnRunFinishedListener.onRunFinished(mResult, null);
|
||||
mOnRunFinishedListener.onRunFinished(mResult);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
JAVA_SCRIPT_ENGINE.set("activity", Activity.class, null);
|
||||
JAVA_SCRIPT_ENGINE.removeAndDestroy(Thread.currentThread());
|
||||
JAVA_SCRIPT_ENGINE.set("activity", null);
|
||||
JAVA_SCRIPT_ENGINE.removeAndDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
|
||||
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
|
||||
import com.stardust.scriptdroid.ui.main.MainActivity;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class ImportIntentActivity extends BaseActivity {
|
||||
Intent intent = getIntent();
|
||||
final String path = intent.getData().getPath();
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(R.string.text_please_input_name)
|
||||
.input(getString(R.string.text_name), new File(path).getName(), new MaterialDialog.InputCallback() {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.stardust.scriptdroid.record.root;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.jecelyin.common.utils.StringUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/6.
|
||||
*/
|
||||
|
||||
public class InputEventConverter {
|
||||
|
||||
public static Pair<Double, String> convert(String line) {
|
||||
int j = line.indexOf(']');
|
||||
double time = 0;
|
||||
if (j > 0) {
|
||||
time = Double.parseDouble(line.substring(1, j));
|
||||
line = line.substring(j + 2);
|
||||
}
|
||||
String[] str = line.split(" ", 4);
|
||||
for (int i = 1; i < 4; i++) {
|
||||
str[i] = hex2dec(str[i]);
|
||||
}
|
||||
str[0] = str[0].substring(0, str[0].length() - 1);
|
||||
return new Pair<>(time, "sendevent " + TextUtils.join(" ", str));
|
||||
}
|
||||
|
||||
private static String hex2dec(String hex) {
|
||||
return String.valueOf((int) Long.parseLong(hex, 16));
|
||||
}
|
||||
|
||||
private static class SingleEvent {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("^\\[([^\\]]*)\\]\\s+([^:]*):\\s+([^\\s]*)\\s+([^\\s]*)\\s+([^\\s]*)$");
|
||||
|
||||
double time;
|
||||
String device;
|
||||
String type;
|
||||
String code;
|
||||
String value;
|
||||
|
||||
|
||||
SingleEvent(String str) {
|
||||
Matcher matcher = PATTERN.matcher(str);
|
||||
matcher.matches();
|
||||
time = Double.parseDouble(matcher.group(1));
|
||||
device = matcher.group(2);
|
||||
type = matcher.group(3);
|
||||
code = matcher.group(4);
|
||||
value = matcher.group(5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SingleEvent{" +
|
||||
"time=" + time +
|
||||
", device='" + device + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", code='" + code + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public String convertToCode(String events) {
|
||||
BufferedReader reader = new BufferedReader(new StringReader(events));
|
||||
String line;
|
||||
StringBuilder code = new StringBuilder();
|
||||
try {
|
||||
EventConverter converter = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
SingleEvent event = new SingleEvent(line);
|
||||
if (event.value.equals("DOWN") && event.type.equals("BIN_TOUCH")) {
|
||||
converter = new EventConverter();
|
||||
} else if (event.value.equals("UP") && event.type.equals("BIN_TOUCH")) {
|
||||
code.append(converter.toString());
|
||||
converter = null;
|
||||
} else if (converter != null) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
public static class EventConverter {
|
||||
|
||||
List<SingleEvent> mSingleEvents = new ArrayList<>();
|
||||
|
||||
public void add(SingleEvent event) {
|
||||
if (event.type.equals("ABS_MT_POSITION_X") || event.type.equals("ABS_MT_POSITION_Y")) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.stardust.scriptdroid.record.root;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import com.stardust.scriptdroid.tool.Shell;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/6.
|
||||
*/
|
||||
|
||||
public class InputEventRecorder {
|
||||
|
||||
private Thread mRecordThread;
|
||||
private Shell mShell;
|
||||
|
||||
public void record() {
|
||||
mShell = new Shell(true);
|
||||
mRecordThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mShell.execute("getevent -t");
|
||||
readOutput(mShell);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
mRecordThread.start();
|
||||
}
|
||||
|
||||
private void readOutput(Shell shell) {
|
||||
String line;
|
||||
try {
|
||||
BufferedReader succeedReader = shell.getSucceedReader();
|
||||
StringBuilder succeedOutput = shell.getSucceedOutput();
|
||||
double time = 0;
|
||||
DecimalFormat df = new DecimalFormat("#.###");
|
||||
while ((line = succeedReader.readLine()) != null) {
|
||||
if (line.startsWith("[")) {
|
||||
Pair<Double, String> c = InputEventConverter.convert(line);
|
||||
if (time == 0) {
|
||||
time = c.first;
|
||||
} else if (c.first - time > 0.1f) {
|
||||
succeedOutput.append("sleep ").append(df.format(c.first - time)).append("\n");
|
||||
time = c.first;
|
||||
}
|
||||
succeedOutput.append(c.second).append("\n");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String stop() {
|
||||
mRecordThread.interrupt();
|
||||
mShell.destroy();
|
||||
return mShell.getSucceedOutput().toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -58,6 +58,12 @@ public class AccessibilityWatchDogService extends AccessibilityService {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeDelegate(int priority) {
|
||||
synchronized (mDelegates) {
|
||||
return mDelegates.remove(priority) != null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEnable() {
|
||||
return AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.stardust.scriptdroid.tool;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.view.accessibility.AccessibilityServiceUtils;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/26.
|
||||
*/
|
||||
|
||||
public class AccessibilityServiceTool {
|
||||
|
||||
public static void enableAccessibilityService() {
|
||||
if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
|
||||
if (!AccessibilityServiceUtils.enableAccessibilityServiceByRootAndWaitFor(App.getApp(), AccessibilityWatchDogService.class, 3000)) {
|
||||
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
|
||||
}
|
||||
} else {
|
||||
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.stardust.scriptdroid.tool;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
|
||||
import com.stardust.app.OnActivityResultDelegate;
|
||||
import com.stardust.scriptdroid.R;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/5.
|
||||
*/
|
||||
|
||||
public class ImageSelector implements OnActivityResultDelegate {
|
||||
|
||||
public interface ImageSelectorCallback {
|
||||
void onImageSelected(String path);
|
||||
}
|
||||
|
||||
private static final int REQUEST_CODE = "LOVE EATING".hashCode() >> 16;
|
||||
private Activity mActivity;
|
||||
private ImageSelectorCallback mCallback;
|
||||
|
||||
public ImageSelector(Activity activity, Manager manager, ImageSelectorCallback callback) {
|
||||
manager.addDelegate(REQUEST_CODE, this);
|
||||
mActivity = activity;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void select() {
|
||||
mActivity.startActivityForResult(Intent.createChooser(
|
||||
new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), mActivity.getString(R.string.text_select_image)),
|
||||
REQUEST_CODE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (data == null)
|
||||
return;
|
||||
Uri selectedImageUri = data.getData();
|
||||
String[] filePathColumn = {MediaStore.Images.Media.DATA};
|
||||
Cursor cursor = mActivity.getContentResolver().query(selectedImageUri, filePathColumn, null, null, null);
|
||||
if (cursor != null) {
|
||||
cursor.moveToFirst();
|
||||
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
|
||||
String filePath = cursor.getString(columnIndex);
|
||||
cursor.close();
|
||||
mCallback.onImageSelected(filePath);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.stardust.scriptdroid.tool;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.stardust.scriptdroid.record.root.InputEventConverter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -23,20 +25,127 @@ public class Shell {
|
||||
private final static String COMMAND_EXIT = "exit\n";
|
||||
private final static String COMMAND_LINE_END = "\n";
|
||||
|
||||
private Process mProcess;
|
||||
private DataOutputStream mCommandOutputStream;
|
||||
private BufferedReader mSucceedReader;
|
||||
private BufferedReader mErrorReader;
|
||||
|
||||
private StringBuilder mSucceedOutput = new StringBuilder();
|
||||
private StringBuilder mErrorOutput = new StringBuilder();
|
||||
|
||||
public Shell() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public Shell(boolean root) {
|
||||
try {
|
||||
mProcess = Runtime.getRuntime().exec(root ? COMMAND_SU : COMMAND_SH);
|
||||
mCommandOutputStream = new DataOutputStream(mProcess.getOutputStream());
|
||||
mSucceedReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
|
||||
mErrorReader = new BufferedReader(new InputStreamReader(mProcess.getErrorStream()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Shell execute(String command) {
|
||||
try {
|
||||
mCommandOutputStream.writeBytes(command);
|
||||
if (!command.endsWith(COMMAND_LINE_END)) {
|
||||
mCommandOutputStream.writeBytes(COMMAND_LINE_END);
|
||||
}
|
||||
mCommandOutputStream.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shell exitAndWaitFor() {
|
||||
exit();
|
||||
try {
|
||||
mProcess.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shell readAll() {
|
||||
return readSucceedOutput().readErrorOutput();
|
||||
}
|
||||
|
||||
public Shell readSucceedOutput() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = mSucceedReader.readLine()) != null) {
|
||||
mSucceedOutput.append(line).append("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shell readErrorOutput() {
|
||||
String line;
|
||||
try {
|
||||
while ((line = mErrorReader.readLine()) != null) {
|
||||
mErrorOutput.append(line).append("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public StringBuilder getSucceedOutput() {
|
||||
return mSucceedOutput;
|
||||
}
|
||||
|
||||
public StringBuilder getErrorOutput() {
|
||||
return mErrorOutput;
|
||||
}
|
||||
|
||||
public Shell exit() {
|
||||
execute(COMMAND_EXIT);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shell destroy() {
|
||||
mProcess.destroy();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Process getProcess() {
|
||||
return mProcess;
|
||||
}
|
||||
|
||||
public BufferedReader getSucceedReader() {
|
||||
return mSucceedReader;
|
||||
}
|
||||
|
||||
public BufferedReader getErrorReader() {
|
||||
return mErrorReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Command执行结果
|
||||
*
|
||||
* @author Mountain
|
||||
*/
|
||||
public static class CommandResult {
|
||||
public int result = -1;
|
||||
public String errorMsg;
|
||||
public String successMsg;
|
||||
public int code = -1;
|
||||
public String error;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return result + " | " + successMsg
|
||||
+ " | " + errorMsg;
|
||||
return "ShellResult{" +
|
||||
"code=" + code +
|
||||
", error='" + error + '\'' +
|
||||
", result='" + result + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +189,7 @@ public class Shell {
|
||||
}
|
||||
os.writeBytes(COMMAND_EXIT);
|
||||
os.flush();
|
||||
commandResult.result = process.waitFor();
|
||||
commandResult.code = process.waitFor();
|
||||
//获取错误信息
|
||||
successMsg = new StringBuilder();
|
||||
errorMsg = new StringBuilder();
|
||||
@@ -89,8 +198,8 @@ public class Shell {
|
||||
String s;
|
||||
while ((s = successResult.readLine()) != null) successMsg.append(s);
|
||||
while ((s = errorResult.readLine()) != null) errorMsg.append(s);
|
||||
commandResult.successMsg = successMsg.toString();
|
||||
commandResult.errorMsg = errorMsg.toString();
|
||||
commandResult.result = successMsg.toString();
|
||||
commandResult.error = errorMsg.toString();
|
||||
Log.i(TAG, commandResult.toString());
|
||||
} catch (Exception e) {
|
||||
String errmsg = e.getMessage();
|
||||
@@ -117,4 +226,28 @@ public class Shell {
|
||||
return commandResult;
|
||||
}
|
||||
|
||||
}
|
||||
public static Process exec(String[] commands, boolean isRoot) {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
|
||||
DataOutputStream os = new DataOutputStream(process.getOutputStream());
|
||||
for (String command : commands) {
|
||||
if (command != null) {
|
||||
os.write(command.getBytes());
|
||||
os.writeBytes(COMMAND_LINE_END);
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
os.writeBytes(COMMAND_EXIT);
|
||||
os.flush();
|
||||
os.close();
|
||||
return process;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Process exec(String command, boolean isRoot) {
|
||||
return exec(command.split("\n"), isRoot);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.view.View;
|
||||
|
||||
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.theme.app.ThemeColorAppCompatActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -22,7 +23,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
* Created by Stardust on 2017/1/23.
|
||||
*/
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
public class BaseActivity extends ThemeColorAppCompatActivity {
|
||||
|
||||
protected static final int PERMISSION_REQUEST_CODE = 11186;
|
||||
|
||||
|
||||
@@ -26,14 +26,16 @@ import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.droid.Droid;
|
||||
import com.stardust.scriptdroid.ui.edit.completion.InputMethodEnhanceBar;
|
||||
import com.stardust.scriptdroid.ui.edit.editor920.Editor920Activity;
|
||||
import com.stardust.scriptdroid.ui.edit.sidemenu.AssistClipListRecyclerView;
|
||||
import com.stardust.scriptdroid.ui.edit.sidemenu.EditSideMenuFragment;
|
||||
import com.stardust.scriptdroid.ui.edit.sidemenu.FunctionListRecyclerView;
|
||||
import com.stardust.scriptdroid.ui.edit.editor920.Editor920Activity;
|
||||
import com.stardust.widget.ToolbarMenuItem;
|
||||
import com.stardust.theme.ThemeColorManager;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
import com.stardust.util.SparseArrayEntries;
|
||||
import com.stardust.view.ViewBinder;
|
||||
import com.stardust.view.ViewBinding;
|
||||
import com.stardust.widget.ToolbarMenuItem;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -46,14 +48,20 @@ public class EditActivity extends Editor920Activity {
|
||||
private static final String KEY_EDIT_ACTIVITY_FIRST_USE = "KEY_EDIT_ACTIVITY_FIRST_USE";
|
||||
|
||||
private static final String ACTION_ON_RUN_FINISHED = "ACTION_ON_RUN_FINISHED";
|
||||
private static final String EXTRA_EXCEPTION = "EXTRA_EXCEPTION";
|
||||
private static final String EXTRA_EXCEPTION_MESSAGE = "EXTRA_EXCEPTION_MESSAGE";
|
||||
|
||||
private static final Droid.OnRunFinishedListener ON_RUN_FINISHED_LISTENER = new Droid.OnRunFinishedListener() {
|
||||
|
||||
@Override
|
||||
public void onRunFinished(Object result, Exception e) {
|
||||
public void onRunFinished(Object result) {
|
||||
App.getApp().sendBroadcast(new Intent(ACTION_ON_RUN_FINISHED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(@NonNull Exception e) {
|
||||
App.getApp().sendBroadcast(new Intent(ACTION_ON_RUN_FINISHED)
|
||||
.putExtra(EXTRA_EXCEPTION, e));
|
||||
.putExtra(EXTRA_EXCEPTION_MESSAGE, e.getMessage()));
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,9 +87,9 @@ public class EditActivity extends Editor920Activity {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(ACTION_ON_RUN_FINISHED)) {
|
||||
setMenuStatus(R.id.run, MenuDef.STATUS_NORMAL);
|
||||
Exception e = (Exception) intent.getSerializableExtra(EXTRA_EXCEPTION);
|
||||
if (e != null)
|
||||
Snackbar.make(mView, getString(R.string.text_error) + ": " + e.getMessage(), Snackbar.LENGTH_INDEFINITE).show();
|
||||
String msg = intent.getStringExtra(EXTRA_EXCEPTION_MESSAGE);
|
||||
if (msg != null)
|
||||
Snackbar.make(mView, getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -123,10 +131,12 @@ public class EditActivity extends Editor920Activity {
|
||||
mName = mFile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setUpUI() {
|
||||
setTheme(R.style.EditorTheme);
|
||||
ThemeColorManager.addActivityStatusBar(this);
|
||||
mView = View.inflate(this, R.layout.activity_edit, null);
|
||||
mDrawerLayout = (DrawerLayout) mView.findViewById(R.id.drawer_layout);
|
||||
setContentView(mView);
|
||||
@@ -271,7 +281,7 @@ public class EditActivity extends Editor920Activity {
|
||||
}
|
||||
|
||||
private void showExitConfirmDialog() {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(R.string.text_alert)
|
||||
.content(R.string.edit_exit_without_save_warn)
|
||||
.positiveText(R.string.text_cancel)
|
||||
|
||||
@@ -101,7 +101,7 @@ public class CodeCompletion implements TextWatcher {
|
||||
|
||||
private static final String[] KEYWORDS = {"arguments", "break", "case", "catch", "class", "continue", "default", "do", "else", "eval", "export", "false", "for", "function", "if", "import", "in", "int", "new", "null", "package", "return", "switch", "this", "throw", "throws", "true", "try", "typeof", "var", "volatile", "while", "with", "Array", "Date", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "name", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"};
|
||||
private static final int KEY_WORD_LENGTH_MAX = 15;
|
||||
private static final String[] FUNCTIONS = {"toast", "launchPackage", "launch", "launchApp", "click", "longClick", "scrollUp", "scrollDown", "select", "focus", "paste", "input", "sleep", "isStopped", "notStopped", "log", "err", "openConsole", "clearConsole", "shell", "getTexts", "getPackageName", "getActivityName", "setClip"};
|
||||
private static final String[] FUNCTIONS = {"toast", "launchPackage", "launch", "launchApp", "click", "longClick", "scrollUp", "scrollDown", "select", "focus", "paste", "input", "sleep", "isStopped", "notStopped", "log", "err", "openConsole", "clearConsole", "shell", "getTexts", "getPackageName", "getActivityName", "setClip", "addAccessibilityDelegate"};
|
||||
|
||||
private boolean searchCodeCompletion(String str) {
|
||||
Collection<CodeCompletionItem> c = searchWordCompletion(str);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.stardust.scriptdroid.ui.edit.completion;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@@ -9,6 +11,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stardust.scriptdroid.R;
|
||||
|
||||
@@ -49,7 +52,7 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
|
||||
EditTextBridge mEditTextBridge;
|
||||
private CodeCompletion mCodeCompletion = new CodeCompletion(this);
|
||||
private List<CodeCompletion.CodeCompletionItem> mCodeCompletionList = new ArrayList<>();
|
||||
private OnClickListener mOnCodeCompletionItemClickListener = new OnClickListener() {
|
||||
private final OnClickListener mOnCodeCompletionItemClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = getChildViewHolder(v).getAdapterPosition();
|
||||
@@ -57,6 +60,16 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
|
||||
}
|
||||
};
|
||||
|
||||
private final OnLongClickListener mOnCodeCompletionItemLongClickListener = new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
int position = getChildViewHolder(v).getAdapterPosition();
|
||||
((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("", mCodeCompletionList.get(position).getDisplayText()));
|
||||
Toast.makeText(getContext(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private void init() {
|
||||
setAdapter(new CodeCompletionAdapter());
|
||||
setLayoutManager(new LinearLayoutManager(getContext(), HORIZONTAL, false));
|
||||
@@ -74,6 +87,7 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
|
||||
mCodeCompletionList.addAll(list);
|
||||
}
|
||||
getAdapter().notifyDataSetChanged();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +114,7 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
itemView.setOnClickListener(mOnCodeCompletionItemClickListener);
|
||||
itemView.setOnLongClickListener(mOnCodeCompletionItemLongClickListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.ui.help.DocumentActivity;
|
||||
import com.stardust.scriptdroid.ui.help.DocumentationActivity;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
|
||||
import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
|
||||
import com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification;
|
||||
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
|
||||
import com.stardust.view.ViewBinder;
|
||||
import com.stardust.view.ViewBinding;
|
||||
|
||||
@@ -85,7 +86,7 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
|
||||
|
||||
@ViewBinding.Click(R.id.syntax_and_api)
|
||||
private void startSyntaxHelpActivity() {
|
||||
startActivity(new Intent(getContext(), DocumentActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
HelpCatalogueActivity.showCatalogue(getActivity());
|
||||
}
|
||||
|
||||
@ViewBinding.Click(R.id.console)
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -47,7 +48,7 @@ public class ErrorReportActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void showErrorMessageByDialog(String message, final String errorDetail) {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(R.string.text_crash)
|
||||
.content(R.string.crash_feedback)
|
||||
.positiveText(R.string.text_exit)
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.widget.Toast;
|
||||
import com.heinrichreimersoftware.androidissuereporter.IssueReporterActivity;
|
||||
import com.heinrichreimersoftware.androidissuereporter.model.github.GithubTarget;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.theme.ThemeColorManager;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.stardust.scriptdroid.ui.help;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
import com.stardust.widget.MarkdownView;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/2/1.
|
||||
*/
|
||||
|
||||
public class DocumentActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setUpUI();
|
||||
}
|
||||
|
||||
private void setUpUI() {
|
||||
setContentView(R.layout.activity_document);
|
||||
setUpToolbar();
|
||||
loadDocument();
|
||||
}
|
||||
|
||||
private void loadDocument() {
|
||||
MarkdownView markdownView = $(R.id.markdown);
|
||||
try {
|
||||
markdownView.loadMarkdown(FileUtils.readString(getResources().openRawResource(R.raw.document)));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
markdownView.setText(R.string.text_load_failed);
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
Toolbar toolbar = $(R.id.toolbar);
|
||||
toolbar.setTitle(R.string.text_syntax_and_api);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.stardust.scriptdroid.ui.help;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
import com.stardust.widget.CommonMarkdownView;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/2/1.
|
||||
*/
|
||||
|
||||
public class DocumentationActivity extends BaseActivity {
|
||||
|
||||
public static void openDocumentation(Context context, String title, String assetPath) {
|
||||
context.startActivity(new Intent(context, DocumentationActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("title", title)
|
||||
.putExtra("path", assetPath));
|
||||
}
|
||||
|
||||
public static void openDocumentation(Context context, String title, int rawResId) {
|
||||
context.startActivity(new Intent(context, DocumentationActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("title", title)
|
||||
.putExtra("resId", rawResId));
|
||||
}
|
||||
|
||||
private String mDocumentation;
|
||||
private String mTitle;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
handleIntent();
|
||||
setUpUI();
|
||||
}
|
||||
|
||||
private void handleIntent() {
|
||||
mTitle = getIntent().getStringExtra("title");
|
||||
String path = getIntent().getStringExtra("path");
|
||||
if (path == null) {
|
||||
int rawId = getIntent().getIntExtra("resId", 0);
|
||||
mDocumentation = FileUtils.readString(getResources().openRawResource(rawId));
|
||||
} else {
|
||||
try {
|
||||
mDocumentation = FileUtils.readString(getAssets().open("help/documentation/" + path + ".md"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpUI() {
|
||||
setContentView(R.layout.activity_document);
|
||||
setToolbarAsBack(mTitle);
|
||||
loadDocument();
|
||||
}
|
||||
|
||||
private void loadDocument() {
|
||||
CommonMarkdownView markdownView = $(R.id.markdown);
|
||||
try {
|
||||
markdownView.loadMarkdown(mDocumentation);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
markdownView.setText(R.string.text_load_failed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.stardust.scriptdroid.ui.help;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/6.
|
||||
*/
|
||||
|
||||
public class HelpCatalogueActivity extends BaseActivity {
|
||||
|
||||
public static void showCatalogue(Context context, String catalogue) {
|
||||
context.startActivity(new Intent(context, HelpCatalogueActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra("catalogue", catalogue));
|
||||
}
|
||||
|
||||
public static void showCatalogue(Context context) {
|
||||
showCatalogue(context, "main");
|
||||
}
|
||||
|
||||
private static final Map<String, List<Item>> CATALOGUES;
|
||||
|
||||
private static class Item extends JSONObject {
|
||||
|
||||
String title;
|
||||
String summary;
|
||||
private String catalogue;
|
||||
private String documentation;
|
||||
|
||||
Item(String title, String summary) {
|
||||
this.title = title;
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
Item(String title) {
|
||||
this(title, null);
|
||||
}
|
||||
|
||||
Item catalogue(String catalogue) {
|
||||
this.catalogue = catalogue;
|
||||
return this;
|
||||
}
|
||||
|
||||
Item documentation(String documentation) {
|
||||
this.documentation = documentation;
|
||||
return this;
|
||||
}
|
||||
|
||||
void redirect(Context context) {
|
||||
if (documentation != null) {
|
||||
DocumentationActivity.openDocumentation(context, title, documentation);
|
||||
} else if (catalogue != null) {
|
||||
showCatalogue(context, catalogue);
|
||||
} else {
|
||||
DocumentationActivity.openDocumentation(context, title, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Item> mItems;
|
||||
private RecyclerView mRecyclerView;
|
||||
private View.OnClickListener mOnItemClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
|
||||
if (holder != null) {
|
||||
mItems.get(holder.getAdapterPosition()).redirect(HelpCatalogueActivity.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
handleIntent();
|
||||
setUpUI();
|
||||
}
|
||||
|
||||
private void handleIntent() {
|
||||
String catalogue = getIntent().getStringExtra("catalogue");
|
||||
if (catalogue == null)
|
||||
catalogue = "main";
|
||||
mItems = CATALOGUES.get(catalogue);
|
||||
}
|
||||
|
||||
private void setUpUI() {
|
||||
setContentView(R.layout.activity_help);
|
||||
setToolbarAsBack(getString(R.string.text_help));
|
||||
setUpRecyclerView();
|
||||
}
|
||||
|
||||
private void setUpRecyclerView() {
|
||||
mRecyclerView = $(R.id.catalogue);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
mRecyclerView.setAdapter(new Adapter());
|
||||
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, RecyclerView.VERTICAL));
|
||||
}
|
||||
|
||||
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
int view = R.layout.activity_help_catalogue_item;
|
||||
return new ViewHolder(LayoutInflater.from(HelpCatalogueActivity.this).inflate(view, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Item item = mItems.get(position);
|
||||
holder.title.setText(item.title);
|
||||
if (item.summary != null) {
|
||||
holder.summary.setVisibility(View.VISIBLE);
|
||||
holder.summary.setText(item.summary);
|
||||
} else {
|
||||
holder.summary.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView title, summary;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
itemView.setOnClickListener(mOnItemClickListener);
|
||||
title = (TextView) itemView.findViewById(R.id.title);
|
||||
summary = (TextView) itemView.findViewById(R.id.summary);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Map<String, List<Item>> fromJson;
|
||||
try {
|
||||
String json = FileUtils.readString(App.getApp().getAssets().open("help/catalogue.json"));
|
||||
Gson gson = new Gson();
|
||||
Type type = new TypeToken<Map<String, List<Item>>>() {
|
||||
}.getType();
|
||||
fromJson = gson.fromJson(json, type);
|
||||
} catch (IOException e) {
|
||||
fromJson = new HashMap<>();
|
||||
e.printStackTrace();
|
||||
}
|
||||
CATALOGUES = fromJson;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
@@ -17,6 +20,7 @@ import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -24,7 +28,9 @@ import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
|
||||
import com.stardust.app.NotRemindAgainDialog;
|
||||
import com.stardust.app.OnActivityResultDelegate;
|
||||
import com.stardust.scriptdroid.BuildConfig;
|
||||
import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
|
||||
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
|
||||
@@ -32,17 +38,23 @@ import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
|
||||
import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchNotification;
|
||||
import com.stardust.scriptdroid.file.FileUtils;
|
||||
import com.stardust.scriptdroid.file.SampleFileManager;
|
||||
import com.stardust.scriptdroid.record.root.InputEventRecorder;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
|
||||
import com.stardust.scriptdroid.tool.BackPressedHandler;
|
||||
import com.stardust.scriptdroid.tool.ImageSelector;
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
|
||||
import com.stardust.scriptdroid.ui.settings.SettingsActivity;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
import com.stardust.view.ViewBinder;
|
||||
import com.stardust.view.ViewBinding;
|
||||
import com.stardust.view.accessibility.AccessibilityServiceUtils;
|
||||
import com.stardust.widget.SlidingUpPanel;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
||||
public class MainActivity extends BaseActivity implements FileChooserDialog.FileCallback {
|
||||
@@ -58,6 +70,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
private ScriptFileList mScriptFileList;
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private Receiver mReceiver;
|
||||
private OnActivityResultDelegate.Manager mManager = new OnActivityResultDelegate.Manager();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -88,14 +101,14 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
AccessibilityServiceUtils.goToAccessibilitySetting(MainActivity.this);
|
||||
AccessibilityServiceTool.enableAccessibilityService();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void addScriptFile(final String path) {
|
||||
new MaterialDialog.Builder(this).title(R.string.text_name)
|
||||
new ThemeColorMaterialDialogBuilder(this).title(R.string.text_name)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT)
|
||||
.input(getString(R.string.text_please_input_name), new File(path).getName(), new MaterialDialog.InputCallback() {
|
||||
@Override
|
||||
@@ -117,16 +130,27 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
mAddFilePanel = $(R.id.bottom_menu);
|
||||
|
||||
setUpToolbar();
|
||||
setVersionName();
|
||||
setUpDrawerHeader();
|
||||
setUpScriptList();
|
||||
ViewBinder.bind(this);
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void setVersionName() {
|
||||
private void setUpDrawerHeader() {
|
||||
TextView version = $(R.id.version);
|
||||
version.setText("Version " + BuildConfig.VERSION_NAME);
|
||||
String path = Pref.def().getString(Pref.KEY_DRAWER_HEADER_IMAGE_PATH, null);
|
||||
if (path != null) {
|
||||
setDrawerHeaderImage(path);
|
||||
}
|
||||
$(R.id.drawer).setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
private void setDrawerHeaderImage(String path) {
|
||||
Drawable d = BitmapDrawable.createFromPath(path);
|
||||
if (d != null)
|
||||
((ImageView) $(R.id.drawer_header_img)).setImageDrawable(d);
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
@@ -158,7 +182,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
|
||||
|
||||
private void createScriptFileForScript(final String script) {
|
||||
new MaterialDialog.Builder(this).title(R.string.text_name)
|
||||
new ThemeColorMaterialDialogBuilder(this).title(R.string.text_name)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT)
|
||||
.input(getString(R.string.text_please_input_name), "", new MaterialDialog.InputCallback() {
|
||||
@Override
|
||||
@@ -212,6 +236,17 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@ViewBinding.Click(R.id.drawer_header_img)
|
||||
public void selectHeaderImage() {
|
||||
new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() {
|
||||
@Override
|
||||
public void onImageSelected(String path) {
|
||||
setDrawerHeaderImage(path);
|
||||
Pref.def().edit().putString(Pref.KEY_DRAWER_HEADER_IMAGE_PATH, path).apply();
|
||||
}
|
||||
}).select();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
@NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
@@ -244,7 +279,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
}
|
||||
|
||||
private void handleRecordedScript(final String script) {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(R.string.text_recorded)
|
||||
.items(getString(R.string.text_new_file), getString(R.string.text_copy_to_clip))
|
||||
.itemsCallback(new MaterialDialog.ListCallback() {
|
||||
@@ -282,6 +317,11 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
mManager.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public static void onActionRecordStopped(Context context, String script) {
|
||||
Intent intent = new Intent(context, MainActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
@@ -290,6 +330,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
private class Receiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.ThemeColorRecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -19,6 +20,7 @@ import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
|
||||
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
|
||||
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperationPopupMenu;
|
||||
import com.stardust.scriptdroid.tool.ClassTool;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import static com.stardust.scriptdroid.tool.ViewTool.$;
|
||||
|
||||
@@ -28,7 +30,7 @@ import static com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation.*;
|
||||
* Created by Stardust on 2017/1/23.
|
||||
*/
|
||||
|
||||
public class ScriptListRecyclerView extends RecyclerView {
|
||||
public class ScriptListRecyclerView extends ThemeColorRecyclerView {
|
||||
|
||||
private ScriptFileList mScriptFileList;
|
||||
|
||||
@@ -107,7 +109,7 @@ public class ScriptListRecyclerView extends RecyclerView {
|
||||
}
|
||||
|
||||
private void showOperationDialog(final int position) {
|
||||
new MaterialDialog.Builder(getContext()).items(ScriptFileOperation.getOperationNames())
|
||||
new ThemeColorMaterialDialogBuilder(getContext()).items(ScriptFileOperation.getOperationNames())
|
||||
.itemsCallback(new MaterialDialog.ListCallback() {
|
||||
@Override
|
||||
public void onSelection(MaterialDialog dialog, View itemView, int operation, CharSequence text) {
|
||||
|
||||
@@ -17,8 +17,9 @@ import com.stardust.scriptdroid.bounds_assist.BoundsAssistant;
|
||||
import com.stardust.scriptdroid.droid.Droid;
|
||||
import com.stardust.scriptdroid.external.notification.bounds_assist.BoundsAssistSwitchNotification;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
|
||||
import com.stardust.scriptdroid.ui.console.ConsoleActivity;
|
||||
import com.stardust.scriptdroid.ui.help.DocumentActivity;
|
||||
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
|
||||
import com.stardust.view.ViewBinder;
|
||||
import com.stardust.view.ViewBinding;
|
||||
import com.stardust.view.accessibility.AccessibilityServiceUtils;
|
||||
@@ -83,7 +84,7 @@ public class SlideMenuFragment extends Fragment {
|
||||
|
||||
@ViewBinding.Click(R.id.syntax_and_api)
|
||||
private void startSyntaxHelpActivity() {
|
||||
startActivity(new Intent(getContext(), DocumentActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
HelpCatalogueActivity.showCatalogue(getActivity());
|
||||
}
|
||||
|
||||
@ViewBinding.Click(R.id.auto_operate_service)
|
||||
@@ -94,7 +95,7 @@ public class SlideMenuFragment extends Fragment {
|
||||
@ViewBinding.Check(R.id.sw_auto_operate_service)
|
||||
private void setAutoOperateServiceEnable(boolean enable) {
|
||||
if (enable && !AccessibilityWatchDogService.isEnable()) {
|
||||
AccessibilityServiceUtils.goToAccessibilitySetting(getContext());
|
||||
AccessibilityServiceTool.enableAccessibilityService();
|
||||
} else if (!enable && AccessibilityWatchDogService.isEnable()) {
|
||||
AccessibilityWatchDogService.disable();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.stardust.scriptdroid.droid.script.file.ScriptFile;
|
||||
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
|
||||
import com.stardust.scriptdroid.external.shortcut.Shortcut;
|
||||
import com.stardust.scriptdroid.ui.main.ScriptListRecyclerView;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -108,7 +109,7 @@ public abstract class ScriptFileOperation {
|
||||
@Override
|
||||
public void operate(final ScriptListRecyclerView recyclerView, final ScriptFileList scriptFileList, final int position) {
|
||||
String oldName = scriptFileList.get(position).name;
|
||||
new MaterialDialog.Builder(recyclerView.getContext())
|
||||
new ThemeColorMaterialDialogBuilder(recyclerView.getContext())
|
||||
.title(R.string.text_rename)
|
||||
.checkBoxPrompt(App.getApp().getString(R.string.text_rename_file_meanwhile), false, null)
|
||||
.input(App.getApp().getString(R.string.text_please_input_new_name), oldName, new MaterialDialog.InputCallback() {
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.BuildConfig;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.tool.IntentTool;
|
||||
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
|
||||
import com.stardust.view.ViewBinder;
|
||||
import com.stardust.view.ViewBinding;
|
||||
|
||||
@@ -39,7 +40,7 @@ public class AboutActivity extends BaseActivity {
|
||||
private void setUpUI() {
|
||||
setContentView(R.layout.activity_about);
|
||||
setVersionName();
|
||||
setUpToolbar();
|
||||
setToolbarAsBack(getString(R.string.text_about));
|
||||
ViewBinder.bind(this);
|
||||
}
|
||||
|
||||
@@ -49,18 +50,6 @@ public class AboutActivity extends BaseActivity {
|
||||
version.setText("Version " + BuildConfig.VERSION_NAME);
|
||||
}
|
||||
|
||||
private void setUpToolbar() {
|
||||
Toolbar toolbar = $(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ViewBinding.Click(R.id.github)
|
||||
private void openGitHub() {
|
||||
IntentTool.goToLink(this, getString(R.string.my_github));
|
||||
@@ -83,7 +72,7 @@ public class AboutActivity extends BaseActivity {
|
||||
|
||||
@ViewBinding.Click(R.id.donate)
|
||||
private void showDonateMeDialog() {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title(R.string.donate)
|
||||
.items("支付宝")
|
||||
.itemsCallback(new MaterialDialog.ListCallback() {
|
||||
@@ -111,7 +100,7 @@ public class AboutActivity extends BaseActivity {
|
||||
mLolClickCount++;
|
||||
Toast.makeText(this, R.string.text_lll, Toast.LENGTH_LONG).show();
|
||||
if (mLolClickCount >= 5) {
|
||||
new MaterialDialog.Builder(this)
|
||||
new ThemeColorMaterialDialogBuilder(this)
|
||||
.title("Crash Test")
|
||||
.positiveText("Crash")
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.stardust.scriptdroid.ui.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
@@ -15,11 +16,17 @@ import com.stardust.scriptdroid.tool.IntentTool;
|
||||
import com.stardust.scriptdroid.ui.BaseActivity;
|
||||
import com.stardust.scriptdroid.ui.error.IssueReportActivity;
|
||||
import com.stardust.scriptdroid.ui.main.MainActivity;
|
||||
import com.stardust.theme.app.ColorSelectActivity;
|
||||
import com.stardust.theme.util.ListBuilder;
|
||||
import com.stardust.util.MapEntries;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import de.psdev.licensesdialog.LicenseResolver;
|
||||
import de.psdev.licensesdialog.LicensesDialog;
|
||||
import de.psdev.licensesdialog.licenses.License;
|
||||
import de.psdev.licensesdialog.licenses.MozillaPublicLicense11;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/2/2.
|
||||
@@ -27,6 +34,32 @@ import de.psdev.licensesdialog.LicensesDialog;
|
||||
|
||||
public class SettingsActivity extends BaseActivity {
|
||||
|
||||
private static final List<ColorSelectActivity.ColorItem> COLOR_ITEMS;
|
||||
|
||||
static {
|
||||
COLOR_ITEMS = new ListBuilder<ColorSelectActivity.ColorItem>()
|
||||
.add(new ColorSelectActivity.ColorItem("基佬红", 0xFFF44336))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬粉", 0xFFE91E63))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬紫", 0xFF9C27B0))
|
||||
.add(new ColorSelectActivity.ColorItem("基深紫", 0xFF673AB7))
|
||||
.add(new ColorSelectActivity.ColorItem("基靛蓝", 0xFF3F51B5))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬蓝", 0xFF2196F3))
|
||||
.add(new ColorSelectActivity.ColorItem("基亮蓝", 0xFF03A9F4))
|
||||
.add(new ColorSelectActivity.ColorItem("基蓝绿", 0xFF00BCD4))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬青", 0xFF009688))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬绿", 0xFF4CAF50))
|
||||
.add(new ColorSelectActivity.ColorItem("基亮绿", 0xFF8BC34A))
|
||||
.add(new ColorSelectActivity.ColorItem("基黄绿", 0xFFCDDC39))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬黄", 0xFFFFEB3B))
|
||||
.add(new ColorSelectActivity.ColorItem("基琥珀", 0xFFFFC107))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬橙", 0xFFFF9800))
|
||||
.add(new ColorSelectActivity.ColorItem("基深橙", 0xFFFF5722))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬棕", 0xFF795548))
|
||||
.add(new ColorSelectActivity.ColorItem("基佬灰", 0xFF9E9E9E))
|
||||
.add(new ColorSelectActivity.ColorItem("基南灰", 0xFF607D8B))
|
||||
.list();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -68,6 +101,12 @@ public class SettingsActivity extends BaseActivity {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
ACTION_MAP = new MapEntries<String, Runnable>()
|
||||
.entry(getString(R.string.text_theme_color), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ColorSelectActivity.startColorSelect(getActivity(), getString(R.string.mt_color_picker_title), COLOR_ITEMS);
|
||||
}
|
||||
})
|
||||
.entry(getString(R.string.text_re_import_samples), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -124,6 +163,7 @@ public class SettingsActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void showLicenseDialog() {
|
||||
LicenseResolver.registerLicense(MozillaPublicLicense20.instance);
|
||||
new LicensesDialog.Builder(getActivity())
|
||||
.setNotices(R.raw.licenses)
|
||||
.setIncludeOwnLicense(true)
|
||||
@@ -131,5 +171,35 @@ public class SettingsActivity extends BaseActivity {
|
||||
.showAppCompat();
|
||||
}
|
||||
|
||||
public static class MozillaPublicLicense20 extends License {
|
||||
|
||||
public static MozillaPublicLicense20 instance = new MozillaPublicLicense20();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Mozilla Public License 2.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readSummaryTextFromResources(Context context) {
|
||||
return getContent(context, R.raw.mpl_20_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readFullTextFromResources(Context context) {
|
||||
return getContent(context, R.raw.mpl_20_full);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return "https://www.mozilla.org/en-US/MPL/2.0/";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.stardust.theme.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.theme.ThemeColor;
|
||||
import com.stardust.theme.ThemeColorManager;
|
||||
import com.stardust.theme.ThemeColorMutable;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/5.
|
||||
*/
|
||||
|
||||
public class ThemeColorMaterialDialogBuilder extends MaterialDialog.Builder implements ThemeColorMutable {
|
||||
public ThemeColorMaterialDialogBuilder(@NonNull Context context) {
|
||||
super(context);
|
||||
ThemeColorManager.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThemeColor(ThemeColor themeColor) {
|
||||
int color = themeColor.colorPrimary;
|
||||
positiveColor(color);
|
||||
negativeColor(color);
|
||||
neutralColor(color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package com.stardust.view.accessibility;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/6.
|
||||
*/
|
||||
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public class AccessibilityNodeInfoDumper {
|
||||
|
||||
private static final String LOGTAG = AccessibilityNodeInfoDumper.class.getSimpleName();
|
||||
private static final String[] NAF_EXCLUDED_CLASSES = new String[]{
|
||||
android.widget.GridView.class.getName(), android.widget.GridLayout.class.getName(),
|
||||
android.widget.ListView.class.getName(), android.widget.TableLayout.class.getName()
|
||||
};
|
||||
|
||||
/**
|
||||
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
|
||||
* and generates an xml dump into the /data/local/window_dump.xml
|
||||
*
|
||||
* @param root The root accessibility node.
|
||||
* @param rotation The rotaion of current display
|
||||
* @param width The pixel width of current display
|
||||
* @param height The pixel height of current display
|
||||
*/
|
||||
public static void dumpWindowToFile(AccessibilityNodeInfo root, int rotation,
|
||||
int width, int height) {
|
||||
File baseDir = new File(Environment.getDataDirectory(), "local");
|
||||
if (!baseDir.exists()) {
|
||||
baseDir.mkdir();
|
||||
baseDir.setExecutable(true, false);
|
||||
baseDir.setWritable(true, false);
|
||||
baseDir.setReadable(true, false);
|
||||
}
|
||||
dumpWindowToFile(root,
|
||||
new File(new File(Environment.getDataDirectory(), "local"), "window_dump.xml"),
|
||||
rotation, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
|
||||
* and generates an xml dump to the location specified by <code>dumpFile</code>
|
||||
*
|
||||
* @param root The root accessibility node.
|
||||
* @param dumpFile The file to dump to.
|
||||
* @param rotation The rotaion of current display
|
||||
* @param width The pixel width of current display
|
||||
* @param height The pixel height of current display
|
||||
*/
|
||||
public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile, int rotation,
|
||||
int width, int height) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
final long startTime = SystemClock.uptimeMillis();
|
||||
try {
|
||||
FileWriter writer = new FileWriter(dumpFile);
|
||||
XmlSerializer serializer = Xml.newSerializer();
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
serializer.setOutput(stringWriter);
|
||||
serializer.startDocument("UTF-8", true);
|
||||
serializer.startTag("", "hierarchy");
|
||||
serializer.attribute("", "rotation", Integer.toString(rotation));
|
||||
dumpNodeRec(root, serializer, 0, width, height);
|
||||
serializer.endTag("", "hierarchy");
|
||||
serializer.endDocument();
|
||||
writer.write(stringWriter.toString());
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "failed to dump window to file", e);
|
||||
}
|
||||
final long endTime = SystemClock.uptimeMillis();
|
||||
Log.w(LOGTAG, "Fetch time: " + (endTime - startTime) + "ms");
|
||||
}
|
||||
|
||||
private static void dumpNodeRec(AccessibilityNodeInfo node, XmlSerializer serializer, int index,
|
||||
int width, int height) throws IOException {
|
||||
serializer.startTag("", "node");
|
||||
if (!nafExcludedClass(node) && !nafCheck(node))
|
||||
serializer.attribute("", "NAF", Boolean.toString(true));
|
||||
serializer.attribute("", "index", Integer.toString(index));
|
||||
serializer.attribute("", "text", safeCharSeqToString(node.getText()));
|
||||
serializer.attribute("", "resource-id", safeCharSeqToString(node.getViewIdResourceName()));
|
||||
serializer.attribute("", "class", safeCharSeqToString(node.getClassName()));
|
||||
serializer.attribute("", "package", safeCharSeqToString(node.getPackageName()));
|
||||
serializer.attribute("", "content-desc", safeCharSeqToString(node.getContentDescription()));
|
||||
serializer.attribute("", "checkable", Boolean.toString(node.isCheckable()));
|
||||
serializer.attribute("", "checked", Boolean.toString(node.isChecked()));
|
||||
serializer.attribute("", "clickable", Boolean.toString(node.isClickable()));
|
||||
serializer.attribute("", "enabled", Boolean.toString(node.isEnabled()));
|
||||
serializer.attribute("", "focusable", Boolean.toString(node.isFocusable()));
|
||||
serializer.attribute("", "focused", Boolean.toString(node.isFocused()));
|
||||
serializer.attribute("", "scrollable", Boolean.toString(node.isScrollable()));
|
||||
serializer.attribute("", "long-clickable", Boolean.toString(node.isLongClickable()));
|
||||
serializer.attribute("", "password", Boolean.toString(node.isPassword()));
|
||||
serializer.attribute("", "selected", Boolean.toString(node.isSelected()));
|
||||
serializer.attribute("", "bounds", AccessibilityNodeInfoHelper.getVisibleBoundsInScreen(
|
||||
node, width, height).toShortString());
|
||||
int count = node.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (child != null) {
|
||||
if (child.isVisibleToUser()) {
|
||||
dumpNodeRec(child, serializer, i, width, height);
|
||||
child.recycle();
|
||||
} else {
|
||||
Log.i(LOGTAG, String.format("Skipping invisible child: %s", child.toString()));
|
||||
}
|
||||
} else {
|
||||
Log.i(LOGTAG, String.format("Null child %d/%d, parent: %s",
|
||||
i, count, node.toString()));
|
||||
}
|
||||
}
|
||||
serializer.endTag("", "node");
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of classes to exclude my not be complete. We're attempting to
|
||||
* only reduce noise from standard layout classes that may be falsely
|
||||
* configured to accept clicks and are also enabled.
|
||||
*
|
||||
* @param node
|
||||
* @return true if node is excluded.
|
||||
*/
|
||||
private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
|
||||
String className = safeCharSeqToString(node.getClassName());
|
||||
for (String excludedClassName : NAF_EXCLUDED_CLASSES) {
|
||||
if (className.endsWith(excludedClassName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We're looking for UI controls that are enabled, clickable but have no
|
||||
* text nor content-description. Such controls configuration indicate an
|
||||
* interactive control is present in the UI and is most likely not
|
||||
* accessibility friendly. We refer to such controls here as NAF controls
|
||||
* (Not Accessibility Friendly)
|
||||
*
|
||||
* @param node
|
||||
* @return false if a node fails the check, true if all is OK
|
||||
*/
|
||||
private static boolean nafCheck(AccessibilityNodeInfo node) {
|
||||
boolean isNaf = node.isClickable() && node.isEnabled()
|
||||
&& safeCharSeqToString(node.getContentDescription()).isEmpty()
|
||||
&& safeCharSeqToString(node.getText()).isEmpty();
|
||||
|
||||
if (!isNaf)
|
||||
return true;
|
||||
|
||||
// check children since sometimes the containing element is clickable
|
||||
// and NAF but a child's text or description is available. Will assume
|
||||
// such layout as fine.
|
||||
return childNafCheck(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be used when it's already determined that the node is NAF and
|
||||
* a further check of its children is in order. A node maybe a container
|
||||
* such as LinerLayout and may be set to be clickable but have no text or
|
||||
* content description but it is counting on one of its children to fulfill
|
||||
* the requirement for being accessibility friendly by having one or more of
|
||||
* its children fill the text or content-description. Such a combination is
|
||||
* considered by this dumper as acceptable for accessibility.
|
||||
*
|
||||
* @param node
|
||||
* @return false if node fails the check.
|
||||
*/
|
||||
private static boolean childNafCheck(AccessibilityNodeInfo node) {
|
||||
int childCount = node.getChildCount();
|
||||
for (int x = 0; x < childCount; x++) {
|
||||
AccessibilityNodeInfo childNode = node.getChild(x);
|
||||
|
||||
if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty()
|
||||
|| !safeCharSeqToString(childNode.getText()).isEmpty())
|
||||
return true;
|
||||
|
||||
if (childNafCheck(childNode))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String safeCharSeqToString(CharSequence cs) {
|
||||
if (cs == null)
|
||||
return "";
|
||||
else {
|
||||
return stripInvalidXMLChars(cs);
|
||||
}
|
||||
}
|
||||
|
||||
private static String stripInvalidXMLChars(CharSequence cs) {
|
||||
StringBuffer ret = new StringBuffer();
|
||||
char ch;
|
||||
/* http://www.w3.org/TR/xml11/#charsets
|
||||
[#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
|
||||
[#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
|
||||
[#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
|
||||
[#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
|
||||
[#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
|
||||
[#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
|
||||
[#x10FFFE-#x10FFFF].
|
||||
*/
|
||||
for (int i = 0; i < cs.length(); i++) {
|
||||
ch = cs.charAt(i);
|
||||
|
||||
if ((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) ||
|
||||
(ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) ||
|
||||
(ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) ||
|
||||
(ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) ||
|
||||
(ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) ||
|
||||
(ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) ||
|
||||
(ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) ||
|
||||
(ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) ||
|
||||
(ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) ||
|
||||
(ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) ||
|
||||
(ch >= 0x10FFFE && ch <= 0x10FFFF))
|
||||
ret.append(".");
|
||||
else
|
||||
ret.append(ch);
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.stardust.view.accessibility;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/6.
|
||||
*/
|
||||
|
||||
public class AccessibilityNodeInfoHelper {
|
||||
|
||||
/**
|
||||
* Returns the node's bounds clipped to the size of the display
|
||||
*
|
||||
* @param node
|
||||
* @param width pixel width of the display
|
||||
* @param height pixel height of the display
|
||||
* @return null if node is null, else a Rect containing visible bounds
|
||||
*/
|
||||
public static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
// targeted node's bounds
|
||||
Rect nodeRect = new Rect();
|
||||
node.getBoundsInScreen(nodeRect);
|
||||
|
||||
Rect displayRect = new Rect();
|
||||
displayRect.top = 0;
|
||||
displayRect.left = 0;
|
||||
displayRect.right = width;
|
||||
displayRect.bottom = height;
|
||||
|
||||
boolean intersect = nodeRect.intersect(displayRect);
|
||||
return nodeRect;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.stardust.view.accessibility;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -7,8 +8,11 @@ import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stardust.scriptdroid.App;
|
||||
import com.stardust.scriptdroid.Pref;
|
||||
import com.stardust.scriptdroid.R;
|
||||
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
|
||||
import com.stardust.scriptdroid.tool.Shell;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/26.
|
||||
@@ -23,7 +27,7 @@ public class AccessibilityServiceUtils {
|
||||
context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
public static boolean isAccessibilityServiceEnabled(Context context, Class<?> accessibilityService) {
|
||||
public static boolean isAccessibilityServiceEnabled(Context context, Class<? extends AccessibilityService> accessibilityService) {
|
||||
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
|
||||
|
||||
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
|
||||
@@ -43,4 +47,23 @@ public class AccessibilityServiceUtils {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean enableAccessibilityServiceByRootAndWaitFor(Context context, Class<? extends AccessibilityService> accessibilityService, long timeout) {
|
||||
Shell.execCommand("settings put secure enabled_accessibility_services %accessibility:"
|
||||
+ context.getPackageName() + "/" + accessibilityService.getName(), true);
|
||||
long millis = System.currentTimeMillis();
|
||||
while (true) {
|
||||
if (isAccessibilityServiceEnabled(context, accessibilityService))
|
||||
return true;
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (System.currentTimeMillis() - millis >= timeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.stardust.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.parser.Parser;
|
||||
import org.commonmark.renderer.html.HtmlRenderer;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/5.
|
||||
*/
|
||||
|
||||
public class CommonMarkdownView extends WebView {
|
||||
|
||||
private Parser mParser = Parser.builder().build();
|
||||
private HtmlRenderer mHtmlRender = HtmlRenderer.builder().build();
|
||||
|
||||
public CommonMarkdownView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CommonMarkdownView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CommonMarkdownView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
loadUrl("javascript:document.body.style.margin=\"12%\"; void 0");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public CommonMarkdownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public void loadMarkdown(String markdown) {
|
||||
String html = renderMarkdown(markdown);
|
||||
loadDataWithBaseURL(null, html, "text/html", "utf-8", null);
|
||||
}
|
||||
|
||||
private String renderMarkdown(String markdown) {
|
||||
Node document = mParser.parse(markdown);
|
||||
return mHtmlRender.render(document);
|
||||
}
|
||||
|
||||
public void setText(int resId) {
|
||||
setText(getContext().getString(resId));
|
||||
}
|
||||
|
||||
private void setText(String text) {
|
||||
loadDataWithBaseURL(null, text, "text/plain", "utf-8", null);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.stardust.widget;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.ThemeColorRecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -16,7 +17,7 @@ import com.stardust.scriptdroid.R;
|
||||
* Created by Stardust on 2017/2/4.
|
||||
*/
|
||||
|
||||
public class ExpandableRecyclerView extends RecyclerView {
|
||||
public class ExpandableRecyclerView extends ThemeColorRecyclerView {
|
||||
|
||||
public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
|
||||
mOnChildClickListener = onChildClickListener;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.stardust.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zzhoujay.markdown.MarkDown;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/2/1.
|
||||
*/
|
||||
|
||||
public class MarkdownView extends TextView {
|
||||
public MarkdownView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public MarkdownView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public MarkdownView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public MarkdownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setMovementMethod(ScrollingMovementMethod.getInstance());
|
||||
setVerticalScrollBarEnabled(true);
|
||||
setTextIsSelectable(true);
|
||||
setClickable(true);
|
||||
}
|
||||
|
||||
public void loadMarkdown(final String text, final Html.ImageGetter imageGetter) {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Spanned spanned = MarkDown.fromMarkdown(text, imageGetter, MarkdownView.this);
|
||||
setText(spanned);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadMarkdown(final String text) {
|
||||
loadMarkdown(text, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
<com.stardust.theme.widget.ThemeColorFloatingActionButton
|
||||
android:id="@+id/share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
|
||||
@@ -6,30 +6,23 @@
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
<com.stardust.widget.MarkdownView
|
||||
<com.stardust.widget.CommonMarkdownView
|
||||
android:id="@+id/markdown"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:textColor="@android:color/secondary_text_light"/>
|
||||
</LinearLayout>
|
||||
@@ -13,7 +13,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
</com.stardust.theme.widget.ThemeColorToolbar>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
|
||||
28
app/src/main/res/layout/activity_help.xml
Normal file
28
app/src/main/res/layout/activity_help.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/catalogue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</LinearLayout>
|
||||
25
app/src/main/res/layout/activity_help_catalogue_item.xml
Normal file
25
app/src/main/res/layout/activity_help_catalogue_item.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/primary_text_light"
|
||||
android:textSize="16sp"
|
||||
tools:text="标题"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/secondary_text_light_nodisable"
|
||||
android:textSize="14sp"
|
||||
tools:text="简介"/>
|
||||
</LinearLayout>
|
||||
@@ -18,7 +18,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
@@ -35,7 +35,7 @@
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_add_white_48dp"/>
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
</com.stardust.theme.widget.ThemeColorToolbar>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
@@ -65,13 +65,13 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_add_file_green"/>
|
||||
|
||||
<TextView
|
||||
<com.stardust.theme.widget.ThemeColorTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
@@ -91,13 +91,13 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_folder_open_outline_green"/>
|
||||
|
||||
<TextView
|
||||
<com.stardust.theme.widget.ThemeColorTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
@@ -117,13 +117,13 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_video_record"/>
|
||||
|
||||
<TextView
|
||||
<com.stardust.theme.widget.ThemeColorTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
@@ -140,6 +140,7 @@
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/drawer"
|
||||
layout="@layout/drawer"
|
||||
android:layout_width="304dp"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
<com.stardust.theme.widget.ThemeColorToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
android:layout_marginBottom="5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/drawer_header_img"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/slide_menu_img_200px"/>
|
||||
|
||||
@@ -72,7 +74,7 @@
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="6dp"
|
||||
@@ -95,7 +97,7 @@
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="6dp"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -20,7 +20,7 @@
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/expand_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
@@ -35,7 +35,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
@@ -57,7 +57,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon_assist_service"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -73,7 +73,7 @@
|
||||
android:text="@string/text_assist_service"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<com.stardust.theme.widget.ThemeColorSwitch
|
||||
android:id="@+id/sw_assist_service"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -90,7 +90,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon_assist_service_notification"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -106,7 +106,7 @@
|
||||
android:text="@string/text_assist_service_notification"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<com.stardust.theme.widget.ThemeColorSwitch
|
||||
android:id="@+id/sw_assist_service_notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
@@ -35,7 +35,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
@@ -56,7 +56,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon_service"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -72,7 +72,7 @@
|
||||
android:text="@string/text_auto_operate_service"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<com.stardust.theme.widget.ThemeColorSwitch
|
||||
android:id="@+id/sw_auto_operate_service"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -90,7 +90,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon_assist_service"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -106,7 +106,7 @@
|
||||
android:text="@string/text_assist_service"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<com.stardust.theme.widget.ThemeColorSwitch
|
||||
android:id="@+id/sw_assist_service"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -123,7 +123,7 @@
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon_assist_service_notification"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
@@ -139,7 +139,7 @@
|
||||
android:text="@string/text_assist_service_notification"
|
||||
android:textColor="@android:color/primary_text_light"/>
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<com.stardust.theme.widget.ThemeColorSwitch
|
||||
android:id="@+id/sw_assist_service_notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -157,7 +157,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="16dp"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<ImageView
|
||||
<com.stardust.theme.widget.ThemeColorImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
###一、语法
|
||||
### 一、语法
|
||||
本软件使用JavaScript语言([ECMAscript E5/E5.1](http://www.ecma-international.org/ecma-262/5.1/)),基于[Duktape](http://www.duktape.org/)引擎拓展一些自动操作(点击、长按、滑动等)函数。因而语法参见JavaScript(例如[w3cschool教程](http://www.w3school.com.cn/js/))。
|
||||
###二、自动操作函数
|
||||
### 二、自动操作函数
|
||||
**自动操作的函数都需要开启"自动操作服务"才能执行,否则执行到相应函数时脚本会停止运行。**
|
||||
* `click(text)` 点击文本text所在的区域,并返回是否点击成功。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。
|
||||
* `click(text, i)` 点击第i个文本text所在的区域,并返回是否点击成功。i从0开始,当不指定时点击所有区域。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。
|
||||
> 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。
|
||||
* `click(left, top, bottom, right)`
|
||||
> 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text)`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。
|
||||
@@ -16,7 +15,14 @@
|
||||
* `scrollDown` 下滑。不加参数时与scrollUp类似。
|
||||
* `input(text)` 把所有输入框的文本都置为text。例如input("测试")。
|
||||
* `input(i, text)` 把第i个输入框的文本设为text。i从0开始。
|
||||
###三、其他函数
|
||||
* `addAccessibilityDelegate(function)` 增加一个AccessibilityEvent处理代理。这可以实现很多强大的功能,但需要熟悉安卓的AccessibilityEvent。例如:
|
||||
```
|
||||
addAccessibilityDelegate(function(service, event){
|
||||
//do something
|
||||
});
|
||||
```
|
||||
### 三、其他函数
|
||||
|
||||
* `sleep(n)` 暂停执行n**毫秒**时间。
|
||||
* `toast(text)` 显示提示文本。
|
||||
* `launch(packageName)` 运行包名为packageName的应用。例如launch("com.tencent.mm")是运行微信,应用包名可以通过一些工具获取;获取通过函数launchApp代替
|
||||
@@ -39,16 +45,15 @@ for(var i = 0; i < texts.size(); i++){
|
||||
openConsole();
|
||||
```
|
||||
|
||||
###四、全局变量
|
||||
### 四、全局变量
|
||||
* `context` ApplicationContext,参见安卓[android.content.Context](https://developer.android.com/reference/android/content/Context.html)
|
||||
> 这里的context由于是ApplicationContext,是不可见的,不能用于dialog和其他UI相关。如果要显示弹窗或者视图,请启动UI模式(代码的第一行为`"ui";`既可)并使用activity代替。
|
||||
* `activity` UI模式下会启动一个Activity来运行脚本,并在UI线程下运行。可通过该变量来获取该activity。
|
||||
###五、控制台
|
||||
### 五、控制台
|
||||
* `openConsole()` 打开控制台。
|
||||
* `clearConsole()` 清空控制台。
|
||||
* `log(text)` 在控制台中输出日志,以例如`log("Hello world");`。
|
||||
* `err(text)` 在控制台中输出错误信息,以红色字体显示,例如:
|
||||
|
||||
```
|
||||
try{
|
||||
//do something
|
||||
@@ -58,8 +63,10 @@ try{
|
||||
openConsole();
|
||||
}
|
||||
```
|
||||
|
||||
###六、在脚本中调用Java
|
||||
### 六、运行模式
|
||||
* UI模式 在脚本第一行加上`"ui;"`。UI模式下会启动一个Activity来运行脚本,并在UI线程下运行。
|
||||
* 驻留模式 在脚本第一行加上`"stay;"`,驻留模式下代码运行结束后不会马上退出,只有"关闭所有正在运行的脚本"或者调用`stop()`才会停止。适用于诸如addAccessibilityDelegate的代理后台运行。
|
||||
### 七、在脚本中调用Java
|
||||
使用importClass来引入要使用的库,例如:
|
||||
```javascript
|
||||
importClass("android.view.View.OnClickListener")
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>DuktapeJava</name>
|
||||
<url>https://github.com/gubaojian/DuktapeJava</url>
|
||||
<name>Rhino: JavaScript in Java</name>
|
||||
<url>https://github.com/mozilla/rhino</url>
|
||||
<copyright/>
|
||||
<license>MIT License</license>
|
||||
<license>Mozilla Public License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>920 Text Editor</name>
|
||||
@@ -25,10 +25,10 @@
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>MarkDown</name>
|
||||
<url>https://github.com/zzhoujay/Markdown</url>
|
||||
<copyright>Copyright (c) 2015 zzhoujay</copyright>
|
||||
<license>MIT License</license>
|
||||
<name>commonmark-java</name>
|
||||
<url>https://github.com/atlassian/commonmark-java</url>
|
||||
<copyright>Copyright (c) 2015-2016 Atlassian and others.</copyright>
|
||||
<license>BSD 2-Clause License</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Alipay ZeroSdk</name>
|
||||
|
||||
180
app/src/main/res/raw/mpl_20_full.txt
Normal file
180
app/src/main/res/raw/mpl_20_full.txt
Normal file
@@ -0,0 +1,180 @@
|
||||
Mozilla Public License
|
||||
Version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or
|
||||
|
||||
that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
means any of the following:
|
||||
|
||||
any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
|
||||
|
||||
under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and
|
||||
|
||||
under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or
|
||||
|
||||
under Patent Claims infringed by Covered Software in the absence of its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and
|
||||
|
||||
You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.
|
||||
3
app/src/main/res/raw/mpl_20_summary.txt
Normal file
3
app/src/main/res/raw/mpl_20_summary.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
@@ -32,7 +32,7 @@
|
||||
<string name="text_setting">设置</string>
|
||||
<string name="text_exit">退出</string>
|
||||
<string name="text_auto_operate_service">自动操作服务</string>
|
||||
<string name="text_syntax_and_api">语法与API</string>
|
||||
<string name="text_syntax_and_api">帮助</string>
|
||||
<string name="text_close_all_running_scripts">关闭所有正在运行的脚本</string>
|
||||
<string name="text_about">关于</string>
|
||||
<string name="text_licenses">开放源代码许可</string>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="donate_developer">打赏作者</string>
|
||||
<string name="text_email">邮箱</string>
|
||||
<string name="my_github">https://github.com/hyb1996/NoRootScriptDroid</string>
|
||||
<string name="share_app">[免Root脚本精灵]下载地址:https://github.com/hyb1996/NoRootScriptDroid/releases</string>
|
||||
<string name="share_app">[免Root脚本精灵]下载地址:http://www.coolapk.com/apk/com.stardust.scriptdroid </string>
|
||||
<string name="text_assist_service">点击区域辅助服务</string>
|
||||
<string name="text_assist_service_notification">通知栏点击区域辅助开关</string>
|
||||
<string name="text_others">其他</string>
|
||||
@@ -83,7 +83,7 @@
|
||||
<string name="text_choose_email_app">请选择邮件应用</string>
|
||||
<string name="text_assist_clip">点击定位剪贴板</string>
|
||||
<string name="text_script_stopped">脚本已停止</string>
|
||||
<string name="assist_mode_notice">\"点击区域辅助服务\"是在要点击的区域不是文本时使用的,参见\"语法与API\"。\"点击区域辅助服务\"只有在\"自动操作服务\"开启时才有效。</string>
|
||||
<string name="assist_mode_notice">\"点击区域辅助服务\"是在要点击的区域不是文本时使用的,参见\"帮助\"。\"点击区域辅助服务\"只有在\"自动操作服务\"开启时才有效。</string>
|
||||
<string name="text_please_choose">请选择</string>
|
||||
<string name="text_crash">崩溃了o(≧口≦)o</string>
|
||||
<string name="crash_feedback">点击\"退出\"退出程序(ಥ _ ಥ)或者提交错误报告或者复制调试信息反馈给开发者(≧∇≦)ノ</string>
|
||||
@@ -114,7 +114,7 @@
|
||||
<string name="text_file_write_fail">文件写入失败</string>
|
||||
<string name="text_need_enable_accessibility_service">需要打开\"自动操作服务“才能录制脚本</string>
|
||||
<string name="text_join_qq_group">加入QQ交流群</string>
|
||||
<string name="text_qq_group_id_copied">已复制群号到剪贴板</string>
|
||||
<string name="text_copied">已复制剪贴板</string>
|
||||
<string name="text_close">关闭</string>
|
||||
<string name="text_re_record">重新录制</string>
|
||||
<string name="text_use_volume_control_record">使用音量键控制</string>
|
||||
@@ -125,4 +125,14 @@
|
||||
<string name="key_max_length_for_code_completion">key_max_length_for_code_completion</string>
|
||||
<string name="text_max_length_for_code_completion">代码补全最大文件长度</string>
|
||||
<string name="text_file_exists">文件已存在</string>
|
||||
<string name="text_accessibility_service">无障碍服务</string>
|
||||
<string name="text_enable_accessibility_service_by_root">通过Root权限自动启用服务</string>
|
||||
<string name="text_auto_operate_service_enabled_but_not_running">自动操作服务已启用但并未运行,这可能是安卓的BUG,您可能需要重启手机</string>
|
||||
<string name="key_enable_accessibility_service_by_root">key_enable_accessibility_service_by_root</string>
|
||||
<string name="text_enable_accessibility_service_by_root_timeout">使用Root权限启动自动操作服务超时</string>
|
||||
<string name="summary_enable_accessibility_service_by_root">开启后运行脚本会使用Root权限自动开启自动操作服务</string>
|
||||
<string name="text_appearance">外观</string>
|
||||
<string name="text_theme_color">主题色</string>
|
||||
<string name="text_select_image">选择图片</string>
|
||||
<string name="text_help">帮助</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,46 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/text_script_record">
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_script_record">
|
||||
|
||||
<SwitchPreference
|
||||
<com.stardust.theme.preference.ThemeColorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/key_use_volume_control_record"
|
||||
android:summary="@string/summary_use_volume_control_record"
|
||||
android:title="@string/text_use_volume_control_record"/>
|
||||
</PreferenceCategory>
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/text_edit">
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_edit">
|
||||
|
||||
<com.afollestad.materialdialogs.prefs.MaterialEditTextPreference
|
||||
android:defaultValue="2000"
|
||||
android:inputType="number"
|
||||
android:key="@string/key_max_length_for_code_completion"
|
||||
android:title="@string/text_max_length_for_code_completion"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/text_others">
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:title="@string/text_re_import_samples"/>
|
||||
</PreferenceCategory>
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_accessibility_service">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/text_about">
|
||||
<com.stardust.theme.preference.ThemeColorSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/key_enable_accessibility_service_by_root"
|
||||
android:summary="@string/summary_enable_accessibility_service_by_root"
|
||||
android:title="@string/text_enable_accessibility_service_by_root"/>
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:title="@string/text_issue_report"/>
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_appearance">
|
||||
<Preference android:title="@string/text_theme_color"/>
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:title="@string/text_join_qq_group"/>
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_others">
|
||||
|
||||
<Preference
|
||||
android:title="@string/text_about"/>
|
||||
<Preference android:title="@string/text_re_import_samples"/>
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:title="@string/text_licenses"/>
|
||||
</PreferenceCategory>
|
||||
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_about">
|
||||
|
||||
<Preference android:title="@string/text_issue_report"/>
|
||||
|
||||
<Preference android:title="@string/text_join_qq_group"/>
|
||||
|
||||
<Preference android:title="@string/text_about"/>
|
||||
|
||||
<Preference android:title="@string/text_licenses"/>
|
||||
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,6 +1,28 @@
|
||||
package com.stardust.scriptdroid;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.stardust.scriptdroid.record.root.InputEventConverter;
|
||||
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.ContextFactory;
|
||||
import org.mozilla.javascript.ImporterTopLevel;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
@@ -8,13 +30,14 @@ import org.junit.Test;
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
// TODO: 2017/3/3 引擎兼容性
|
||||
// TODO: 2017/3/3 更多的示例
|
||||
// TODO: 2017/3/3 自定义函数
|
||||
// TODO: 2017/3/3
|
||||
|
||||
private int i = 0;
|
||||
|
||||
@Test
|
||||
public void testSync() throws Exception {
|
||||
|
||||
public void test() {
|
||||
System.out.println(new SingleEvent("[ 109722.085815] /dev/input/event1: EV_SYN SYN_REPORT 00000000"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user