Added: Root automator, custom theme color, some helps

This commit is contained in:
hyb1996
2017-03-07 13:49:56 +08:00
parent 6ec0601373
commit 38c22646e0
88 changed files with 6659 additions and 412 deletions

View File

@@ -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"/>

View File

@@ -0,0 +1,13 @@
{
"main" : [
{"title":"语法"},
{"title":"JavaScript教程"},
{"title":"自动操作函数"},
{"title":"一般常用函数"},
{"title":"控制台"},
{"title":"UI(用户界面)"},
{"title":"驻留模式与无障碍服务委托"},
{"title":"shell命令"},
{"title":"需要Root权限的自动操作函数"}
]
}

View 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.0API 级别 23及更高版本的设备上可以是应用清单中声明的任何权限。在运行 Android 5.1API 级别 22和更低版本的设备上必须是应用定义的可选权限。
### revoke package_name permission
从应用中撤销权限。在运行 Android 6.0API 级别 23及更高版本的设备上可以是应用清单中声明的任何权限。在运行 Android 5.1API 级别 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"));
```

View 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();
}
```

View 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。

View 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

View File

@@ -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);
}
*/

View File

@@ -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();
}));
});

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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());

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -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() {

View File

@@ -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));
}
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}
*/

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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")) {
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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();
}
});
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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/";
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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")

View File

@@ -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>

View 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 Contributors 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 partys 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 partys 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 partys 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.

View 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/.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"));
}
}