Release 2.0.1Beta

This commit is contained in:
hyb1996
2017-03-19 16:21:07 +08:00
parent 85affed538
commit dd787236e7
110 changed files with 2428 additions and 4710 deletions

View File

@@ -78,8 +78,7 @@
android:theme="@style/IssueReportTheme"/>
<activity android:name=".droid.script.ScriptExecuteActivity"/>
<service android:name=".external.notification.record.ActionRecordSwitchHandleService"/>
<service android:name=".service.VolumeChangeListenService"/>
<service android:name=".service.VolumeChangeObverseService"/>
<service android:name="com.stardust.view.Floaty$FloatHeadService"/>
<service android:name="com.stardust.view.ResizableFloaty$FloatingWindowService"/>
<service android:name="com.stardust.scriptdroid.external.floating_window.HoverMenuService"/>

View File

@@ -53,6 +53,11 @@
"title": "需要Root权限的自动操作函数",
"type": "markdown",
"path":"documentation"
},
{
"title": "调用Java API",
"type": "markdown",
"path":"documentation"
}
],
"EMCAScript教程": [

View File

@@ -27,6 +27,7 @@ if(result.code == 1){
* [应用包名](#应用包名)
* [pm命令](#pm命令)
* [其他命令](#其他命令)
* [Shell类](#Shell类)
## am命令
@@ -356,4 +357,41 @@ ls filepath
例如:
```
log(shell("ls /system/bin"));
```
```
## Shell类
当需要连续执行很多命令时每条命令都需要申请Root权限执行时间会大大增加。这时可以使用Shell类。
### new Shell(root = false)
* root \<Boolean\> 是否以root权限执行。默认为false。
Shell类的构造函数。
### Shell.execute(cmd)
* cmd \<String\> 要执行的命令
执行命令cmd例如
```
var sh = Shell(true);
sh.execute("rm /sdcard/1.txt"); //删除SD卡上的1.txt文件
```
### Shell.exitAndWaitFor()
退出Shell并等待所有命令执行完成。
一个完整的例子如下:
```
var sh = Shell(true);
sh.execute("rm /sdcard/1.txt");
sh.execute("input keyevent 26");
sh.exitAndWaitFor();
```
### Shell.waitFor()
等待当前所有命令执行完成。返回一个整数作为返回码1为执行成功其他数字为错误。
### Shell.exit()
退出Shell。调用该函数后再执行的命令无效。
除以上命令之外, Shell也包含自动操作的命令例如Shell.Tap参见《需要Root权限的自动操作函数》。

View File

@@ -20,10 +20,10 @@ launchPackage("com.tencent.mm");
launchApp("微信");
```
### getPackageName()
### currentPackage()
返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。
### getActivityName()
### currentActivity()
返回最近一次监测到的正在运行的Activity的名称一般可以认为就是当前正在运行的Activity的名称。
### setClip(text)

View File

@@ -1,16 +1,21 @@
目录
* [没有root权限是否支持游戏](#没有root权限是否支持游戏)
* [如何重复做某些操作n次](#如何重复做某些操作n次)
* [如何循环做某些操作](#如何循环做某些操作)
* [某些有文字的按键点击无效怎么办](#某些有文字的按键点击无效怎么办)
* [如何点击图标按钮](#如何点击图标按钮)
* [如何支持Tasker调用](#如何支持Tasker调用)
### 没有root权限是否支持游戏
**游戏需要root权限!!!**
**游戏需要root权限!!!**
**游戏需要root权限!!!**
100%的游戏在没有root权限的情况下都不支持。
**游戏需要root权限!!!**
**游戏需要root权限!!!**
**游戏需要root权限!!!**
不管是录制脚本还是运行脚本在没有root权限的情况下都无法做到在游戏界面不使用root权限录制脚本录制出来的是空白的。
### 如何重复做某些操作n次
使用循环语句实现实现。例如每个1秒执行一个操作总共执行20次的代码如下:
```
for(var i = 1; i <= 20; i++){
@@ -19,10 +24,27 @@ for(var i = 1; i <= 20; i++){
}
```
### 如何循环做某些操作
使用while(true)语句实现。例如QQ无限发送消息的脚本如下
```
while(true){
input("测试");
click("发送");
}
```
### 某些有文字的按键点击无效怎么办
例如QQ下方的"联系人"按钮用click("联系人")是无效的。这是因为这些按钮上的文字并不是真正的文字,而是一个图标。参见[如何点击图标按钮](#如何点击图标按钮)。
### 如何点击图标按钮
有以下几种方法:
* 录制脚本
* 通过图标在屏幕上的位置来点击。要获取图标在屏幕上的位置,可以开启悬浮窗,选择布局范围查看,之后点击要点击的图标,在弹出的窗口中的"bounds"一栏即图标在屏幕上的区域。例如:
* 通过id, desc等选择器来定位图片
### 如何支持Tasker调用
新建任务时选择文件点击浏览找到要运行的脚本文件在MimeType处填入application/x-javascript。

View File

@@ -8,6 +8,9 @@
### log(text)
* text \<String\> | \<Object\> 要打印到控制台的信息
### print(text)
等同于log。
在控制台中输出日志,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。
### err(error)

View File

@@ -0,0 +1,162 @@
以下内容来自[Rhino:Scripting Java](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino/Scripting_Java)。
这个章节描述了如何在rhino中使用java。使用脚本调用Java有很多用途它使得我们可以利用Java中现有的库来帮助我们构建强大的脚本。我们可以通过编写脚本来对Java程序进行测试。可以通过脚本来进行探索式编程辅助Java的开发所谓探索式编程就是通过快速地编程调用库或API来探索这些库或API可以做什么显而易见脚本语言很适合探索式编程。
这里注意ECMA标准并没有包含和Java(或者其他任何对象系统)交互的标准。本文所描述的所有内容,应该被认为是一个扩展。
目录:
* [访问Java的包和类](#访问Java的包和类)
* [使用Java的类](#使用Java的类)
* [实现Java接口](#实现Java接口)
* [创建Java数组](#创建Java数组)
* [Java字符串与JavaScript字符串](#Java字符串与JavaScript字符串)
* [Java异常](#Java异常)
* [JavaImporter](#JavaImporter)
## 访问Java的包和类
Java的每段代码都是类的一部分每一个JAVA类都是包的一部分。在Javascript中脚本不属于任何package。我们可以访问Java包中的类么
Rhino定义了一个顶层的变量Packages。Packages的所有属性都是Java中顶层的包比如java和com。比如我们可以访问java包
```
Packages.java.util.Vector
```
还有一种更方便的方式Rhino定义了一个顶层的变量java等价于Packages.java。所以上面的例子可以更简介地写成
```
java.util.Vector
```
如果你的脚本需要访问很多的Java类每次都附带完整的包名会使得编程很麻烦。Rhino提供了一个顶层的方法importPackage它的功能和Java的import一样。比如我们可以导入java.io包中的所有类然后直接通过类名File来访问java.io.File:
```
importPackage(java.io)
```
这里importPackage(java.io)使得java.io包中的所有类(例如File)可以在顶层被访问。这和Java中的java.io.*;等价。
要注意Java会默认导入java.lang.*但是Rhino不会。因为JavaScript的顶层对象Boolean、Math、Number、Object和String和java.lang包中同名的类并不相同。因为这种冲突建议不要用importPackage来导入java.lang包。
有一点要注意的就是Rhino对于指定包名或类名时是如何处理错误的。如果java.Myclass是可访问的Rhino会试图加载名为java.MyClass的类如果加载失败它会假设java.MyClass是一个包名不会报错。只有在你试图将这个对象当作类使用时才会报错。
## 使用Java的类
现在我们可以访问Java类下一步就是要创建一个对象。方法就和在Java中一样 用new来创建对象
```
new java.util.Date();
```
如果我们将创建的对象存放在JavaScript变量中我们可以调用它的方法
```
var f = new java.io.File("test.text");
log(f.exists());
log(f.getName());
```
静态方法和属性可以直接通过类对象来访问:
```
log(java.lang.Math.PI);
```
不像Java在JavaScript里方法就是一个对象。它可以被评估也可以被调用。如果我们去查看这个方法我们可以看到这个方法所有重载的形式
```
log(f.listFiles);
```
输出:
```
function listFiles() {/*
java.io.File[] listFiles()
java.io.File[] listFiles(java.io.FilenameFilter)
java.io.File[] listFiles(java.io.FileFilter)
*/}
```
输出告诉我们File类有listFiles方法的三种重载一种不包含参数的另一种包含一个FilenameFilter类型的参数第三个包含一个FileFilter类型的参数。所有的方法都返回一个File对象数组。可以观察到Java方法的参数和返回类型在探索式编程中是非常有用的尤其是在对一个方法的参数和返回对象不确定的时候。
另一个有助于探索式编程的特性是可以看到对象中定义的所有方法和属性。用JavaScript的for..in , 我们可以打印这些值:
```
for (i in f) { log(i) }
```
注意这里不仅列出了File类中的所有方法也列出了从基类java.lang.Object中继承的方法例如wait。这使得我们可以更好地处理那些有复杂继承关系的对象因为我们可以看到对象中所有可用的方法。
Rhino可以通过属性名来方便地访问JavaBean的属性。一个JavaBean的属性foo被方法getFoo和setFoo定义另外一个也叫foo的boolean类型的属性可以被isFoo来定义。比如, 下面的代码实际上调用了File对象的getName和isDirectory方法。
## 实现Java接口
```
obj = { run: function () { log("\nrunning"); } };
r = new java.lang.Runnable(obj);
t = new java.lang.Thread(r);
t.start();
```
若一个接口只有一个方法,也可以用直接用函数代替:
```
t = java.lang.Thread(function () { print("\nrunning"); });
t.start();
```
## 创建Java数组、
Rhino没有提供特定的语法创建Java的数组只能通过反射来创建。
```
a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
b = java.lang.reflect.Array.newInstance(java.lang.String, 5);
```
之后可以像JavaScript数组一样使用
```
a[0] = 'a';
a[1] = 'b';
```
## Java字符串与JavaScript字符串
Java字符串与JavaScript字符串并不等同但在某种程度上两者可以通用。例如你可以传递一个JavaScript的字符串给一个需要Java字符串参数的函数。
```
javaString = new java.lang.String("Java");
jsString = "JavaScript";
log(javaString.length());
log(jsString.length);
```
也可以对Java字符串调用了一个在java.lang.String中没有定义的JavaScript方法例如
```
log(javaString.match(/a.*/));
```
## Java异常
例如:
```
try {
java.lang.Class.forName("NonExistingClass");
} catch (e) {
if (e.javaException instanceof java.lang.ClassNotFoundException) {
print("Class not found");
}
}
```
或者
```
function classForName(name) {
try {
return java.lang.Class.forName(name);
} catch (e if e.javaException instanceof java.lang.ClassNotFoundException) {
print("Class " + name + " not found");
} catch (e if e.javaException instanceof java.lang.NullPointerException) {
print("Class name is null");
}
}
classForName("NonExistingClass");
classForName(null);
```
## JavaImporter
```
var SwingGui = JavaImporter(Packages.javax.swing,
Packages.javax.swing.event,
Packages.javax.swing.border,
java.awt.event,
java.awt.Point,
java.awt.Rectangle,
java.awt.Dimension);
...
with (SwingGui) {
var mybutton = new JButton(test);
var mypoint = new Point(10, 10);
var myframe = new JFrame();
...
}
```

View File

@@ -1,12 +1,15 @@
目录:
* [简介](#简介)
* [选择器](#选择器)
* [筛选条件](#筛选条件)
* [搜索](#搜索)
* [操作](#操作)
* [控件](#控件)
* [控件集合](#控件集合)
## 简介
选择器提供了更强大的定位界面控件与对进行操作的功能,可以用他们完成更多的自动操作。
选择器提供了更强大的定位界面控件与对进行操作的功能,可以用他们完成更多的自动操作和提取界面信息
选择器通过附加筛选条件,筛选出符合条件的控件或控件集合,之后可以对这些控件这些操作。
可供选择的筛选条件包括:
* id
@@ -17,21 +20,21 @@
* bounds 在屏幕上的范围
* drawingOrder 绘制顺序
* 各种属性,主要包括:
* clickable
* longClickable
* checkable
* checked
* scrollable
* selected
* editable
* visibleToUser
* enabled
* clickable 可点击
* longClickable 可长按
* checkable 可勾选
* checked 被勾选
* scrollable 可滑动
* selected 被选择
* editable 可编辑
* visibleToUser 可见
* enabled 已启用
要对选择器所确定的筛选条件对屏幕上的控件进行筛选调用方法find(), findOne(), untilFind(), untilFind()。其中find(), untilFind()返回控件的集合findOne(), untilFindOne()返回一个控件。until***函数会一直寻找知道屏幕上出现满足条件的控件为止。
要对选择器所确定的筛选条件对屏幕上的控件进行筛选调用方法find(), findOne(), untilFind()。其中find(), untilFind()返回控件的集合findOne()返回一个控件。untilFind和FindOne函数会一直寻找直到屏幕上出现满足条件的控件为止。
一个完整的选择器示例如下:
```
var emoj = id("name").packageName("com.tencent.mobileqq").clickable()
.className("ImageView").drawingOrder(5).untilFindOne();
.className("ImageView").drawingOrder(5).findOne();
emoj.click();
```
上述代码是找到QQ聊天界面下面的表情按钮(第5个按钮)并点击。也可以简化为:
@@ -42,9 +45,11 @@ id("name").packageName("com.tencent.mobileqq").clickable()
最后的click()函数相当于untilFind().click()。也就是如果只需要在找到控件以后立即进行操作只需要调用相应的操作函数而不需要先调用find等函数等到控件再操作。
要获取屏幕上的控件的信息(例如id, text, desc, bounds等),可以开启悬浮窗,使用界面层次查看和界面范围查看。
以下是具体的文档
因为选择器部分内容如果有安卓开发经验会更容易掌握因而更适合UI测试
## 选择器
# 选择器
## 筛选条件
### id(resId)
* resId \<String\> 控件的id以"包名:id/"开头,例如"com.tencent.mm:id/send_btn"。也可以不指定包名这时会以当前正在运行的应用的包名来补全id。例如id("send_btn"),在QQ界面想当于id("com.tencent.mobileqq:id/send_btn")。
@@ -56,6 +61,8 @@ id("name").packageName("com.tencent.mobileqq").clickable()
附加控件id包含字符串str的筛选条件。
> 控件的id属性通常是可以用来确定控件的唯一标识如果一个控件有id那么使用id来找到他是最好的方法。要查看屏幕上的控件的id可以开启悬浮窗并使用界面工具点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外在列表中会出现多个控件的id相同的情况。例如微信的联系人列表每个头像的id都是一样的。此时不能用id来唯一确定控件。
### idStartsWith(prefix)
* prefix \<String\> id前缀
@@ -72,127 +79,358 @@ id("name").packageName("com.tencent.mobileqq").clickable()
附加id需要满足正则表达式。有关正则表达式可以查看[菜鸟教程](http://www.runoob.com/regexp/regexp-example.html)。
### text(str)
* str \<String\> 控件文本
附加控件文本等于字符串str的筛选条件。
> 控件的text(文本)属性是控件上的显示的文字,例如微信左上角的"微信"文本。对于图片或者其他控件通常text属性为null。
### textContains(str)
* str \<String\> 要包含的字符串
附加控件文本需要以prefix开头的筛选条件。
### textStartsWith(prefix)
* prefix \<String\> 前缀
附加控件文本需要以prefix开头的筛选条件。
### textEndsWith(suffix)
* suffix \<String\> 后缀
附加控件文本需要以suffix结束的筛选条件。
### textMatches(str)
* regex \<Regex\> 要满足的正则表达式
### desc(desc)
附加控件文本需要满足正则表达式的条件。
### desc(str)
* str \<String\> 控件描述
附加控件描述等于字符串str的筛选条件。
> 控件的desc(描述全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。
### descContains(str)
* str \<String\> 要包含的字符串
附加控件描述需要以prefix开头的筛选条件。
### descStartsWith(prefix)
* prefix \<String\> 前缀
附加控件描述需要以prefix开头的筛选条件。
### descEndsWith(suffix)
* suffix \<String\> 后缀
附加控件描述需要以suffix结束的筛选条件。
### descMatches(str)
* regex \<Regex\> 要满足的正则表达式
### className(className)
附加控件描述需要满足正则表达式的条件。
### className(str)
* str \<String\> 控件类名
附加控件类名等于字符串str的筛选条件。
> 控件的className属性是一个控件的具体类型例如图片控件通常为ImageView文本控件通常为TextView, 输入框通常为EditText。(若一个控件是在android.widget包里的那么android.widget可以省略。但除此之外的其他控件必须是类的全名例如"com.stardust.theme.ThemeColorImageView")。
### classNameContains(str)
* str \<String\> 要包含的字符串
附加控件类名需要以prefix开头的筛选条件。
### classNameStartsWith(prefix)
* prefix \<String\> 前缀
附加控件类名需要以prefix开头的筛选条件。
### classNameEndsWith(suffix)
* suffix \<String\> 后缀
附加控件类名需要以suffix结束的筛选条件。
### classNameMatches(str)
* regex \<Regex\> 要满足的正则表达式
### packageName(packageName)
附加控件类名需要满足正则表达式的条件。
### packageName(str)
* str \<String\> 包名
附加包名等于字符串str的筛选条件。
> packageName属性也即控件所属的应用的包名(参见《shell命令:应用包名》),通常用来保证当前运行的应用是想要的应用。
### packageNameContains(str)
* str \<String\> 要包含的字符串
附加包名需要以prefix开头的筛选条件。
### packageNameStartsWith(prefix)
* prefix \<String\> 前缀
附加包名需要以prefix开头的筛选条件。
### packageNameEndsWith(suffix)
* suffix \<String\> 后缀
附加包名需要以suffix结束的筛选条件。
### packageNameMatches(str)
* regex \<Regex\> 要满足的正则表达式
附加包名需要满足正则表达式的条件。
### bounds(l, t, r, b)
* l, t, r, b 用来定位控件在屏幕上的范围的四个整数
### boundsInside(l, t, r, b)
### boundsContains(l, t, r, b)
### drawingOrder(order)
* order \<Number\> 控件在父视图中的绘制顺序,在一些
### checkable(b = true)
* b \<Boolean\> 表示控件是否可勾选, 默认为true
附加控件是否可勾选的条件。可勾选指的是例如QQ和微信发送图片时图片的勾选。
### selected(b = true)
* b \<Boolean\> 表示控件是否被选中默认为true
附加控件是否被选中的条件。被选中指的是例如QQ聊天界面点击下方的表情按钮时会出现自己收藏的表情这时表情按钮便处于选中状态。
### clickable(b = true)
* b \<Boolean\> 表示控件是否可点击, 默认为true
附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击这取决于控件的实现。因而要确定一个控件的clickable属性可以开启悬浮窗工具。
### longClickable(b = true)
* b \<Boolean\> 表示控件是否可长按, 默认为true
附加控件是否可长按的条件。
### enabled(b = true)
* b \<Boolean\> 表示控件是否已启用, 默认为true
### password(b = true)
附加控件是否已启用的条件。大多数控件都是启用的状态,处于“禁用”状态通常是灰色并且不可点击。
### scrollable(b = true)
* b \<Boolean\> 表示控件是否可滑动, 默认为true
附加控件是否可滑动的条件。
### editable(b = true)
* b \<Boolean\> 表示控件是否可编辑, 默认为true
附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText)。
### contentInvalid(b = true)
* b \<Boolean\> 表示控件是否是contentInvalid, 默认为true
我也不知道是什么,下次再补充吧。
### contextClickable(b = true)
* b \<Boolean\> 表示控件是否是contextClickable, 默认为true
我也不知道是什么,下次再补充吧。
### multiLine(b = true)
* b \<Boolean\> 表示文本或输入框控件是否是多行显示的, 默认为true
### dismissable(b = true)
附加控件是否文本或输入框控件是否是多行显示的条件。
## 搜索
### findOne()
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。参见[控件](#控件)。
### find()
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一直会找到,因而会出现返回的控件集合为空的情况。参见[控件集合](#控件集合)。
### untilFind()
根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。参见[控件集合](#控件集合)。
## 操作
### click()
找到所有符合条件的控件并点击。相当于untilFind().click()。参见[click](#UiCollection.click)。
### longClick()
### accessibilityFocus()
### clearAccessibilityFocus()
### focus()
### clearFocus()
找到所有符合条件的控件并长按。相当于untilFind().longClick()。参见[longClick](#UiCollection.longClick)。
### copy()
找到所有符合条件的控件并复制其内容只对文本框或输入框控件有文字选中时有效。相当于untilFind().copy()。参见[copy](#UiCollection.copy)。
### paste()
找到所有符合条件的控件并粘贴只对输入框控件有效。相当于untilFind().paste()。参见[paste](#UiCollection.paste)。
### select()
找到所有符合条件的控件并选中。相当于untilFind().select()。参见[select](#UiCollection.select)。
### cut()
找到所有符合条件的控件并剪切其内容只对文本框或输入框控件有文字选中时有效。相当于untilFind().cut()。参见[cut](#UiCollection.cut)。
### collapse()
找到所有符合条件的控件并折叠。相当于untilFind().collapse()。参见[collapse](#UiCollection.collapse)。
### expand()
### dismiss()
找到所有符合条件的控件并展开。相当于untilFind().expand()。参见[expand](#UiCollection.expand)。
### show()
找到所有符合条件的控件并使其出现在屏幕上。相当于untilFind().show()。参见[show](#UiCollection.show)。
### scrollForward()
找到所有符合条件的控件并向前滑。相当于untilFind().scrollForward()。参见[scrollForward](#UiCollection.scrollForward)。
向前滑指的是,对于左右滑动的控件向右滑,上下滑动的控件向下滑。
### scrollBackward()
找到所有符合条件的控件并向后滑。相当于untilFind().scrollBackward()。参见[scrollBackward](#UiCollection.scrollBackward)。
向后滑指的是,对于左右滑动的控件向左滑,上下滑动的控件向上滑。
### scrollUp()
找到所有符合条件的控件并向上滑。相当于untilFind().scrollUp()。参见[scrollUp](#UiCollection.scrollUp)。
### scrollDown()
找到所有符合条件的控件并向下滑。相当于untilFind().scrollDown()。参见[scrollDown](#UiCollection.scrollDown)。
### scrollLeft()
找到所有符合条件的控件并向左滑。相当于untilFind().scrollLeft()。参见[scrollLeft](#UiCollection.scrollLeft)。
### scrollRight()
找到所有符合条件的控件并向右滑。相当于untilFind().scrollRight()。参见[scrollRight](#UiCollection.scrollRight)。
### contextClick()
### setSelection(start, end)
* start \<Number\> 文字选中开始位置
* end \<Number\> 文字选中结束位置
找到所有符合条件的控件并设置文字选中区域只对文本控件和输入框有效。相当于untilFind().setSelection()。参见[setSelection](#UiCollection.setSelection)。
### setText(text)
* text \<String\> 要设置的文本
找到所有符合条件的控件并设置文字只对输入框有效。相当于untilFind().setText()。参见[setText](#UiCollection.setText)。
### setProgress(value)
# 控件集合
UiCollection, 通过选择器的find(), untilFind()方法返回的对象。可以对其进行操作或者获取其信息。
### scrollTo(row, column)
### UiCollection.size()
返回集合中的控件数。
## 控件
### UiCollection.get(i)
* i \<Number\> 索引
## 控件集合
返回集合中第i+1个控件(UiObject)。
### UiCollection.each(func)
* func \<Function\> 遍历函数
遍历集合。例如:
```
var c = clickable();
c.each(function(o){
log(o.text());
});
```
### find(selector)
* selector \<UiSelector\> 选择器
根据selector所确定的条件在该控件集合中找到所有符合条件的控件并返回控件集合。例如
```
var names = id("name");
var clickableNames = names.find(clickable());
```
## findOne(selector)
* selector \<UiSelector\> 选择器
根据selector所确定的条件在该控件集合中找到一个符合条件的控件并返回控件。若找不到则返回null。
### UiCollection.click()
点击集合中所有控件,并返回是否全部点击成功。
### UiCollection.longClick()
长按集合中所有控件,并返回是否全部操作成功。
### UiCollection.copy()
对集合中所有控件执行复制操作,并返回是否全部操作成功。
### UiCollection.paste()
对集合中所有控件执行粘贴操作,并返回是否全部操作成功。
### UiCollection.select()
对集合中所有控件执行选中操作,并返回是否全部操作成功。
### UiCollection.cut()
对集合中所有控件执行剪切操作,并返回是否全部操作成功。
### UiCollection.collapse()
对集合中所有控件执行折叠操作,并返回是否全部操作成功。
### UiCollection.expand()
对集合中所有控件执行展开操作,并返回是否全部操作成功。
### UiCollection.show()
对集合中所有控件执行显示操作,并返回是否全部操作成功。
### UiCollection.scrollForward()
对集合中所有控件执行向前滑的操作,并返回是否全部操作成功。
### UiCollection.scrollBackward()
对集合中所有控件执行向后滑的操作,并返回是否全部操作成功。
### UiCollection.scrollUp()
对集合中所有控件执行向上滑的操作,并返回是否全部操作成功。
### UiCollection.scrollDown()
对集合中所有控件执行向下滑的操作,并返回是否全部操作成功。
### UiCollection.scrollLeft()
对集合中所有控件执行向左滑的操作,并返回是否全部操作成功。
### UiCollection.scrollRight()
对集合中所有控件执行向右滑的操作,并返回是否全部操作成功。
### UiCollection.contextClick()
### UiCollection.setSelection(start, end)
* start \<Number\> 文字选中开始位置
* end \<Number\> 文字选中结束位置
对集合中所有控件设置文字选中区域,并返回是否全部操作成功。
### UiCollection.setText(text)
* text \<String\> 要设置的文本
对集合中所有控件设置文本,并返回是否全部操作成功。
# 控件
UiObject, 选择器的findOne方法返回的对象。和UiCollection有相似的方法不再赘述。除此之外还有以下方法。
### child(i)
* i \<Number\> 子控件索引
返回第i+1个子控件(UiObject)。
### parent()
返回父控件(UiObject)。
### children()
返回该控件的所有子控件组成的控件集合。

View File

@@ -80,27 +80,11 @@ var select = function(a, b, c, d){
}, arguments);
}
var focus = function(a, b, c, d){
return performAction(function(target){
return droid.focus(target);
}, arguments);
}
var paste = function(a, b, c, d){
return performAction(function(target){
return droid.paste(target);
}, arguments);
}
var editable = function(i){
return droid.editable(i);
}
var input = function(a, b){
if(arguments.length == 1){
return droid.setText(editable(-1), a);
return droid.setText(droid.editable(-1), a);
}else{
return droid.setText(editable(a), b);
return droid.setText(droid.editable(a), b);
}
}
@@ -120,6 +104,8 @@ var log = function(str){
droid.log(str);
}
var print = log;
var err = function(e){
droid.err(e);
}
@@ -137,16 +123,12 @@ var shell = function(cmd, root){
return droid.shell(cmd, root);
}
var getTexts = function(){
return droid.getTexts();
var currentPackage = function(){
return droid.currentPackage();
}
var getPackageName = function(){
return droid.getPackageName();
}
var getActivityName = function(){
return droid.getActivityName();
var currentActivity = function(){
return droid.currentActivity();
}
var setClip = function(text){
@@ -229,6 +211,7 @@ for(var x in __selector__){
this[x] = (function(method) {
return function(){
var s = droid.selector();
//这里不知道怎么写。尴尬。只能写成这样。
if(arguments.length == 0){
return s[method]();
}else if(arguments.length == 1){

View File

@@ -0,0 +1,190 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
print("----------------------------------------");
// Use the XML constructor to parse an string into an XML object
var John = "<employee><name>John</name><age>25</age></employee>";
var Sue ="<employee><name>Sue</name><age>32</age></employee>";
var tagName = "employees";
var employees = new XML("<" + tagName +">" + John + Sue + "</" + tagName +">");
print("The employees XML object constructed from a string is:\n" + employees);
print("----------------------------------------");
// Use an XML literal to create an XML object
var order = <order>
<customer>
<firstname>John</firstname>
<lastname>Doe</lastname>
</customer>
<item>
<description>Big Screen Television</description>
<price>1299.99</price>
<quantity>1</quantity>
</item>
</order>
// Construct the full customer name
var name = order.customer.firstname + " " + order.customer.lastname;
// Calculate the total price
var total = order.item.price * order.item.quantity;
print("The order XML object constructed using a literal is:\n" + order);
print("The total price of " + name + "'s order is " + total);
print("----------------------------------------");
// construct a new XML object using expando and super-expando properties
var order = <order/>;
order.customer.name = "Fred Jones";
order.customer.address.street = "123 Long Lang";
order.customer.address.city = "Underwood";
order.customer.address.state = "CA";
order.item[0] = "";
order.item[0].description = "Small Rodents";
order.item[0].quantity = 10;
order.item[0].price = 6.95;
print("The order custructed using expandos and super-expandos is:\n" + order);
// append a new item to the order
order.item += <item><description>Catapult</description><price>139.95</price></item>;
print("----------------------------------------");
print("The order after appending a new item is:\n" + order);
print("----------------------------------------");
// dynamically construct an XML element using embedded expressions
var tagname = "name";
var attributename = "id";
var attributevalue = 5;
var content = "Fred";
var x = <{tagname} {attributename}={attributevalue}>{content}</{tagname}>;
print("The dynamically computed element value is:\n" + x.toXMLString());
print("----------------------------------------");
// Create a SOAP message
var message = <soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<soap:Body>
<m:GetLastTradePrice xmlns:m="http://mycompany.com/stocks">
<symbol>DIS</symbol>
</m:GetLastTradePrice>
</soap:Body>
</soap:Envelope>
// declare the SOAP and stocks namespaces
var soap = new Namespace("http://schemas.xmlsoap.org/soap/envelope/");
var stock = new Namespace ("http://mycompany.com/stocks");
// extract the soap encoding style and body from the soap message
var encodingStyle = message.@soap::encodingStyle;
print("The encoding style of the soap message is specified by:\n" + encodingStyle);
// change the stock symbol
message.soap::Body.stock::GetLastTradePrice.symbol = "MYCO";
var body = message.soap::Body;
print("The body of the soap message is:\n" + body);
print("----------------------------------------");
// create an manipulate an XML object using the default xml namespace
default xml namespace = "http://default.namespace.com";
var x = <x/>;
x.a = "one";
x.b = "two";
x.c = <c xmlns="http://some.other.namespace.com">three</c>;
print("XML object constructed using the default xml namespace:\n" + x);
default xml namespace="";
print("----------------------------------------");
var order = <order id = "123456" timestamp="Mon Mar 10 2003 16:03:25 GMT-0800 (PST)">
<customer>
<firstname>John</firstname>
<lastname>Doe</lastname>
</customer>
<item id="3456">
<description>Big Screen Television</description>
<price>1299.99</price>
<quantity>1</quantity>
</item>
<item id = "56789">
<description>DVD Player</description>
<price>399.99</price>
<quantity>1</quantity>
</item>
</order>;
// get the customer element from the orderprint("The customer is:\n" + order.customer);
// get the id attribute from the order
print("The order id is:" + order.@id);
// get all the child elements from the order element
print("The children of the order are:\n" + order.*);
// get the list of all item descriptions
print("The order descriptions are:\n" + order.item.description);
// get second item by numeric index
print("The second item is:\n" + order.item[1]);
// get the list of all child elements in all item elements
print("The children of the items are:\n" + order.item.*);
// get the second child element from the order by index
print("The second child of the order is:\n" + order.*[1]);
// calculate the total price of the order
var totalprice = 0;
for each (i in order.item) {
totalprice += i.price * i.quantity;
}
print("The total price of the order is: " + totalprice);
print("----------------------------------------");
var e = <employees>
<employee id="1"><name>Joe</name><age>20</age></employee>
<employee id="2"><name>Sue</name><age>30</age></employee>
</employees>;
// get all the names in e
print("All the employee names are:\n" + e..name);
// employees with name Joe
print("The employee named Joe is:\n" + e.employee.(name == "Joe"));
// employees with id's 1 & 2
print("Employees with ids 1 & 2:\n" + e.employee.(@id == 1 || @id == 2));
// name of employee with id 1
print("Name of the the employee with ID=1: " + e.employee.(@id == 1).name);
print("----------------------------------------");
openConsole();

View File

@@ -0,0 +1 @@
shell("pm disable com.netease.cloudmusic", true);

View File

@@ -0,0 +1 @@
shell("am start -n com.tencent.mm/com.tencent.mm.plugin.scanner.ui.BaseScanUI", true);

View File

@@ -0,0 +1 @@
shell("am start -n com.eg.android.AlipayGphone/com.alipay.mobile.scan.as.main.MainCaptureActivity", true);

View File

@@ -0,0 +1 @@
shell("am kill-all", true);

View File

@@ -0,0 +1,2 @@
shell("pm enable com.netease.cloudmusic", true);
launchApp("网易云音乐");

View File

@@ -0,0 +1,2 @@
KeyCode("KEYCODE_POWER");
//或者 KeyCode(26);

View File

@@ -1,10 +1,10 @@
"ui";
importClass("android.widget.EditText");
importClass("android.widget.Button");
importClass("android.widget.LinearLayout");
importClass("android.content.Intent");
importClass("android.net.Uri");
importClass(android.widget.EditText);
importClass(android.widget.Button);
importClass(android.widget.LinearLayout);
importClass(android.content.Intent);
importClass(android.net.Uri);
var qq = new EditText(activity);
qq.setHint("请输入QQ号");

View File

@@ -1,6 +1,6 @@
"ui";
importClass("com.afollestad.materialdialogs.MaterialDialog");
importClass(com.afollestad.materialdialogs.MaterialDialog);
new MaterialDialog.Builder(activity)
.title("简单计算器")

View File

@@ -4,7 +4,7 @@ toast("出现红包时将会自动拆开并关闭");
while(!isStopped()){
if(click("点击拆开")){
//拆开红包后尝试10次关闭红包页面
for(var i = 0; i < 10; i++){
for(let i = 0; i < 10; i++){
sleep(300);
if(click("关闭"))
break;

View File

@@ -0,0 +1,16 @@
importClass(android.content.Intent);
importClass(android.net.Uri);
importClass(android.provider.Settings);
function 打开App设置(packageName){
var intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", packageName, null);
intent.setData(uri);
context.startActivity(intent);
}
打开App设置("com.tencent.mobileqq");
while(!currentPackage().equals("com.android.settings"));
while(!click("通知"));
while(!click("全部阻止"));

View File

@@ -1,5 +1,5 @@
function openRunningServices(){
function 打开正在运行的服务(){
launch("com.android.settings");
var i = 0;
while(!click("开发者选项")){
@@ -14,10 +14,10 @@ function openRunningServices(){
}
importClass("android.os.Build");
importClass(android.os.Build);
if(Build.VERSION.SDK_INT < 23){
toast("本代码只适用于Android6.0以上");
}else{
openRunningServices();
打开正在运行的服务();
}

View File

@@ -0,0 +1,26 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
/**
* liveConnect.js: a simple demonstration of JavaScript-to-Java connectivity
*/
// Create a new StringBuffer. Note that the class name must be fully qualified
// by its package. Packages other than "java" must start with "Packages", i.e.,
// "Packages.javax.servlet...".
var sb = new java.lang.StringBuffer();
// Now add some stuff to the buffer.
sb.append("hi, mom");
sb.append(3); // this will add "3.0" to the buffer since all JS numbers
// are doubles by default
sb.append(true);
// Now print it out. (The toString() method of sb is automatically called
// to convert the buffer to a string.)
// Should print "hi, mom3.0true".
print(sb);
openConsole();

View File

@@ -0,0 +1,22 @@
function 下滑(){
className("AbsListView").scrollable().scrollForward();
}
function (){
className("ImageView").desc("赞").click();
}
function 显示更多(){
for(let i = 0; i < 2;i++){
click("显示更多");
}
}
toast("请打开点赞自己的人的界面");
while(notStopped()){
for(let i = 0; i < 10; i++){
();
}
显示更多();
下滑();
}

View File

@@ -0,0 +1,6 @@
while(true){
if(id("i4").untilFindOne().parent().parent().click()){
sleep(700);
id("gf").click();
}
}

View File

@@ -0,0 +1,3 @@
launchApp("网易云音乐");
id("kp").click();
text("签到").className("TextView").click();

View File

@@ -16,16 +16,16 @@ public interface OnActivityResultDelegate {
void onActivityResult(int requestCode, int resultCode, Intent data);
interface DelegateHost {
Manager getDelegateManger();
Intermediary getDelegateManger();
}
class Manager implements OnActivityResultDelegate {
class Intermediary implements OnActivityResultDelegate {
private SparseArray<OnActivityResultDelegate> specialDelegate = new SparseArray<>();
private SparseArray<OnActivityResultDelegate> mSpecialDelegate = new SparseArray<>();
private List<OnActivityResultDelegate> mDelegates = new ArrayList<>();
public void onActivityResult(int requestCode, int resultCode, Intent data) {
OnActivityResultDelegate delegate = specialDelegate.get(requestCode);
OnActivityResultDelegate delegate = mSpecialDelegate.get(requestCode);
if (delegate != null) {
delegate.onActivityResult(requestCode, resultCode, data);
}
@@ -39,7 +39,14 @@ public interface OnActivityResultDelegate {
}
public void addDelegate(int requestCode, OnActivityResultDelegate delegate) {
specialDelegate.put(requestCode, delegate);
mSpecialDelegate.put(requestCode, delegate);
}
public void removeDelegate(OnActivityResultDelegate delegate) {
if (mDelegates.remove(delegate)) {
// TODO: 2017/3/16 优化
mSpecialDelegate.removeAt(mSpecialDelegate.indexOfValue(delegate));
}
}
}

View File

@@ -183,7 +183,6 @@ public class HoverMenuView extends RelativeLayout {
MagnetPositioner magnetPositioner = new MagnetPositioner(getResources().getDisplayMetrics(), mSidePullerPositioner, new MagnetPositioner.OnCompletionListener() {
@Override
public void onPullToSideCompleted() {
// TODO: this collapsed logic is duplicated
mVisualState = COLLAPSED;
enableDragging();
mTransitionListener.onCollapsed();
@@ -257,7 +256,6 @@ public class HoverMenuView extends RelativeLayout {
removeAllTabs();
addAllTabs();
// TODO: need null check in case selected tab is gone.
Log.d(TAG, "Content change. Active id: " + mActiveTabId);
Log.d(TAG, "Active tab view: " + findViewById(mActiveTabId.hashCode()));
mActiveTab = findViewById(mActiveTabId.hashCode());
@@ -270,7 +268,7 @@ public class HoverMenuView extends RelativeLayout {
super(context);
mWindowDragWatcher = windowDragWatcher;
mMenuAnchor = new CollapsedMenuAnchor(getResources().getDisplayMetrics(), 10);
mMenuAnchor.setAnchorAt((int) savedAnchor.x, savedAnchor.y); // TODO: savedAnchor isn't really a position, its a tuple of values (side, y-pos)
mMenuAnchor.setAnchorAt((int) savedAnchor.x, savedAnchor.y);
mNavigator = null == navigator ? new DefaultNavigator(context) : navigator;
init();
}
@@ -335,7 +333,6 @@ public class HoverMenuView extends RelativeLayout {
mExitRequestListener = null == exitRequestListener ? new NoOpHoverMenuExitRequestListener() : exitRequestListener;
}
// TODO: create custom object to hold anchor state
public PointF getAnchorState() {
return new PointF(mMenuAnchor.getAnchorSide(), mMenuAnchor.getAnchorNormalizedY());
}
@@ -493,7 +490,6 @@ public class HoverMenuView extends RelativeLayout {
Log.d(TAG, "Enabling dragging - x: " + anchorPosition.x + ", y: " + anchorPosition.y);
Log.d(TAG, "Drag width: " + mTabSizeInPx + ", height: " + mTabSizeInPx);
// TODO: should create a method specifically for placing the drag bounds.
mWindowDragWatcher.deactivate();
mWindowDragWatcher.activate(mDragListener, new Rect(
anchorPosition.x,
@@ -548,7 +544,6 @@ public class HoverMenuView extends RelativeLayout {
// Select the first tab.
setActiveTab(mTabIds.get(0));
// TODO: we might set an adapter while expanded - track down the need for this and change it.
// We force a collapse because at this point we're in a weird initial state.
doCollapse(true);
}
@@ -605,7 +600,6 @@ public class HoverMenuView extends RelativeLayout {
return;
}
// TODO: how can we avoid this null check?
if (null != mActiveTab) {
Log.d(TAG, "Adjusting tab position due to layout change.");
getCollapsedMenuViewController().onDragTo(new Point(

View File

@@ -2,30 +2,27 @@ package com.stardust.scriptdroid;
import android.app.Activity;
import android.app.Application;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextPaint;
import android.util.Log;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
import com.stardust.scriptdroid.droid.script.RhinoJavaScriptEngine;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.squareup.leakcanary.LeakCanary;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.scriptdroid.tool.ViewTool;
import com.stardust.scriptdroid.ui.error.ErrorReportActivity;
import com.stardust.theme.ThemeColor;
import com.stardust.theme.ThemeColorManager;
import com.stardust.util.CrashHandler;
import com.stardust.util.StateObserver;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* Created by Stardust on 2017/1/27.
*/
@@ -34,12 +31,12 @@ public class App extends Application {
private static final String TAG = "App";
private static WeakReference<App> instance;
private static App instance;
private static StateObserver stateObserver;
private static WeakReference<Activity> currentActivity;
private static Activity currentActivity;
public static App getApp() {
return instance.get();
return instance;
}
public static StateObserver getStateObserver() {
@@ -49,26 +46,40 @@ public class App extends Application {
public void onCreate() {
super.onCreate();
ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent)));
ThemeColorManager.init(this);
setUpDebugEnvironment();
init();
configApp();
registerActivityLifecycleCallback();
initAccessibilityServiceDelegates();
}
private void setUpDebugEnvironment() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
if (!BuildConfig.DEBUG)
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(ErrorReportActivity.class));
instance = new WeakReference<>(this);
}
private void init() {
ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent)));
ThemeColorManager.init(this);
instance = this;
stateObserver = new StateObserver(PreferenceManager.getDefaultSharedPreferences(this));
registerActivityLifecycleCallback();
initAccessibilityServiceDelegates();
}
private void configApp() {
ScriptFileList.setImpl(SharedPrefScriptFileList.getInstance());
JavaScriptEngine.setDefault(new RhinoJavaScriptEngine(DroidRuntime.getRuntime()));
}
private void initAccessibilityServiceDelegates() {
AccessibilityWatchDogService.addDelegateIfNeeded(100, ActionPerformAccessibilityDelegate.class);
AccessibilityWatchDogService.addDelegateIfNeeded(200, AccessibilityRecorderDelegate.getInstance());
AccessibilityWatchDogService.addDelegateIfNeeded(300, AccessibilityEventCommandHost.instance);
AccessibilityWatchDogService.addDelegateIfNeeded(400, AccessibilityInfoProvider.instance);
AccessibilityWatchDogService.addDelegateIfNeeded(200, AccessibilityActionRecorder.getInstance());
AccessibilityWatchDogService.addDelegateIfNeeded(300, AccessibilityEventCommandHost.getInstance());
AccessibilityWatchDogService.addDelegateIfNeeded(400, AccessibilityInfoProvider.getInstance());
AccessibilityWatchDogService.addDelegateIfNeeded(500, LayoutInspector.getInstance());
}
@@ -77,7 +88,7 @@ public class App extends Application {
registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
currentActivity = new WeakReference<>(activity);
currentActivity = activity;
}
@@ -88,14 +99,19 @@ public class App extends Application {
@Override
public void onActivityResumed(Activity activity) {
currentActivity = new WeakReference<>(activity);
currentActivity = activity;
}
@Override
public void onActivityStopped(Activity activity) {
currentActivity = null;
}
});
}
public static Activity currentActivity() {
return currentActivity.get();
return currentActivity;
}
public static String getResString(int id) {

View File

@@ -0,0 +1,33 @@
package com.stardust.scriptdroid.accessibility;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.scriptdroid.R;
import com.stardust.util.SparseArrayEntries;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
/**
* Created by Stardust on 2017/3/16.
*/
public class AccessibilityEventHelper {
private static final SparseArray<Integer> EVENT_ACTION_RES_ID_MAP = new SparseArrayEntries<Integer>()
.entry(TYPE_VIEW_CLICKED, R.string.text_clicked)
.entry(TYPE_VIEW_LONG_CLICKED, R.string.text_long_clicked)
.entry(TYPE_VIEW_SCROLLED, R.string.text_scrolled)
.entry(TYPE_VIEW_TEXT_CHANGED, R.string.text_text_changed)
.sparseArray();
public static int getEventTypeNameResId(AccessibilityEvent event) {
return EVENT_ACTION_RES_ID_MAP.get(event.getEventType());
}
}

View File

@@ -15,11 +15,15 @@ import com.stardust.view.accessibility.AccessibilityDelegate;
public class AccessibilityInfoProvider implements AccessibilityDelegate {
public static AccessibilityInfoProvider instance = new AccessibilityInfoProvider();
private static AccessibilityInfoProvider instance = new AccessibilityInfoProvider();
private String mLatestPackage = "";
private String mLatestActivity = "";
public static AccessibilityInfoProvider getInstance() {
return instance;
}
public synchronized String getLatestPackage() {
return mLatestPackage;
}

View File

@@ -1,6 +1,7 @@
package com.stardust.scriptdroid.droid;
import android.support.annotation.NonNull;
import android.util.Log;
import com.jraska.console.timber.ConsoleTree;
import com.stardust.scriptdroid.App;
@@ -15,6 +16,9 @@ import com.stardust.scriptdroid.tool.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import timber.log.Timber;
@@ -26,12 +30,6 @@ public class Droid {
public static final java.lang.String STAY = "\"stay\";";
static {
Timber.plant(new ConsoleTree.Builder()
.infoColor(0xcc000000)
.build());
}
public static final String UI = "\"ui\";";
public interface OnRunFinishedListener extends Serializable {
@@ -54,13 +52,13 @@ public class Droid {
}
private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime();
public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = new RhinoJavaScriptEngine(RUNTIME);
public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = JavaScriptEngine.getDefault();
private static final OnRunFinishedListener DEFAULT_LISTENER = new SimpleOnRunFinishedListener() {
@Override
public void onException(@NonNull Exception e) {
if (!(e instanceof ScriptStopException)) {
if (!causedByInterruptedException(e)) {
RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage());
Timber.e(e, App.getApp().getString(R.string.text_error));
Timber.e(e.getMessage());
}
}
};
@@ -73,12 +71,22 @@ public class Droid {
return instance;
}
public static boolean causedByInterruptedException(Throwable e) {
while (e != null) {
if (e instanceof InterruptedException) {
return true;
}
e = e.getCause();
}
return false;
}
public void runScriptFile(File file) {
runScriptFile(file, null);
}
public void runScriptFile(File file, OnRunFinishedListener listener) {
Timber.v("开始运行脚本文件: " + file);
Timber.v(DateFormat.getTimeInstance().format(new Date()) + "/" + App.getResString(R.string.text_start_running) + file);
listener = listener == null ? DEFAULT_LISTENER : listener;
try {
checkFile(file);

View File

@@ -6,15 +6,23 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Environment;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.jraska.console.timber.ConsoleTree;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
@@ -43,6 +51,18 @@ import timber.log.Timber;
public class DroidRuntime {
static {
Timber.plant(new ConsoleTree.Builder()
.minPriority(Log.VERBOSE)
.verboseColor(0xff909090)
.debugColor(0xffc88b48)
.infoColor(0xffc9c9c9)
.warnColor(0xffa97db6)
.errorColor(0xffff534e)
.assertColor(0xffff5540)
.build());
}
private static final String TAG = "DroidRuntime";
private static DroidRuntime runtime = new DroidRuntime();
@@ -154,8 +174,11 @@ public class DroidRuntime {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
}
public void log(@Nullable Object str) {
Timber.i("" + str);
public void log(@Nullable Object o) {
String str = o + "";
SpannableString spannableString = new SpannableString(str);
spannableString.setSpan(new ForegroundColorSpan(Color.BLACK), 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Console.writeLine(spannableString);
}
public void err(@Nullable Object o) {
@@ -184,7 +207,7 @@ public class DroidRuntime {
return (T) action.getResult();
}
private void ensureAccessibilityServiceEnable() {
public void ensureAccessibilityServiceEnable() {
if (AccessibilityWatchDogService.getInstance() == null) {
String errorMessage = null;
if (AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class)) {
@@ -232,14 +255,14 @@ public class DroidRuntime {
return Shell.execCommand(cmd, root != 0);
}
public String getPackageName() {
public String currentPackage() {
ensureAccessibilityServiceEnable();
return ActionPerformAccessibilityDelegate.getLatestPackage();
return AccessibilityInfoProvider.getInstance().getLatestPackage();
}
public String getActivityName() {
public String currentActivity() {
ensureAccessibilityServiceEnable();
return ActionPerformAccessibilityDelegate.getLatestActivity();
return AccessibilityInfoProvider.getInstance().getLatestActivity();
}
public UiSelector selector() {

View File

@@ -69,8 +69,9 @@ public class UiSelector extends UiGlobalSelector {
private static final String TAG = "UiSelector";
public UiObjectCollection find() {
DroidRuntime.getRuntime().ensureAccessibilityServiceEnable();
FindCommand command = new FindCommand();
AccessibilityEventCommandHost.instance.executeAndWaitForEvent(command);
AccessibilityEventCommandHost.getInstance().executeAndWaitForEvent(command);
return command.result;
}
@@ -85,11 +86,7 @@ public class UiSelector extends UiGlobalSelector {
}
public UiObject findOne() {
UiObjectCollection collection = find();
if (collection == null || collection.size() == 0) {
return null;
}
return new UiObject(collection.get(0).getInfo());
return untilFindOne();
}
@NonNull
@@ -103,7 +100,7 @@ public class UiSelector extends UiGlobalSelector {
addFilter(new DfsFilter() {
@Override
protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) {
String fullId = AccessibilityInfoProvider.instance.getLatestPackage() + ":id/" + id;
String fullId = AccessibilityInfoProvider.getInstance().getLatestPackage() + ":id/" + id;
return fullId.equals(nodeInfo.getViewIdResourceName());
}
});

View File

@@ -23,28 +23,19 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate
public static final Action NO_ACTION = null;
private static final Object ACTION_LOCK = new Object();
private static Action action;
private static String latestPackage, latestActivity;
public static void setAction(Action action) {
synchronized (ActionPerformAccessibilityDelegate.class) {
synchronized (ACTION_LOCK) {
ActionPerformAccessibilityDelegate.action = action;
}
}
public synchronized static String getLatestPackage() {
return latestPackage;
}
public synchronized static String getLatestActivity() {
return latestActivity;
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
synchronized (ActionPerformAccessibilityDelegate.class) {
setLatestComponent(event.getPackageName(), event.getClassName());
synchronized (ACTION_LOCK) {
if (action == NO_ACTION)
return false;
AccessibilityNodeInfo root = service.getRootInActiveWindow();
@@ -66,26 +57,12 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate
private void onActionPerformed() {
synchronized (ActionPerformAccessibilityDelegate.class) {
synchronized (ACTION_LOCK) {
action = NO_ACTION;
DroidRuntime.getRuntime().notifyActionPerformed();
}
}
private static void setLatestComponent(CharSequence latestPackage, CharSequence latestClass) {
if (latestPackage == null)
return;
ActionPerformAccessibilityDelegate.latestPackage = latestPackage.toString();
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 ignored) {
}
}
}

View File

@@ -14,6 +14,16 @@ import java.util.Map;
public abstract class JavaScriptEngine {
private static JavaScriptEngine defaultEngine;
public static JavaScriptEngine getDefault() {
return defaultEngine;
}
public static void setDefault(JavaScriptEngine engine) {
defaultEngine = engine;
}
Map<String, Object> mVariableMap = new HashMap<>();
public abstract Object execute(String script);
@@ -26,6 +36,8 @@ public abstract class JavaScriptEngine {
public abstract int stopAll();
public abstract String[] getGlobalFunctions();
static class Init {
private static final String INIT_SCRIPT = readInitScript();

View File

@@ -26,6 +26,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
}
private final Set<Thread> mThreads = new HashSet<>();
private String[] mFunctions;
public RhinoJavaScriptEngine(DroidRuntime runtime) {
set("droid", runtime);
@@ -51,6 +52,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
private Context createContext() {
Context context = Context.enter();
context.setOptimizationLevel(-1);
context.setLanguageVersion(Context.VERSION_1_7);
context.setInstructionObserverThreshold(10000);
return context;
}
@@ -80,6 +82,24 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
return n;
}
@Override
public String[] getGlobalFunctions() {
if (mFunctions == null)
mFunctions = getGlobalFunctionsInner();
return mFunctions;
}
private String[] getGlobalFunctionsInner() {
Scriptable scriptable = (Scriptable) execute("this");
Object[] ids = scriptable.getIds();
String[] functions = new String[ids.length];
for (int i = 0; i < ids.length; i++) {
functions[i] = ids[i].toString();
}
return functions;
}
@Override
public void removeAndDestroy() {
synchronized (mThreads) {
@@ -87,6 +107,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
}
}
public static class InterruptibleContextFactory extends ContextFactory {
@Override

View File

@@ -8,6 +8,16 @@ import java.io.File;
public abstract class ScriptFileList {
private static ScriptFileList impl;
public static void setImpl(ScriptFileList list) {
impl = list;
}
public static ScriptFileList getImpl() {
return impl;
}
public abstract void add(ScriptFile scriptFile);
public abstract ScriptFile get(int i);
@@ -24,6 +34,6 @@ public abstract class ScriptFileList {
return file.delete();
}
public abstract boolean containsPath(String path);
}

View File

@@ -33,9 +33,11 @@ public class FloatingWindowManger {
Intent intent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + packageName));
Uri.parse("package:" + packageName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + packageName));
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + packageName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
App.getApp().startActivity(intent);
}

View File

@@ -37,11 +37,9 @@ import io.mattcarroll.hover.NavigatorContent;
public class HoverMenuAdapter implements io.mattcarroll.hover.HoverMenuAdapter {
public static final String ID_MAIN = "intro";
public static final String ID_MAIN = "main";
public static final String ID_RECORD = "record";
public static final String ID_SCRIPT_LIST = "script_list";
public static final String MENU_ID = "menu";
public static final String PLACEHOLDER_ID = "placeholder";
private final Context mContext;
private final List<String> mTabIds;

View File

@@ -7,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
@@ -50,8 +51,10 @@ public class HoverMenuService extends Service {
public static final String MESSAGE_SHOW_LAYOUT_BOUNDS = "MESSAGE_SHOW_LAYOUT_BOUNDS";
public static final String MESSAGE_COLLAPSE_MENU = "MESSAGE_COLLAPSE_MENU";
public static final String MESSAGE_MENU_COLLAPSING = "MESSAGE_MENU_COLLAPSING";
public static final String MESSAGE_MENU_EXPANDING = "MESSAGE_MENU_EXPANDING";
public static final String MESSAGE_MENU_EXIT = "MESSAGE_MENU_EXIT";
private static WeakReference<HoverMenuService> service = new WeakReference<>(null);
private static HoverMenuService service = null;
private static boolean sIsRunning;
public static void startService(Context context) {
@@ -64,8 +67,8 @@ public class HoverMenuService extends Service {
}
public static void stopService() {
if (isServiceRunning() && service.get() != null) {
service.get().stopSelf();
if (isServiceRunning() && service != null) {
service.stopSelf();
setIsRunning(false);
}
}
@@ -92,6 +95,7 @@ public class HoverMenuService extends Service {
private HoverMenu.OnExitListener mWindowHoverMenuMenuExitListener = new HoverMenu.OnExitListener() {
@Override
public void onExitByUserRequest() {
EventBus.getDefault().post(new MessageEvent(MESSAGE_MENU_EXIT));
savePreferredLocation();
mWindowHoverMenu.hide();
stopSelf();
@@ -101,11 +105,11 @@ public class HoverMenuService extends Service {
@Override
public void onCreate() {
super.onCreate();
if (service.get() != null) {
if (service != null) {
stopSelf();
return;
}
service = new WeakReference<>(this);
service = this;
EventBus.getDefault().register(this);
mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
try {
@@ -127,7 +131,7 @@ public class HoverMenuService extends Service {
}
private void initWindowMenu() {
mWindowHoverMenu = (WindowHoverMenu) new HoverMenuBuilder(this)
mWindowHoverMenu = (WindowHoverMenu) new HoverMenuBuilder(new ContextThemeWrapper(this, R.style.AppTheme))
.displayWithinWindow()
.useAdapter(new HoverMenuAdapter(this))
.restoreVisualState(loadPreferredLocation())
@@ -138,6 +142,7 @@ public class HoverMenuService extends Service {
mWindowHoverMenu.setHoverMenuTransitionListener(new SimpleHoverMenuTransitionListener() {
@Override
public void onExpanding() {
EventBus.getDefault().post(new MessageEvent(MESSAGE_MENU_EXPANDING));
captureCurrentWindow();
}
@@ -166,17 +171,20 @@ public class HoverMenuService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mWindowHoverMenu.show();
if (mWindowHoverMenu != null)
mWindowHoverMenu.show();
return START_STICKY;
}
@Override
public void onDestroy() {
mWindowHoverMenu.hide();
EventBus.getDefault().unregister(this);
if (mWindowHoverMenu != null)
mWindowHoverMenu.hide();
if (EventBus.getDefault().isRegistered(this))
EventBus.getDefault().unregister(this);
setIsRunning(false);
if (service.get() == this)
service.clear();
if (service == this)
service = null;
}

View File

@@ -1,21 +1,28 @@
package com.stardust.scriptdroid.external.floating_window.content;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.tool.ClipboardTool;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.util.MessageEvent;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,11 +43,19 @@ public class MainMenuNavigatorContent implements NavigatorContent {
private View mView;
@ViewBinding.Id(R.id.current_package)
private TextView mCurrentPackageTextView;
@ViewBinding.Id(R.id.current_activity)
private TextView mCurrentActivityTextView;
private String mCurrentPackage, mCurrentActivity;
private Context mContext;
public MainMenuNavigatorContent(Context context) {
mContext = context;
mView = View.inflate(context, R.layout.floating_window_main_menu, null);
ViewBinder.bind(this);
EventBus.getDefault().register(this);
}
@ViewBinding.Click(R.id.layout_hierarchy)
@@ -70,6 +85,13 @@ public class MainMenuNavigatorContent implements NavigatorContent {
Toast.makeText(mView.getContext(), R.string.text_no_running_script, Toast.LENGTH_SHORT).show();
}
@ViewBinding.Click(R.id.open_launcher)
private void openMainActivity() {
App.getApp().startActivity(new Intent(App.getApp(), MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK));
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
@NonNull
@Override
public View getView() {
@@ -81,11 +103,40 @@ public class MainMenuNavigatorContent implements NavigatorContent {
}
@SuppressLint("SetTextI18n")
private void syncCurrentInfo() {
mCurrentPackage = AccessibilityInfoProvider.getInstance().getLatestPackage();
mCurrentActivity = AccessibilityInfoProvider.getInstance().getLatestActivity();
mCurrentActivityTextView.setText(mContext.getString(R.string.text_current_activity) + mCurrentActivity);
mCurrentPackageTextView.setText(mContext.getString(R.string.text_current_package) + mCurrentPackage);
}
@ViewBinding.Click(R.id.current_activity)
private void copyCurrentActivity() {
ClipboardTool.setClip(mCurrentActivity);
Toast.makeText(mContext, R.string.text_copied, Toast.LENGTH_SHORT).show();
}
@ViewBinding.Click(R.id.current_package)
private void copyCurrentPackage() {
ClipboardTool.setClip(mCurrentPackage);
Toast.makeText(mContext, R.string.text_copied, Toast.LENGTH_SHORT).show();
}
@Override
public void onHidden() {
}
@Subscribe
public void onMessageEvent(MessageEvent event) {
if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXPANDING)) {
syncCurrentInfo();
} else if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXIT)) {
EventBus.getDefault().unregister(this);
}
}
public View findViewById(int id) {
return mView.findViewById(id);
}

View File

@@ -2,24 +2,61 @@ package com.stardust.scriptdroid.external.floating_window.content;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.SwitchCompat;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.accessibility.AccessibilityEventHelper;
import com.stardust.scriptdroid.external.floating_window.HoverMenuService;
import com.stardust.scriptdroid.record.Recorder;
import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder;
import com.stardust.scriptdroid.record.inputevent.InputEventConverter;
import com.stardust.scriptdroid.record.inputevent.TouchRecorder;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.util.MessageEvent;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import io.mattcarroll.hover.Navigator;
import io.mattcarroll.hover.NavigatorContent;
/**
* Created by Stardust on 2017/3/12.
*/
public class RecordNavigatorContent implements NavigatorContent {
public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStateChangedListener {
private View mView;
@ViewBinding.Id(R.id.sw_recorded_by_root)
private SwitchCompat mRecordedByRootSwitch;
@ViewBinding.Id(R.id.sw_record_toast)
private SwitchCompat mRecordToastSwitch;
@ViewBinding.Id(R.id.img_start_or_pause)
private ImageView mStartOrPauseRecordIcon;
@ViewBinding.Id(R.id.text_start_or_pause)
private TextView mStartOrPauseRecordText;
@ViewBinding.Id(R.id.stop_record)
private View mStopRecord;
private Recorder mRecorder;
private Context mContext;
public RecordNavigatorContent(Context context) {
mView = new View(context);
mContext = context;
mView = View.inflate(context, R.layout.floating_window_record, null);
ViewBinder.bind(this);
}
@NonNull
@@ -30,11 +67,118 @@ public class RecordNavigatorContent implements NavigatorContent {
@Override
public void onShown(@NonNull Navigator navigator) {
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
}
@Override
public void onHidden() {
if (EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this);
}
}
@ViewBinding.Click(R.id.sw_root_container)
private void toggleRecordedByRootSwitch() {
mRecordedByRootSwitch.toggle();
}
@ViewBinding.Click(R.id.sw_record_toast_container)
private void toggleRecordToastSwitch() {
mRecordToastSwitch.toggle();
}
@ViewBinding.Click(R.id.start_or_pause)
private void startOrPauseRecord() {
if (mRecorder == null) {
startRecord();
} else if (mRecorder.getState() == Recorder.STATE_PAUSED) {
resumeRecord();
} else {
pauseRecord();
}
}
private void resumeRecord() {
mRecorder.resume();
setState(Recorder.STATE_RECORDING);
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
private void pauseRecord() {
mRecorder.pause();
setState(Recorder.STATE_PAUSED);
}
private void startRecord() {
mRecorder = mRecordedByRootSwitch.isChecked() ? new TouchRecorder() : AccessibilityActionRecorder.getInstance();
AccessibilityActionRecorder.getInstance().setRecordMessageEnabled(mRecordToastSwitch.isChecked());
mRecorder.setOnStateChangedListener(this);
mRecorder.start();
setState(Recorder.STATE_RECORDING);
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
private void setState(int state) {
mStopRecord.setVisibility(state == Recorder.STATE_STOPPED ? View.GONE : View.VISIBLE);
mStartOrPauseRecordIcon.setImageResource(state == Recorder.STATE_RECORDING ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_48dp);
//我知道这样写代码会被打 但我懒...
mStartOrPauseRecordText.setText(state == Recorder.STATE_RECORDING ? R.string.text_pause_record :
state == Recorder.STATE_PAUSED ? R.string.text_resume_record : R.string.text_start_record);
}
@ViewBinding.Click(R.id.stop_record)
private void stopRecord() {
mRecorder.stop();
setState(Recorder.STATE_STOPPED);
EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU));
}
public View findViewById(int id) {
return mView.findViewById(id);
}
@Subscribe
public void onMessageEvent(MessageEvent event) {
if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXPANDING)) {
pauseRecord();
}
}
@Subscribe
public void onAccessibilityActionRecordEvent(AccessibilityActionRecorder.AccessibilityActionRecordEvent event) {
if (mRecordToastSwitch.isChecked()) {
Toast.makeText(mContext, AccessibilityEventHelper.getEventTypeNameResId(event.getAccessibilityEvent()), Toast.LENGTH_SHORT).show();
}
}
@Subscribe
public void onTouchRecorderStateChanged(InputEventConverter.RecordStateChangeEvent event) {
if (!event.isRecording()) {
stopRecord();
}
}
@Override
public void onStart() {
Toast.makeText(mContext, R.string.text_start_record, Toast.LENGTH_SHORT).show();
}
@Override
public void onStop() {
MainActivity.onActionRecordStopped(mContext, mRecorder.getCode());
mRecorder = null;
}
@Override
public void onPause() {
}
@Override
public void onResume() {
}
}

View File

@@ -6,6 +6,7 @@ import android.view.ContextThemeWrapper;
import android.view.View;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.external.floating_window.view.FloatingScriptFileListView;
@@ -22,7 +23,7 @@ public class ScritpListNavigatorContent implements NavigatorContent {
public ScritpListNavigatorContent(Context context) {
mFloatingScriptFileListView = new FloatingScriptFileListView(new ContextThemeWrapper(context, R.style.AppTheme));
mFloatingScriptFileListView.setScriptFileList(SharedPrefScriptFileList.getInstance());
mFloatingScriptFileListView.setScriptFileList(ScriptFileList.getImpl());
}
@NonNull

View File

@@ -0,0 +1,171 @@
package com.stardust.scriptdroid.external.notification.record;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.record.Recorder;
import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.service.VolumeChangeObverseService;
import com.stardust.scriptdroid.ui.main.MainActivity;
/**
* Created by Stardust on 2017/2/14.
*/
public class AccessibilityActionRecordNotification {
private static final String INTENT_ACTION = " com.stardust.scriptdroid.Record";
private static final String EXTRA_ACTION = "action";
private static final int ACTION_STOP = 17771;
private static final int ACTION_START_OR_PAUSE = 17772;
private static final int ACTION_DELETE = 17773;
private static final int ACTION_REDO = 17774;
private static final int NOTIFY_ID = 22236;
private static NotificationCompat.Builder builder;
private static BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int action = intent.getIntExtra(EXTRA_ACTION, 0);
switch (action) {
case ACTION_STOP:
stopRecord(context);
collapseNotificationBar(context);
break;
case ACTION_START_OR_PAUSE:
startOrPauseRecord(context);
collapseNotificationBar(context);
break;
case ACTION_DELETE:
AccessibilityActionRecordNotification.cancelNotification();
stopRecordIfNeeded(context);
VolumeChangeObverseService.stopServiceIfNeeded();
break;
case ACTION_REDO:
redoRecord(context);
break;
}
}
};
static PendingIntent getStartOrPauseIntent() {
Intent intent = new Intent(INTENT_ACTION)
.putExtra(EXTRA_ACTION, ACTION_START_OR_PAUSE);
return PendingIntent.getBroadcast(App.getApp(), ACTION_START_OR_PAUSE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
static PendingIntent getStopIntent() {
Intent intent = new Intent(INTENT_ACTION)
.putExtra(EXTRA_ACTION, ACTION_STOP);
return PendingIntent.getBroadcast(App.getApp(), ACTION_STOP, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
static PendingIntent getDeleteIntent() {
Intent intent = new Intent(INTENT_ACTION)
.putExtra(EXTRA_ACTION, ACTION_DELETE);
return PendingIntent.getBroadcast(App.getApp(), ACTION_DELETE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
static PendingIntent getRedoIntent() {
Intent intent = new Intent(INTENT_ACTION)
.putExtra(EXTRA_ACTION, ACTION_REDO);
return PendingIntent.getService(App.getApp(), ACTION_REDO, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static void showOrUpdateNotification() {
if (builder == null) {
builder = new NotificationCompat.Builder(App.getApp())
.setAutoCancel(false)
.setSmallIcon(R.drawable.ic_robot_head)
.setDeleteIntent(getDeleteIntent())
.setCustomContentView(AccessibilityActionRecordSwitchView.getInstance());
}
showNotification(builder.build());
VolumeChangeObverseService.startServiceIfNeeded(App.getApp());
}
private static void showNotification(Notification notification) {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFY_ID, notification);
App.getApp().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION));
}
public static void cancelNotification() {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFY_ID);
App.getApp().unregisterReceiver(broadcastReceiver);
}
private static void collapseNotificationBar(Context context) {
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
private static void stopRecordIfNeeded(Context context) {
int state = AccessibilityActionRecorder.getInstance().getState();
if (state == Recorder.STATE_RECORDING || state == Recorder.STATE_PAUSED) {
stopRecord(context);
}
}
private static void startOrPauseRecord(Context context) {
int state = AccessibilityActionRecorder.getInstance().getState();
if (state == Recorder.STATE_PAUSED) {
resumeRecord(context);
} else if (state == Recorder.STATE_RECORDING) {
pauseRecord(context);
} else {
//state == STOPPED
startRecord(context);
}
}
public static void startRecord(Context context) {
if (AccessibilityWatchDogService.getInstance() == null) {
Toast.makeText(context, R.string.text_need_enable_accessibility_service, Toast.LENGTH_SHORT).show();
return;
}
AccessibilityActionRecorder.getInstance().start();
AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_RECORDING);
Toast.makeText(context, R.string.text_start_record, Toast.LENGTH_SHORT).show();
}
private static void pauseRecord(Context context) {
AccessibilityActionRecorder.getInstance().pause();
AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_PAUSED);
}
private static void resumeRecord(Context context) {
AccessibilityActionRecorder.getInstance().resume();
AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_RECORDING);
}
public static void stopRecord(Context context) {
if (AccessibilityActionRecorder.getInstance().getState() != Recorder.STATE_STOPPED) {
AccessibilityActionRecorder.getInstance().stop();
String script = AccessibilityActionRecorder.getInstance().getCode();
AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_STOPPED);
MainActivity.onActionRecordStopped(context, script);
} else {
Toast.makeText(App.getApp(), R.string.text_not_recording, Toast.LENGTH_SHORT).show();
}
}
private static void redoRecord(Context context) {
if (AccessibilityActionRecorder.getInstance().getState() != Recorder.STATE_STOPPED) {
AccessibilityActionRecorder.getInstance().stop();
AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_STOPPED);
}
}
}

View File

@@ -4,53 +4,50 @@ import android.widget.RemoteViews;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.record.Recorder;
/**
* Created by Stardust on 2017/2/14.
*/
public class ActionRecordSwitchView extends RemoteViews {
public class AccessibilityActionRecordSwitchView extends RemoteViews {
private static ActionRecordSwitchView instance;
private static AccessibilityActionRecordSwitchView instance;
public static final int STOPPED = 0;
public static final int PAUSED = 1;
public static final int RECORDING = 2;
public static ActionRecordSwitchView getInstance() {
public static AccessibilityActionRecordSwitchView getInstance() {
if (instance == null) {
instance = new ActionRecordSwitchView();
instance = new AccessibilityActionRecordSwitchView();
}
return instance;
}
private ActionRecordSwitchView() {
private AccessibilityActionRecordSwitchView() {
super(App.getApp().getPackageName(), R.layout.remote_views_record_switch);
setUpOnClick();
}
public void setState(int state) {
switch (state) {
case STOPPED:
case Recorder.STATE_STOPPED:
setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_play_arrow_grey600_48dp);
setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_start_record));
break;
case RECORDING:
case Recorder.STATE_RECORDING:
setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_pause_grey600_48dp);
setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_pause_record));
break;
case PAUSED:
case Recorder.STATE_PAUSED:
setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_play_arrow_grey600_48dp);
setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_resume_record));
break;
}
ActionRecordSwitchNotification.showOrUpdateNotification();
AccessibilityActionRecordNotification.showOrUpdateNotification();
}
private void setUpOnClick() {
setOnClickPendingIntent(R.id.stop, ActionRecordSwitchHandleService.getStopIntent());
setOnClickPendingIntent(R.id.start_or_pause, ActionRecordSwitchHandleService.getStartOrPauseIntent());
setOnClickPendingIntent(R.id.close, ActionRecordSwitchHandleService.getDeleteIntent());
setOnClickPendingIntent(R.id.redo, ActionRecordSwitchHandleService.getRedoIntent());
setOnClickPendingIntent(R.id.stop, AccessibilityActionRecordNotification.getStopIntent());
setOnClickPendingIntent(R.id.start_or_pause, AccessibilityActionRecordNotification.getStartOrPauseIntent());
setOnClickPendingIntent(R.id.close, AccessibilityActionRecordNotification.getDeleteIntent());
setOnClickPendingIntent(R.id.redo, AccessibilityActionRecordNotification.getRedoIntent());
}
}

View File

@@ -1,164 +0,0 @@
package com.stardust.scriptdroid.external.notification.record;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.widget.Toast;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.service.VolumeChangeListenService;
import com.stardust.scriptdroid.ui.main.MainActivity;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.PAUSED;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.RECORDING;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED;
/**
* Created by Stardust on 2017/2/15.
*/
public class ActionRecordSwitchHandleService extends Service {
private static final String EXTRA_INTENT_VALID = "ActionRecordSwitchHandleService.intentValid";
private static final String EXTRA_ACTION = "action";
private static final int ACTION_STOP = 17771;
private static final int ACTION_START_OR_PAUSE = 17772;
private static final int ACTION_DELETE = 17773;
private static final int ACTION_REDO = 17774;
public static PendingIntent getStartOrPauseIntent() {
Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_START_OR_PAUSE);
return PendingIntent.getService(App.getApp(), ACTION_START_OR_PAUSE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static PendingIntent getStopIntent() {
Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_STOP);
return PendingIntent.getService(App.getApp(), ACTION_STOP, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static PendingIntent getDeleteIntent() {
Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_DELETE);
return PendingIntent.getService(App.getApp(), ACTION_DELETE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
public static PendingIntent getRedoIntent() {
Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class)
.putExtra(EXTRA_INTENT_VALID, true)
.putExtra(EXTRA_ACTION, ACTION_REDO);
return PendingIntent.getService(App.getApp(), ACTION_REDO, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
boolean intentValid = intent.getBooleanExtra(EXTRA_INTENT_VALID, false);
if (intentValid) {
performAction(intent.getIntExtra(EXTRA_ACTION, 0));
}
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
private void performAction(int action) {
switch (action) {
case ACTION_STOP:
stopRecord(this);
collapseNotificationBar();
break;
case ACTION_START_OR_PAUSE:
startOrPauseRecord(this);
collapseNotificationBar();
break;
case ACTION_DELETE:
ActionRecordSwitchNotification.cancelNotification();
stopRecordIfNeeded();
VolumeChangeListenService.stopServiceIfNeeded();
break;
case ACTION_REDO:
redoRecord(this);
break;
}
}
private void collapseNotificationBar() {
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
private void stopRecordIfNeeded() {
if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) {
stopRecord(this);
}
}
private static void startOrPauseRecord(Context context) {
int state = AccessibilityRecorderDelegate.getInstance().getState();
if (state == PAUSED) {
resumeRecord(context);
} else if (state == RECORDING) {
pauseRecord(context);
} else {
//state == STOPPED
startRecord(context);
}
}
public static void startRecord(Context context) {
if (AccessibilityWatchDogService.getInstance() == null) {
Toast.makeText(context, R.string.text_need_enable_accessibility_service, Toast.LENGTH_SHORT).show();
return;
}
AccessibilityRecorderDelegate.getInstance().startRecord();
ActionRecordSwitchView.getInstance().setState(RECORDING);
Toast.makeText(context, R.string.text_start_record, Toast.LENGTH_SHORT).show();
}
private static void pauseRecord(Context context) {
AccessibilityRecorderDelegate.getInstance().pauseRecord();
ActionRecordSwitchView.getInstance().setState(PAUSED);
}
private static void resumeRecord(Context context) {
AccessibilityRecorderDelegate.getInstance().resumeRecord();
ActionRecordSwitchView.getInstance().setState(RECORDING);
}
public static void stopRecord(Context context) {
if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) {
String script = AccessibilityRecorderDelegate.getInstance().stopRecord();
ActionRecordSwitchView.getInstance().setState(STOPPED);
MainActivity.onActionRecordStopped(context, script);
} else {
Toast.makeText(App.getApp(), R.string.text_not_recording, Toast.LENGTH_SHORT).show();
}
}
private static void redoRecord(Context context) {
if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) {
AccessibilityRecorderDelegate.getInstance().stopRecord();
ActionRecordSwitchView.getInstance().setState(STOPPED);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@@ -1,43 +0,0 @@
package com.stardust.scriptdroid.external.notification.record;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.support.v4.app.NotificationCompat;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.service.VolumeChangeListenService;
/**
* Created by Stardust on 2017/2/14.
*/
public class ActionRecordSwitchNotification {
private static final int NOTIFY_ID = 22236;
private static NotificationCompat.Builder builder;
public static void showOrUpdateNotification() {
if (builder == null) {
builder = new NotificationCompat.Builder(App.getApp())
.setAutoCancel(false)
.setSmallIcon(R.drawable.ic_robot_head)
.setDeleteIntent(ActionRecordSwitchHandleService.getDeleteIntent())
.setCustomContentView(ActionRecordSwitchView.getInstance());
}
showNotification(builder.build());
VolumeChangeListenService.startServiceIfNeeded(App.getApp());
}
private static void showNotification(Notification notification) {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFY_ID, notification);
}
public static void cancelNotification() {
NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFY_ID);
}
}

View File

@@ -9,7 +9,9 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.tool.FileUtils;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
@@ -31,8 +33,8 @@ public class ImportIntentActivity extends BaseActivity {
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, R.string.edit_and_run_handle_intent_error, Toast.LENGTH_LONG).show();
finish();
}
finish();
}
private void handleIntent() {
@@ -41,19 +43,22 @@ public class ImportIntentActivity extends BaseActivity {
if (!TextUtils.isEmpty(path)) {
new ThemeColorMaterialDialogBuilder(this)
.title(R.string.text_please_input_name)
.input(getString(R.string.text_name), new File(path).getName(), new MaterialDialog.InputCallback() {
.input(getString(R.string.text_name), FileUtils.getNameWithoutExtension(path), new MaterialDialog.InputCallback() {
@Override
public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {
SharedPrefScriptFileList.getInstance().add(new ScriptFile(input.toString(), path));
ScriptFileList.getImpl().add(new ScriptFile(input.toString(), path));
startMainActivity();
}
})
.show();
} else {
finish();
}
}
private void startMainActivity() {
startActivity(new Intent(this, MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
finish();
}
}

View File

@@ -1,95 +0,0 @@
package com.stardust.scriptdroid.record;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.view.accessibility.AccessibilityDelegate;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.PAUSED;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.RECORDING;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED;
/**
* Created by Stardust on 2017/2/14.
*/
public class AccessibilityRecorderDelegate implements AccessibilityDelegate {
private static final int PRIORITY = 200;
private static final long RECORD_TIME_OUT = 10 * 60 * 1000;
private static AccessibilityRecorderDelegate instance;
private int mState = STOPPED;
public static AccessibilityRecorderDelegate getInstance() {
if (instance == null) {
instance = new AccessibilityRecorderDelegate();
}
return instance;
}
private ActionRecorder mRecorder = new ActionRecorder();
private long mRecordStartMillis;
public void startRecord() {
if (mState != STOPPED) {
throw new IllegalStateException("Recording");
}
mState = RECORDING;
mRecorder = new ActionRecorder();
mRecordStartMillis = System.currentTimeMillis();
}
public String stopRecord() {
if (mState == STOPPED) {
throw new IllegalStateException("Not recording");
}
mState = STOPPED;
String script = mRecorder.getScript();
mRecorder = null;
return script;
}
public void pauseRecord() {
if (mState != RECORDING) {
throw new IllegalStateException("Not recording");
}
mState = PAUSED;
}
public void resumeRecord() {
if (mState != PAUSED) {
throw new IllegalStateException("Not paused");
}
mRecorder.onResume();
mState = RECORDING;
}
public void stopRecordIfNeeded() {
if (mRecorder != null) {
mRecorder = null;
}
}
public int getState() {
return mState;
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
if (mState == RECORDING) {
mRecorder.record(service, event);
checkTimeOut();
}
return false;
}
private void checkTimeOut() {
if (System.currentTimeMillis() - mRecordStartMillis > RECORD_TIME_OUT) {
stopRecord();
}
}
}

View File

@@ -0,0 +1,154 @@
package com.stardust.scriptdroid.record;
/**
* Created by Stardust on 2017/3/16.
*/
public interface Recorder {
interface OnStateChangedListener {
void onStart();
void onStop();
void onPause();
void onResume();
}
OnStateChangedListener NO_OPERATION_LISTENER = new OnStateChangedListener() {
@Override
public void onStart() {
}
@Override
public void onStop() {
}
@Override
public void onPause() {
}
@Override
public void onResume() {
}
};
int STATE_NOT_START = 0;
int STATE_RECORDING = 1;
int STATE_PAUSED = 2;
int STATE_STOPPED = 3;
void start();
void stop();
void pause();
void resume();
String getCode();
int getState();
void setOnStateChangedListener(OnStateChangedListener onStateChangedListener);
abstract class DefaultIMPL implements Recorder {
private OnStateChangedListener mOnStateChangedListener = NO_OPERATION_LISTENER;
private final boolean mSync;
private int mState = STATE_NOT_START;
public DefaultIMPL(boolean syncOfState) {
mSync = syncOfState;
}
public DefaultIMPL() {
this(false);
}
public void start() {
checkState(STATE_NOT_START);
setState(STATE_RECORDING);
startImpl();
mOnStateChangedListener.onStart();
}
private void checkState(int... expectedStates) {
for (int expectedState : expectedStates) {
if (mState == expectedState)
return;
}
throw new IllegalStateException();
}
protected abstract void startImpl();
public void stop() {
checkState(STATE_RECORDING, STATE_PAUSED);
setState(STATE_STOPPED);
stopImpl();
mOnStateChangedListener.onStop();
}
protected abstract void stopImpl();
public void pause() {
checkState(STATE_RECORDING);
setState(STATE_PAUSED);
pauseImpl();
mOnStateChangedListener.onPause();
}
protected synchronized void setState(int state) {
if (mSync) {
synchronized (this) {
mState = state;
}
} else {
mState = state;
}
}
public synchronized int getState() {
if (mSync) {
synchronized (this) {
return mState;
}
} else {
return mState;
}
}
protected void pauseImpl() {
}
public void resume() {
checkState(STATE_PAUSED);
setState(STATE_RECORDING);
resumeImpl();
mOnStateChangedListener.onResume();
}
protected void resumeImpl() {
}
public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) {
mOnStateChangedListener = onStateChangedListener == null ? NO_OPERATION_LISTENER : onStateChangedListener;
}
}
}

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.record;
package com.stardust.scriptdroid.record.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.os.Build;
@@ -11,6 +11,8 @@ import com.stardust.scriptdroid.layout_inspector.NodeInfo;
import com.stardust.util.SparseArrayEntries;
import com.stardust.view.accessibility.AccessibilityNodeInfoHelper;
import org.greenrobot.eventbus.EventBus;
import java.util.List;
@@ -18,7 +20,7 @@ import java.util.List;
* Created by Stardust on 2017/2/14.
*/
public class ActionRecorder {
public class AccessibilityActionConverter {
private static final SparseArray<EventToScriptConverter> CONVERTER_MAP = new SparseArrayEntries<EventToScriptConverter>()
.entry(AccessibilityEvent.TYPE_VIEW_CLICKED, new DoUtilSucceedConverter("click"))
@@ -36,15 +38,22 @@ public class ActionRecorder {
private StringBuilder mScript = new StringBuilder();
private boolean mFirstAction = true;
public AccessibilityActionConverter(boolean shouldIgnoreFirstAction) {
mShouldIgnoreFirstAction = shouldIgnoreFirstAction;
}
private boolean mShouldIgnoreFirstAction = false;
public void record(AccessibilityService service, AccessibilityEvent event) {
EventToScriptConverter converter = CONVERTER_MAP.get(event.getEventType());
if (converter != null) {
if (mFirstAction) {
if (mFirstAction && mShouldIgnoreFirstAction) {
mFirstAction = false;
return;
}
converter.onAccessibilityEvent(service, event, mScript);
mScript.append("\n");
EventBus.getDefault().post(new AccessibilityActionRecorder.AccessibilityActionRecordEvent(event));
}
}

View File

@@ -0,0 +1,95 @@
package com.stardust.scriptdroid.record.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.scriptdroid.record.Recorder;
import com.stardust.view.accessibility.AccessibilityDelegate;
/**
* Created by Stardust on 2017/2/14.
*/
public class AccessibilityActionRecorder extends Recorder.DefaultIMPL implements AccessibilityDelegate {
public static class AccessibilityActionRecordEvent {
private final AccessibilityEvent mAccessibilityEvent;
public AccessibilityActionRecordEvent(AccessibilityEvent event) {
mAccessibilityEvent = event;
}
public AccessibilityEvent getAccessibilityEvent() {
return mAccessibilityEvent;
}
}
private static final long RECORD_TIME_OUT = 10 * 60 * 1000;
private static AccessibilityActionRecorder instance;
private boolean mRecordMessageEnabled;
private boolean mShouldIgnoreFirstAction = false;
public static AccessibilityActionRecorder getInstance() {
if (instance == null) {
instance = new AccessibilityActionRecorder();
}
return instance;
}
private AccessibilityActionConverter mConverter;
private long mRecordStartMillis;
public AccessibilityActionRecorder(){
super(true);
}
@Override
protected void startImpl() {
mConverter = new AccessibilityActionConverter(mShouldIgnoreFirstAction);
mRecordStartMillis = System.currentTimeMillis();
}
@Override
protected void stopImpl() {
setState(STATE_NOT_START);
}
@Override
protected void resumeImpl() {
mConverter.onResume();
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
if (getState() == STATE_RECORDING) {
mConverter.record(service, event);
checkTimeOut();
}
return false;
}
private void checkTimeOut() {
if (System.currentTimeMillis() - mRecordStartMillis > RECORD_TIME_OUT) {
stop();
}
}
@Override
public String getCode() {
return mConverter.getScript();
}
public void setShouldIgnoreFirstAction(boolean shouldIgnoreFirstAction) {
mShouldIgnoreFirstAction = shouldIgnoreFirstAction;
}
public void setRecordMessageEnabled(boolean recordMessageEnabled) {
// TODO: 2017/3/16 添加录制提示
mRecordMessageEnabled = recordMessageEnabled;
}
}

View File

@@ -7,6 +7,8 @@ import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.ui.main.MainActivity;
import org.greenrobot.eventbus.EventBus;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -17,6 +19,21 @@ import java.util.regex.Pattern;
public abstract class InputEventConverter {
public static class RecordStateChangeEvent {
private boolean mIsRecording;
public RecordStateChangeEvent(boolean isRecording) {
this.mIsRecording = isRecording;
}
public boolean isRecording() {
return mIsRecording;
}
}
static class Event {
static final Pattern PATTERN = Pattern.compile("^\\[([^\\]]*)\\]\\s+([^:]*):\\s+([^\\s]*)\\s+([^\\s]*)\\s+([^\\s]*)\\s*$");
@@ -100,11 +117,11 @@ public abstract class InputEventConverter {
}
public void notifyRecordStopped() {
MainActivity.onRootRecordStopped(App.getApp());
EventBus.getDefault().post(new RecordStateChangeEvent(false));
}
public void notifyRecordStarted() {
DroidRuntime.getRuntime().toast(App.getApp().getString(R.string.text_start_record));
EventBus.getDefault().post(new RecordStateChangeEvent(true));
}
}

View File

@@ -3,10 +3,9 @@ package com.stardust.scriptdroid.record.inputevent;
import android.preference.PreferenceManager;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.record.Recorder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import jackpal.androidterm.ShellTermSession;
import jackpal.androidterm.emulatorview.TermSession;
@@ -16,9 +15,8 @@ import jackpal.androidterm.util.TermSettings;
* Created by Stardust on 2017/3/6.
*/
public abstract class InputEventRecorder {
public abstract class InputEventRecorder extends Recorder.DefaultIMPL {
private Thread mRecordThread;
private TermSession mTermSession;
private String mGetEventCommand;
protected InputEventConverter mInputEventConverter;
@@ -31,78 +29,61 @@ public abstract class InputEventRecorder {
public void listen() {
TermSettings settings = new TermSettings(App.getApp().getResources(), PreferenceManager.getDefaultSharedPreferences(App.getApp()));
try {
mTermSession = new ShellTermSession(settings, "su\r") {
boolean enter = false;
@Override
protected void processInput(byte[] data, int offset, int count) {
String[] lines = new String(data, offset, count).split("\n");
for (String line : lines) {
System.out.println(line);
if (!enter && line.endsWith("data # ")) {
mTermSession.write(mGetEventCommand + "\r");
enter = true;
} else if (enter) {
parseAndRecordEvent(line);
}
}
appendToEmulator(data, offset, count);
}
};
mTermSession = new MyShellTermSession(settings, "su\r");
mTermSession.initializeEmulator(80, 40);
mTermSession.write("su\r");
mRecordThread = new Thread(new Runnable() {
@Override
public void run() {
try {
//readOutput(mTermSession);
} catch (Exception e) {
e.printStackTrace();
}
}
});
mRecordThread.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void start() {
@Override
protected void startImpl() {
mInputEventConverter.start();
}
public void pause() {
@Override
protected void pauseImpl() {
mInputEventConverter.pause();
}
private void readOutput(TermSession shell) {
boolean enter = false;
String line;
try {
BufferedReader succeedReader = new BufferedReader(new InputStreamReader(mTermSession.getTermIn()));
while ((line = succeedReader.readLine()) != null) {
System.out.println(line);
if (!enter && line.endsWith("data # ")) {
mTermSession.write(mGetEventCommand + "\r");
enter = true;
} else if (enter) {
parseAndRecordEvent(line);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
@Override
protected void resumeImpl() {
mInputEventConverter.start();
}
protected abstract void parseAndRecordEvent(String eventStr);
public abstract String getCode();
public void stop() {
mRecordThread.interrupt();
@Override
protected void stopImpl() {
mTermSession.finish();
mInputEventConverter.stop();
}
protected abstract void parseAndRecordEvent(String eventStr);
private class MyShellTermSession extends ShellTermSession {
private boolean mGettingEvents = false;
public MyShellTermSession(TermSettings settings, String initialCommand) throws IOException {
super(settings, initialCommand);
}
@Override
protected void processInput(byte[] data, int offset, int count) {
String[] lines = new String(data, offset, count).split("\n");
for (String line : lines) {
System.out.println(line);
if (!mGettingEvents && line.endsWith("data # ")) {
mTermSession.write(mGetEventCommand + "\r");
mGettingEvents = true;
} else if (mGettingEvents) {
parseAndRecordEvent(line);
}
}
appendToEmulator(data, offset, count);
}
}
}

View File

@@ -13,7 +13,6 @@ import java.util.Map;
public class InputEventToJsConverter extends InputEventConverter {
interface EventHandler {
void handle(Event event);
}
@@ -22,10 +21,6 @@ public class InputEventToJsConverter extends InputEventConverter {
private Map<String, EventHandler> mEventHandlerMap = new HashMap<>();
EventHandler getHandler(String key) {
return mEventHandlerMap.get(key);
}
Router put(String key, EventHandler handler) {
mEventHandlerMap.put(key, handler);
return this;
@@ -112,6 +107,7 @@ public class InputEventToJsConverter extends InputEventConverter {
.entry("KEY_VOLUMEDOWN", "VolumeDown")
.entry("KEY_VOLUMEUP", "VolumeUp")
.entry("KEY_BACK", "Back")
.entry("KEY_CAMERA", "Camera")
.map();
@Override
@@ -124,10 +120,10 @@ public class InputEventToJsConverter extends InputEventConverter {
mSwipe = false;
}
} else if (event.value.equals("UP")) {
if (!mStarted && event.code.equals("KEY_VOLUMEUP")) {
if (!mStarted && event.code.equals(mStartTriggerKey)) {
mStarted = true;
notifyRecordStarted();
} else if (mStarted && event.code.equals("KEY_VOLUMEDOWN")) {
} else if (mStarted && event.code.equals(mStopTriggerKey)) {
mStarted = false;
notifyRecordStopped();
} else {
@@ -145,6 +141,7 @@ public class InputEventToJsConverter extends InputEventConverter {
}
private EventHandler mEventHandler = new TypeRouter();
private String mStartTriggerKey, mStopTriggerKey;
private StringBuilder mCode = new StringBuilder().append("var sh = new Shell(true);\n");
private Point mTouchPoint = new Point(), mLastTouchPoint = new Point();
private boolean mTouchDown = false, mSwipe = false;
@@ -166,5 +163,13 @@ public class InputEventToJsConverter extends InputEventConverter {
return mCode.toString();
}
public void setStartTriggerKey(String startTriggerKey) {
mStartTriggerKey = startTriggerKey;
}
public void setStopTriggerKey(String stopTriggerKey) {
mStopTriggerKey = stopTriggerKey;
}
}

View File

@@ -6,9 +6,11 @@ package com.stardust.scriptdroid.record.inputevent;
public class InputEventToJsRecorder extends InputEventRecorder {
private InputEventToJsConverter mInputEventToJsConverter;
public InputEventToJsRecorder() {
super("getevent -t -l", new InputEventToJsConverter());
mInputEventToJsConverter = (InputEventToJsConverter) mInputEventConverter;
}
@Override
@@ -20,4 +22,12 @@ public class InputEventToJsRecorder extends InputEventRecorder {
public String getCode() {
return mInputEventConverter.getCode();
}
public void setStartTriggerKey(String startTriggerKey) {
mInputEventToJsConverter.setStartTriggerKey(startTriggerKey);
}
public void setStopTriggerKey(String stopTriggerKey) {
mInputEventToJsConverter.setStopTriggerKey(stopTriggerKey);
}
}

View File

@@ -0,0 +1,26 @@
package com.stardust.scriptdroid.record.inputevent;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.record.Recorder;
/**
* Created by Stardust on 2017/3/16.
*/
public class TouchRecorder extends InputEventToJsRecorder {
public TouchRecorder() {
listen();
setUpTriggers();
}
private void setUpTriggers() {
setStartTriggerKey(Pref.def().getString(App.getResString(R.string.key_start_record_trigger), null));
setStopTriggerKey(Pref.def().getString(App.getResString(R.string.key_stop_record_trigger), null));
}
}

View File

@@ -23,15 +23,6 @@ import java.util.Map;
*/
public class SampleFileManager {
private static final Map<String, Integer> SAMPLES = new MapEntries<>(new LinkedHashMap<String, Integer>())
.entry("sample/自动操作/sample_open_running_services.js", R.string.text_sample_open_running_services)
.entry("sample/自动操作/sample_qq_hongbao.js", R.string.text_sample_qq_hongbao)
.entry("sample/自动操作/sample_simple_calculator.js", R.string.text_sample_simple_calculator)
.entry("sample/自动操作/sample_force_qq_chat.js", R.string.text_sample_for_qq_chat)
.entry("sample/自动操作/sample_wechat_scan.js", R.string.text_sample_wechat_scan)
.entry("sample/自动操作/sample_alipay_scan.js", R.string.text_sample_alipay_scan)
.map();
private static SampleFileManager instance = new SampleFileManager();
public static SampleFileManager getInstance() {
@@ -42,33 +33,6 @@ public class SampleFileManager {
return App.getApp();
}
public void copySampleScriptFileIfNeeded() {
if (!Pref.def().getBoolean(Pref.SAMPLE_SCRIPTS_COPIED, false)) {
copySampleScriptFile();
}
}
public int copySampleScriptFile() {
ScriptFileList list = SharedPrefScriptFileList.getInstance();
int failCount = 0;
for (Map.Entry<String, Integer> entry : SAMPLES.entrySet()) {
String assetFile = entry.getKey();
String name = getContext().getString(entry.getValue());
String path = ScriptFile.DEFAULT_FOLDER + name + ".js";
if (!list.containsPath(path)) {
if (FileUtils.copyAsset(assetFile, path)) {
list.add(new ScriptFile(name, path));
} else {
failCount++;
}
}
}
if (failCount < SAMPLES.size()) {
Pref.def().edit().putBoolean(Pref.SAMPLE_SCRIPTS_COPIED, true).apply();
}
return failCount;
}
public List<SampleGroup> getSamplesFromAssets(AssetManager assets, String path) {
List<SampleGroup> sampleGroups = new ArrayList<>();
try {

View File

@@ -81,7 +81,7 @@ public class AccessibilityWatchDogService extends AccessibilityService {
Log.v(TAG, "onAccessibilityEvent: " + event);
synchronized (mDelegates) {
for (Map.Entry<Integer, AccessibilityDelegate> entry : mDelegates.entrySet()) {
Log.v(TAG, "delegate: " + entry.getValue().getClass().getName());
//Log.v(TAG, "delegate: " + entry.getValue().getClass().getName());
if (entry.getValue().onAccessibilityEvent(this, event))
break;
}

View File

@@ -8,34 +8,35 @@ import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchHandleService;
import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate;
import com.stardust.scriptdroid.external.notification.record.AccessibilityActionRecordNotification;
import com.stardust.scriptdroid.record.Recorder;
import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder;
import com.stardust.scriptdroid.Pref;
import java.lang.ref.WeakReference;
import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED;
/**
* Created by Stardust on 2017/2/16.
*/
public class VolumeChangeListenService extends Service {
public class VolumeChangeObverseService extends Service {
private static final String ACTION_VOLUME_CHANGE = "android.media.VOLUME_CHANGED_ACTION";
private VolumeChangeReceiver mVolumeChangeReceiver;
private static WeakReference<VolumeChangeListenService> instance;
private static VolumeChangeObverseService instance;
public static void stopServiceIfNeeded() {
if (instance != null && instance.get() != null) {
instance.get().stopSelf();
if (instance != null) {
instance.stopSelf();
instance = null;
}
}
public static void startServiceIfNeeded(Context context) {
if (Pref.isRecordVolumeControlEnable() && (instance == null || instance.get() == null)) {
context.startService(new Intent(context, VolumeChangeListenService.class));
if (Pref.isRecordVolumeControlEnable() && instance == null) {
context.startService(new Intent(context, VolumeChangeObverseService.class));
}
}
@@ -43,10 +44,10 @@ public class VolumeChangeListenService extends Service {
@Override
public void onCreate() {
super.onCreate();
if (instance != null && instance.get() != null) {
instance.get().stopSelf();
if (instance != null) {
instance.stopSelf();
}
instance = new WeakReference<>(this);
instance = this;
mVolumeChangeReceiver = new VolumeChangeReceiver();
registerReceiver(mVolumeChangeReceiver, new IntentFilter(ACTION_VOLUME_CHANGE));
}
@@ -60,6 +61,7 @@ public class VolumeChangeListenService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
instance = null;
unregisterReceiver(mVolumeChangeReceiver);
}
@@ -74,10 +76,10 @@ public class VolumeChangeListenService extends Service {
return;
}
mLastChangeMillis = System.currentTimeMillis();
if (AccessibilityRecorderDelegate.getInstance().getState() == STOPPED) {
ActionRecordSwitchHandleService.startRecord(VolumeChangeListenService.this);
if (AccessibilityActionRecorder.getInstance().getState() == Recorder.STATE_STOPPED) {
AccessibilityActionRecordNotification.startRecord(VolumeChangeObverseService.this);
} else {
ActionRecordSwitchHandleService.stopRecord(VolumeChangeListenService.this);
AccessibilityActionRecordNotification.stopRecord(VolumeChangeObverseService.this);
}
}
}

View File

@@ -25,7 +25,7 @@ public interface BackPressedHandler {
mActivity = activity;
}
public DoublePressExit setDoublePressInterval(long doublePressInterval) {
public DoublePressExit doublePressInterval(long doublePressInterval) {
mDoublePressInterval = doublePressInterval;
return this;
}

View File

@@ -16,15 +16,15 @@ import com.stardust.scriptdroid.R;
public class ImageSelector implements OnActivityResultDelegate {
public interface ImageSelectorCallback {
void onImageSelected(String path);
void onImageSelected(ImageSelector selector, 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);
public ImageSelector(Activity activity, Intermediary intermediary, ImageSelectorCallback callback) {
intermediary.addDelegate(REQUEST_CODE, this);
mActivity = activity;
mCallback = callback;
}
@@ -48,7 +48,7 @@ public class ImageSelector implements OnActivityResultDelegate {
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
cursor.close();
mCallback.onImageSelected(filePath);
mCallback.onImageSelected(this, filePath);
}

View File

@@ -38,49 +38,6 @@ public class Shell {
private StringBuilder mSucceedOutput = new StringBuilder();
private StringBuilder mErrorOutput = new StringBuilder();
public static String bytesToString(byte[] data, int base, int length) {
StringBuilder buf = new StringBuilder();
for(int i = 0; i < length; ++i) {
byte b = data[base + i];
if(b >= 32 && b <= 126) {
buf.append((char)b);
} else {
buf.append(String.format("\\x%02x", new Object[]{Byte.valueOf(b)}));
}
}
return buf.toString();
}
public static void test(final Activity activity) {
TermSettings settings = new TermSettings(App.getApp().getResources(), PreferenceManager.getDefaultSharedPreferences(App.getApp()));
try {
final ShellTermSession termSession = new ShellTermSession(settings, "");
termSession.initializeEmulator(80, 40);
termSession.write("su\r");
termSession.write("getevent\r");
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader reader = new BufferedReader(new InputStreamReader(termSession.getTermIn()));
try {
while (reader.ready()){
String line = reader.readLine();
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
public Shell() {
this(false);
}
@@ -97,7 +54,6 @@ public class Shell {
}
public Shell execute(String command) {
try {
mCommandOutputStream.writeBytes(command);
if (!command.endsWith(COMMAND_LINE_END)) {

View File

@@ -38,6 +38,9 @@ import com.stardust.theme.ThemeColorManager;
import com.stardust.view.ViewBinder;
import java.io.File;
import java.util.Timer;
import timber.log.Timber;
/**
* Created by Stardust on 2017/1/29.
@@ -98,8 +101,10 @@ public class EditActivity extends Editor920Activity {
if (intent.getAction().equals(ACTION_ON_RUN_FINISHED)) {
setMenuStatus(R.id.run, MenuDef.STATUS_NORMAL);
String msg = intent.getStringExtra(EXTRA_EXCEPTION_MESSAGE);
if (msg != null)
if (msg != null) {
Snackbar.make(mView, getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG).show();
Timber.e(msg);
}
}
}
};

View File

@@ -101,11 +101,16 @@ 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", "addAccessibilityDelegate"};
public void setFunctions(String[] functions) {
mFunctions = functions;
}
private String[] mFunctions = {"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);
c.addAll(searchCodeCompletion(str, FUNCTIONS));
c.addAll(searchCodeCompletion(str, mFunctions));
c.addAll(searchKeyWordCompletion(str));
if (c.size() > 0) {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(c, DEFAULT_CODE_COMPLETION_LIST);

View File

@@ -14,6 +14,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
import java.util.ArrayList;
import java.util.Collection;
@@ -73,6 +74,7 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
private void init() {
setAdapter(new CodeCompletionAdapter());
setLayoutManager(new LinearLayoutManager(getContext(), HORIZONTAL, false));
mCodeCompletion.setFunctions(JavaScriptEngine.getDefault().getGlobalFunctions());
}
public void setEditTextBridge(EditTextBridge editTextBridge) {

View File

@@ -11,6 +11,8 @@ import com.jecelyin.editor.v2.ui.IActivity;
import com.jecelyin.editor.v2.ui.TabManager;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Map;
/**
* Created by Stardust on 2017/1/30.
@@ -21,6 +23,18 @@ public class Editor920Activity extends IActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Pref.getInstance(this).setReadOnly(false);
setPref(Pref.KEY_PREF_KEEP_BACKUP_FILE, false);
}
private void setPref(String key, Object value) {
try {
Field field = Pref.class.getDeclaredField("map");
field.setAccessible(true);
((Map) field.get(Pref.getInstance(this))).put(key, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override

View File

@@ -66,11 +66,19 @@ public class IssueReportActivity extends IssueReporterActivity {
final String errorDetail = getIntent().getStringExtra("error");
if (errorDetail != null) {
((EditText) findViewById(R.id.air_inputDescription)).setText(errorDetail);
((EditText) findViewById(R.id.air_inputTitle)).setText(R.string.text_crash_en);
String title = getFirstLine(errorDetail);
((EditText) findViewById(R.id.air_inputTitle)).setText(title);
mCrash = true;
}
}
private String getFirstLine(String str) {
int i = str.indexOf('\n');
if(i < 0)
return str;
return str.substring(0, i);
}
private boolean validateInput() {
if (mValidateInput == null) {
try {

View File

@@ -9,12 +9,17 @@ import com.stardust.widget.CommonMarkdownView;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.tool.FileUtils;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by Stardust on 2017/2/1.
*/
public class DocumentationActivity extends BaseActivity {
private CommonMarkdownView mCommonMarkdownView;

View File

@@ -33,7 +33,7 @@ import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList;
import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchNotification;
import com.stardust.scriptdroid.external.notification.record.AccessibilityActionRecordNotification;
import com.stardust.scriptdroid.record.inputevent.InputEventRecorder;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsRecorder;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
@@ -68,12 +68,14 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
private static final String ARGUMENT_SCRIPT = "ARGUMENT_SCRIPT";
private static final String ACTION_ON_ROOT_RECORD_STOPPED = "ACTION_ON_ROOT_RECORD_STOPPED";
private SlidingUpPanel mAddFilePanel;
private ScriptFileList mScriptFileList;
private DrawerLayout mDrawerLayout;
@ViewBinding.Id(R.id.bottom_menu)
private SlidingUpPanel mAddBottomMenuPanel;
private MyScriptListFragment mMyScriptListFragment;
private SampleScriptListFragment mSampleScriptListFragment;
private DrawerLayout mDrawerLayout;
private OnActivityResultDelegate.Manager mManager = new OnActivityResultDelegate.Manager();
private OnActivityResultDelegate.Intermediary mActivityResultIntermediary = new OnActivityResultDelegate.Intermediary();
private BackPressedHandler.Observer mBackPressObserver = new BackPressedHandler.Observer();
@Override
@@ -113,7 +115,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
private void addScriptFile(final String path) {
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() {
.input(getString(R.string.text_please_input_name), FileUtils.getNameWithoutExtension(path), new MaterialDialog.InputCallback() {
@Override
public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {
MainActivity.this.addScriptFile(input.toString(), path);
@@ -130,7 +132,6 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
mDrawerLayout = (DrawerLayout) View.inflate(this, R.layout.activity_main, null);
setContentView(mDrawerLayout);
setUpFragment();
mAddFilePanel = $(R.id.bottom_menu);
setUpToolbar();
setUpTabLayout();
setUpDrawerHeader();
@@ -141,7 +142,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
private void setUpFragment() {
SlideMenuFragment.setFragment(this, R.id.fragment_slide_menu);
mMyScriptListFragment = new MyScriptListFragment();
mScriptFileList = SharedPrefScriptFileList.getInstance();
mScriptFileList = ScriptFileList.getImpl();
mSampleScriptListFragment = new SampleScriptListFragment();
}
@@ -188,7 +189,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
@ViewBinding.Click(R.id.add)
private void showAddFilePanel() {
mAddFilePanel.show();
mAddBottomMenuPanel.show();
}
@ViewBinding.Click(R.id.create_new_file)
@@ -238,7 +239,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
Snackbar.make(mDrawerLayout, R.string.text_need_enable_accessibility_service, Snackbar.LENGTH_SHORT).show();
return;
}
ActionRecordSwitchNotification.showOrUpdateNotification();
AccessibilityActionRecordNotification.showOrUpdateNotification();
Snackbar.make(mDrawerLayout, R.string.hint_start_record, Snackbar.LENGTH_SHORT).show();
}
@@ -265,11 +266,12 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
@ViewBinding.Click(R.id.drawer_header_img)
public void selectHeaderImage() {
new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() {
new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() {
@Override
public void onImageSelected(String path) {
public void onImageSelected(ImageSelector selector, String path) {
setDrawerHeaderImage(path);
Pref.def().edit().putString(Pref.KEY_DRAWER_HEADER_IMAGE_PATH, path).apply();
mActivityResultIntermediary.removeDelegate(selector);
}
}).select();
}
@@ -334,8 +336,8 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
@Override
public void onBackPressed() {
if (mAddFilePanel.isShowing()) {
mAddFilePanel.dismiss();
if (mAddBottomMenuPanel.isShowing()) {
mAddBottomMenuPanel.dismiss();
} else if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
mDrawerLayout.closeDrawer(Gravity.START);
} else {
@@ -350,7 +352,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mManager.onActivityResult(requestCode, resultCode, data);
mActivityResultIntermediary.onActivityResult(requestCode, resultCode, data);
}
public static void onActionRecordStopped(Context context, String script) {
@@ -370,11 +372,12 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
}
public void OnAppBarClick(View view) {
new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() {
new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() {
@Override
public void onImageSelected(String path) {
public void onImageSelected(ImageSelector selector, String path) {
Pref.def().edit().putString(Pref.KEY_APP_BAR_IMAGE_PATH, path).apply();
setAppBarImage(path);
mActivityResultIntermediary.removeDelegate(selector);
}
}).select();
}

View File

@@ -39,8 +39,7 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler
@Nullable
@Override
public View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my_script_list, container, false);
return view;
return inflater.inflate(R.layout.fragment_my_script_list, container, false);
}
@Override
@@ -55,7 +54,7 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler
private void setUpScriptList() {
mScriptListRecyclerView = $(R.id.script_list);
mScriptFileList = SharedPrefScriptFileList.getInstance();
mScriptFileList = ScriptFileList.getImpl();
mNoScriptHint = $(R.id.hint_no_script);
mScriptListRecyclerView.getAdapter().registerAdapterDataObserver(new SimpleAdapterDataObserver() {
@Override
@@ -91,6 +90,18 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler
}
}
@Override
public void onResume() {
super.onResume();
EventBus.getDefault().register(mScriptListRecyclerView);
}
@Override
public void onPause() {
super.onPause();
EventBus.getDefault().unregister(mScriptListRecyclerView);
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@@ -139,18 +139,6 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView {
Snackbar.make(this, event.messageResId, Snackbar.LENGTH_SHORT).show();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
EventBus.getDefault().unregister(this);
}
private class Adapter extends RecyclerView.Adapter<ViewHolder> {
@Override

View File

@@ -2,13 +2,27 @@ package com.stardust.scriptdroid.ui.main.sample_list;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.app.Fragment;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.droid.script.file.ScriptFileList;
import com.stardust.scriptdroid.sample.Sample;
import com.stardust.scriptdroid.sample.SampleFileManager;
import com.stardust.scriptdroid.tool.FileUtils;
import com.stardust.scriptdroid.ui.edit.EditActivity;
import com.stardust.scriptdroid.ui.main.my_script_list.MyScriptListFragment;
import com.stardust.util.MessageEvent;
import org.greenrobot.eventbus.EventBus;
/**
* Created by Stardust on 2017/3/13.
@@ -28,7 +42,47 @@ public class SampleScriptListFragment extends Fragment {
protected void afterCreateView() {
mSampleScriptListRecyclerView = $(R.id.script_list);
mSampleScriptListRecyclerView.setSamples(SampleFileManager.getInstance().getSamplesFromAssets(getContext().getAssets(), "sample"));
mSampleScriptListRecyclerView.setOnItemLongClickListener(new SampleScriptListRecyclerView.OnItemLongClickListener() {
@Override
public void onItemLongClick(Sample sample) {
showMenuDialog(sample);
}
});
mSampleScriptListRecyclerView.setOnItemClickListener(new SampleScriptListRecyclerView.OnItemClickListener() {
@Override
public void onItemClick(Sample sample) {
viewSample(sample);
}
});
}
private void showMenuDialog(final Sample sample) {
new MaterialDialog.Builder(getActivity())
.title(sample.name)
.items(getString(R.string.text_copy_to_my_scripts))
.itemsCallback(new MaterialDialog.ListCallback() {
@Override
public void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text) {
copySampleToMyScripts(sample);
}
})
.show();
}
private void copySampleToMyScripts(Sample sample) {
String path = ScriptFile.DEFAULT_FOLDER + sample.name + ".js";
if (!ScriptFileList.getImpl().containsPath(path) && FileUtils.copyAsset(sample.path, path)) {
ScriptFileList.getImpl().add(new ScriptFile(sample.name, path));
EventBus.getDefault().post(new MessageEvent(MyScriptListFragment.MESSAGE_SCRIPT_FILE_ADDED));
Snackbar.make(mSampleScriptListRecyclerView, R.string.text_import_succeed, Snackbar.LENGTH_SHORT).show();
} else {
Snackbar.make(mSampleScriptListRecyclerView, R.string.text_file_exists, Snackbar.LENGTH_SHORT).show();
}
}
private void viewSample(Sample sample) {
EditActivity.editAssetFile(getContext(), sample.name, sample.path);
}
}

View File

@@ -29,14 +29,36 @@ import java.util.List;
public class SampleScriptListRecyclerView extends RecyclerView {
public interface OnItemLongClickListener {
void onItemLongClick(Sample sample);
}
private OnClickListener mOnItemClickListener = new OnClickListener() {
public interface OnItemClickListener {
void onItemClick(Sample sample);
}
private OnClickListener mOnItemClickListenerProxy = new OnClickListener() {
@Override
public void onClick(View v) {
SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v);
viewSample(viewHolder.getChild());
if (mOnItemClickListener != null) {
SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v);
mOnItemClickListener.onItemClick(viewHolder.getChild());
}
}
};
private OnLongClickListener mOnLongClickListenerProxy = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v);
mOnItemLongClickListener.onItemLongClick(viewHolder.getChild());
return true;
}
return false;
}
};
private OnItemLongClickListener mOnItemLongClickListener;
private OnItemClickListener mOnItemClickListener;
private Adapter mAdapter;
@@ -57,6 +79,14 @@ public class SampleScriptListRecyclerView extends RecyclerView {
init();
}
public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
mOnItemLongClickListener = onItemLongClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
public void setSamples(List<com.stardust.scriptdroid.sample.SampleGroup> samples) {
mSampleGroups.clear();
for (com.stardust.scriptdroid.sample.SampleGroup sampleGroup : samples) {
@@ -71,12 +101,6 @@ public class SampleScriptListRecyclerView extends RecyclerView {
addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL));
}
private void viewSample(Sample sample) {
EditActivity.editAssetFile(getContext(), sample.name, sample.path);
}
private class SampleGroup implements Parent<Sample> {
private com.stardust.scriptdroid.sample.SampleGroup mSampleGroup;
@@ -108,7 +132,8 @@ public class SampleScriptListRecyclerView extends RecyclerView {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
((LevelBeamView) itemView.findViewById(R.id.level)).setLevel(2);
itemView.setOnClickListener(mOnItemClickListener);
itemView.setOnClickListener(mOnItemClickListenerProxy);
itemView.setOnLongClickListener(mOnLongClickListenerProxy);
}
void bind(Sample sample) {

View File

@@ -3,11 +3,14 @@ package com.stardust.view;
import android.view.View;
import android.widget.CompoundButton;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by Stardust on 2017/1/30.
*
* 哈你说我为什么不用AA框架之前还不知道嘛所以现在用了。
*/
public class ViewBinder {
@@ -21,6 +24,7 @@ public class ViewBinder {
throw new RuntimeException("You must implement findViewById to use view binding", e);
}
Method[] methods = o.getClass().getDeclaredMethods();
bindId(o, findViewById);
for (Method method : methods) {
method.setAccessible(true);
bindClick(o, method, findViewById);
@@ -28,6 +32,20 @@ public class ViewBinder {
}
}
private static void bindId(Object o, Method findViewById) {
for (Field field : o.getClass().getDeclaredFields()) {
field.setAccessible(true);
ViewBinding.Id id = field.getAnnotation(ViewBinding.Id.class);
if (id == null || id.value() == 0)
continue;
try {
field.set(o, findViewById.invoke(o, id.value()));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
private static void bindCheck(final Object o, final Method method, Method findViewById) {
ViewBinding.Check annotation = method.getAnnotation(ViewBinding.Check.class);
if (annotation == null || annotation.value() == 0)

View File

@@ -32,4 +32,10 @@ public @interface ViewBinding {
@interface Check {
int value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Id {
int value();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

View File

@@ -124,6 +124,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:orientation="vertical">
@@ -148,6 +149,7 @@
android:id="@+id/root_record"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center"

View File

@@ -49,4 +49,47 @@
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<TextView
android:id="@+id/open_launcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/text_open_launcher"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<TextView
android:id="@+id/current_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<TextView
android:id="@+id/current_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
</LinearLayout>

View File

@@ -0,0 +1,146 @@
<?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="match_parent"
android:orientation="vertical"
android:theme="@style/AppTheme"
tools:background="@color/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/text_script_record"
android:textColor="@android:color/primary_text_dark"
android:textSize="20sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<RelativeLayout
android:id="@+id/sw_root_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:padding="16dp">
<android.support.v7.widget.SwitchCompat
android:id="@+id/sw_recorded_by_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/sw_recorded_by_root"
android:text="@string/text_record_with_root"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<RelativeLayout
android:id="@+id/sw_record_toast_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:padding="16dp">
<android.support.v7.widget.SwitchCompat
android:id="@+id/sw_record_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/sw_record_toast"
android:text="@string/text_record_msg"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<LinearLayout
android:id="@+id/start_or_pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingTop="16dp">
<ImageView
android:id="@+id/img_start_or_pause"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="8dp"
android:src="@drawable/ic_play_arrow_white_48dp"/>
<TextView
android:id="@+id/text_start_or_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_start_record"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
<LinearLayout
android:id="@+id/stop_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingTop="16dp"
android:visibility="gone">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="8dp"
android:src="@drawable/ic_stop_white_36pt"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_stop_record"
android:textColor="@android:color/primary_text_dark"
android:textSize="16sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#c4c4c4"/>
</LinearLayout>

View File

@@ -75,13 +75,13 @@
<notice>
<name>hover</name>
<copyright>2016 (C) Copyright Open-RnD Sp. z o.o.</copyright>
<url>https://github.com/open-rnd/android-multi-level-listview</url>
<url>https://github.com/google/hover</url>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>event-bus</name>
<copyright>2016 (C) Copyright Open-RnD Sp. z o.o.</copyright>
<url>https://github.com/open-rnd/android-multi-level-listview</url>
<url>https://github.com/greenrobot/EventBus</url>
<license>Apache Software License 2.0</license>
</notice>
<notice>

View File

@@ -3,6 +3,6 @@
<color name="colorPrimary">#009688</color>
<color name="colorPrimaryOld">#8ea22a</color>
<color name="colorPrimaryDark">#00796B</color>
<color name="colorAccent">#FF4081</color>
<color name="colorAccent">#536DFE</color>
<color name="sliding_up_panel_shadow_color">#99444444</color>
</resources>

View File

@@ -76,7 +76,7 @@
<string name="text_choose_file">选择一个文件</string>
<string name="folder_name">/脚本/</string>
<string name="text_error">错误</string>
<string name="text_load_failed">文档加载失败/(ㄒoㄒ)/~~</string>
<string name="text_load_failed">还没有文档/(ㄒoㄒ)/~~</string>
<string name="text_copy_debug_info">复制调试信息</string>
<string name="text_it_is_the_developer_of_app">这是软件开发者(。・・)</string>
<string name="text_lll">略略略</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_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>
@@ -147,4 +147,36 @@
<string name="hint_no_script">点击右上角新建第一个脚本吧~(。・・)</string>
<string name="text_reset_background">重置背景图片设置</string>
<string name="text_restart_app_to_apply">重启软件生效</string>
<string name="text_record_with_root">使用root权限录制</string>
<string name="text_record_msg">录制提示</string>
<string name="text_text_changed">文字改变</string>
<string name="text_scrolled">滑动</string>
<string name="text_long_clicked">长按</string>
<string name="text_clicked">点击</string>
<string name="text_open_launcher">打开主界面</string>
<string name="text_copy_to_my_scripts">导入到我的脚本</string>
<string name="text_import_succeed">导入成功</string>
<string name="text_no_root_script_record">免Root脚本录制</string>
<string name="text_script_record_with_root">Root脚本录制</string>
<string name="text_use_key_to_start_record">开始录制按键</string>
<string name="text_use_key_to_stop_record">结束录制按键</string>
<string name="text_none">NONE</string>
<string name="key_start_record_trigger">key_start_record_trigger</string>
<string name="key_stop_record_trigger">key_stop_record_trigger</string>
<string name="text_current_activity">当前活动:</string>
<string name="text_current_package">当前应用包名:</string>
<string-array name="record_control_keys">
<item></item>
<item>音量上键</item>
<item>音量下键</item>
<item>照相键</item>
</string-array>
<string-array name="record_control_values">
<item>NONE</item>
<item>KEY_VOLUMEUP</item>
<item>KEY_VOLUMEDOWN</item>
<item>KEY_CAMERA</item>
</string-array>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_script_record">
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_no_root_script_record">
<com.stardust.theme.preference.ThemeColorSwitchPreference
android:defaultValue="false"
@@ -10,6 +10,25 @@
android:title="@string/text_use_volume_control_record"/>
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_script_record_with_root">
<com.afollestad.materialdialogs.prefs.MaterialListPreference
android:key="@string/key_start_record_trigger"
android:defaultValue="@string/text_none"
android:entries="@array/record_control_keys"
android:entryValues="@array/record_control_values"
android:title="@string/text_use_key_to_start_record"
/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
android:key="@string/key_stop_record_trigger"
android:defaultValue="@string/text_none"
android:entries="@array/record_control_keys"
android:entryValues="@array/record_control_values"
android:title="@string/text_use_key_to_stop_record"
/>
</com.stardust.theme.preference.ThemeColorPreferenceCategory>
<com.stardust.theme.preference.ThemeColorPreferenceCategory android:title="@string/text_edit">
<com.afollestad.materialdialogs.prefs.MaterialEditTextPreference