Merge pull request #220 from hyb1996/new_ui

release 2.0.15 Beta
This commit is contained in:
Stardust
2017-07-22 09:39:34 +08:00
committed by GitHub
74 changed files with 2033 additions and 290 deletions

View File

@@ -9,8 +9,8 @@ android {
applicationId "com.stardust.scriptdroid"
minSdkVersion 17
targetSdkVersion 23
versionCode 151
versionName "2.0.14 Beta2.1"
versionCode 155
versionName "2.0.15 Alpha3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {

View File

@@ -49,6 +49,16 @@
"type": "markdown",
"path":"documentation"
},
{
"title": "事件与按键、触摸监听",
"type": "markdown",
"path":"documentation"
},
{
"title": "定时器",
"type": "markdown",
"path":"documentation"
},
{
"title": "shell命令",
"type": "markdown",

View File

@@ -1,7 +1,8 @@
### sleep(n)
### sleep(\[n\])
* n \<Number\> 毫秒数
暂停运行n**毫秒**的时间。1秒等于1000毫秒。
如果不加参数n,则会一直暂停,通常用于有事件监听的脚本中使用。
### launchPackage(packageName)
* packageName \<String\> 应用包名。参见《Shell命令:应用包名》章节。

View File

@@ -0,0 +1,380 @@
目录:
* [events](#events)
* [EventEmitter](#EventEmitter)
* [KeyEvent](#KeyEvent)
# events
events也是一个\[EventEmitter\](#EventEmitter),可以使用EventEmitter的所有函数,这里只介绍其自身的函数。
注意,events内置的触摸、按键事件必须调用loop()函数才能正常工作。例如:
```
events.observeKey();
events.onKeyDown("volume_up", function(event){
toast("音量上键被按下了");
});
events.onKeyDown("menu", function(event){
toast("菜单键被按下了");
});
loop();
//后面的语句不会再执行了
```
### events.emitter()
返回一个新的EventEmitter。
### events.observeKey()
启用按键监听,例如音量键、Home键。
在安卓4.3以上才能使用。
### events.onKeyDown(keyName, listener)
* keyName \<String\> 要监听的按键名称
* listener \<Function\> 参数为一个[KeyEvent](#KeyEvent)
注册一个按键监听函数,当有keyName对应的按键被按下会调用该函数。按键名称主要包括:
* volume_up 音量上键
* volume_down 音量下键
* home 主屏幕键
* back 返回键
* menu 菜单键
### events.onKeyUp(keyName, listener)
* keyName \<String\> 要监听的按键名称
* listener \<Function\> 参数为一个[KeyEvent](#KeyEvent)
注册一个按键监听函数,当有keyName对应的按键弹起时会调用该函数。
### events.onceKeyDown(keyName, listener)
* keyName \<String\> 要监听的按键名称
* listener \<Function\> 参数为一个[KeyEvent](#KeyEvent)
注册一个按键监听函数,当有keyName对应的按键被按下时会调用该函数,之后会注销该按键监听。也就是listener只会被调用一次。
### events.onceKeyUp(keyName, listener)
* keyName \<String\> 要监听的按键名称
* listener \<Function\> 参数为一个[KeyEvent](#KeyEvent)
注册一个按键监听函数,当有keyName对应的按键弹起时会调用该函数,之后会注销该按键监听。也就是listener只会被调用一次。
### events.removeAllKeyDownListeners(keyName)
* keyName \<String\> 按键名称
删除该按键的KeyDown事件的所有监听。
### events.removeAllKeyUpListeners(keyName)
* keyName \<String\> 按键名称
删除该按键的KeyUp事件的所有监听。
### events.observeTouch()
启用屏幕触摸监听。(需要root权限)
### events.setTouchEventTimeout(timeout)
* timeout \<Number\> 两个触摸事件的最小间隔。单位毫秒。默认为10毫秒。
设置两个触摸事件分发的最小时间间隔。
例如间隔为10毫秒的话,前一个触摸事件发生并被注册的监听器处理后,至少要过10毫秒才能分发和处理下一个触摸事件,这10毫秒之间的触摸将会被忽略。
### events.getTouchEventTimeout()
返回触摸事件的最小时间间隔。
### events.onTouch(listener)
* listener \<Function\> 参数为Point的函数
注册一个触摸监听函数。
例如:
```
events.observeTouch();
events.onTouch(function(p){
log(p.x + ", " + p.y);
});
events.loop();
```
### events.removeAllTouchListeners()
删除所有事件监听函数。
### key事件
当有按键被按下或弹起时会触发该事件。
```
"auto";
events.observeKey();
events.on("key", function(keyCode, event){
//处理按键事件
});
events.loop();
```
其中监听器的参数KeyCode包括:
* KeyEvent.KEYCODE_HOME 主页键
* KeyEvent.KEYCODE_BACK 返回键
* KeyEvent.KEYCODE_MENU 菜单键
* KeyEvent.KEYCODE_VOLUME_UP 音量上键
* KeyEvent.KEYCODE_VOLUME_DOWN 音量下键
### key_down事件
当有按键被按下时会触发该事件。
```
"auto";
events.observeKey();
events.on("key_down", function(keyCode, event){
//处理按键按下事件
});
events.loop();
```
### key_up事件
当有按键弹起时会触发该事件。
```
"auto";
events.observeKey();
events.on("key_up", function(keyCode, event){
//处理按键弹起事件
});
events.loop();
```
# EventEmitter
### EventEmitter.defaultMaxListeners
每个事件默认可以注册最多 10 个监听器。 单个 EventEmitter 实例的限制可以使用 emitter.setMaxListeners(n) 方法改变。 所有 EventEmitter 实例的默认值可以使用 EventEmitter.defaultMaxListeners 属性改变。
设置 EventEmitter.defaultMaxListeners 要谨慎,因为会影响所有 EventEmitter 实例,包括之前创建的。 因而,调用 emitter.setMaxListeners(n) 优先于 EventEmitter.defaultMaxListeners。
注意,与NodeJs不同,**这是一个硬性限制**。 EventEmitter 实例不允许添加更多的监听器,监听器超过最大数量时会抛出TooManyListenersException。
```
emitter.setMaxListeners(emitter.getMaxListeners() + 1);
emitter.once('event', () => {
// 做些操作
emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0));
});
```
## EventEmitter.addListener(eventName, listener)
* eventName \<any\>
* listener \<Function\>
emitter.on(eventName, listener) 的别名。
### EventEmitter.emit(eventName\[, ...args\])
* eventName <any>
* args <any>
按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。
如果事件有监听器,则返回 true ,否则返回 false。
### EventEmitter.eventNames()
返回一个列出触发器已注册监听器的事件的数组。 数组中的值为字符串或符号。
```
const myEE = events.emitter();
myEE.on('foo', () => {});
myEE.on('bar', () => {});
const sym = Symbol('symbol');
myEE.on(sym, () => {});
console.log(myEE.eventNames());
// 打印: [ 'foo', 'bar', Symbol(symbol) ]
```
### EventEmitter.getMaxListeners()#
返回 EventEmitter 当前的最大监听器限制值,该值可以通过 emitter.setMaxListeners(n) 设置或默认为 EventEmitter.defaultMaxListeners。
### EventEmitter.listenerCount(eventName)
* eventName <any> 正在被监听的事件名
返回正在监听名为 eventName 的事件的监听器的数量。
### EventEmitter.listeners(eventName)
* eventName <any>
返回名为 eventName 的事件的监听器数组的副本。
```
server.on('connection', (stream) => {
console.log('someone connected!');
});
console.log(util.inspect(server.listeners('connection')));
// 打印: [ [Function] ]
```
### EventEmitter.on(eventName, listener)
* eventName <any> 事件名
* listener <Function> 回调函数
添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。
```
server.on('connection', (stream) => {
console.log('有连接!');
});
```
返回一个 EventEmitter 引用,可以链式调用。
默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependListener() 方法可用于将事件监听器添加到监听器数组的开头。
```
const myEE = events.emitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// 打印:
// b
// a
```
### EventEmitter.once(eventName, listener)#
* eventName <any> 事件名
* listener <Function> 回调函数
添加一个单次 listener 函数到名为 eventName 的事件。 下次触发 eventName 事件时,监听器会被移除,然后调用。
```
server.once('connection', (stream) => {
console.log('首次调用!');
});
```
返回一个 EventEmitter 引用,可以链式调用。
默认情况下,事件监听器会按照添加的顺序依次调用。 emitter.prependOnceListener() 方法可用于将事件监听器添加到监听器数组的开头。
```
const myEE = events.emitter();
myEE.once('foo', () => console.log('a'));
myEE.prependOnceListener('foo', () => console.log('b'));
myEE.emit('foo');
// 打印:
// b
// a
```
### EventEmitter.prependListener(eventName, listener)
* eventName <any> 事件名
* listener <Function> 回调函数
添加 listener 函数到名为 eventName 的事件的监听器数组的开头。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventName 和 listener 会导致 listener 被添加与调用多次。
```
server.prependListener('connection', (stream) => {
console.log('有连接!');
});
```
返回一个 EventEmitter 引用,可以链式调用。
### EventEmitter.prependOnceListener(eventName, listener)
* eventName <any> 事件名
* listener <Function> 回调函数
添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。 下次触发 eventName 事件时,监听器会被移除,然后调用。
```
server.prependOnceListener('connection', (stream) => {
console.log('首次调用!');
});
```
返回一个 EventEmitter 引用,可以链式调用。
### EventEmitter.removeAllListeners(\[eventName\])
* eventName <any>
移除全部或指定 eventName 的监听器。
注意,在代码中移除其他地方添加的监听器是一个不好的做法,尤其是当 EventEmitter 实例是其他组件或模块创建的。
返回一个 EventEmitter 引用,可以链式调用。
### EventEmitter.removeListener(eventName, listener)
* eventName <any>
* listener <Function>
从名为 eventName 的事件的监听器数组中移除指定的 listener。
```
const callback = (stream) => {
console.log('有连接!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);
```
removeListener 最多只会从监听器数组里移除一个监听器实例。 如果任何单一的监听器被多次添加到指定 eventName 的监听器数组中,则必须多次调用 removeListener 才能移除每个实例。
注意,一旦一个事件被触发,所有绑定到它的监听器都会按顺序依次触发。 这意味着,在事件触发后、最后一个监听器完成执行前,任何 removeListener() 或 removeAllListeners() 调用都不会从 emit() 中移除它们。 随后的事件会像预期的那样发生。
```
const myEmitter = events.emitter();
const callbackA = () => {
console.log('A');
myEmitter.removeListener('event', callbackB);
};
const callbackB = () => {
console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// callbackA 移除了监听器 callbackB,但它依然会被调用。
// 触发是内部的监听器数组为 [callbackA, callbackB]
myEmitter.emit('event');
// 打印:
// A
// B
// callbackB 被移除了。
// 内部监听器数组为 [callbackA]
myEmitter.emit('event');
// 打印:
// A
```
因为监听器是使用内部数组进行管理的,所以调用它会改变在监听器被移除后注册的任何监听器的位置索引。 虽然这不会影响监听器的调用顺序,但意味着由 emitter.listeners() 方法返回的监听器数组副本需要被重新创建。
返回一个 EventEmitter 引用,可以链式调用。
### EventEmitter.setMaxListeners(n)
* n <integer>
默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 此限制有助于寻找内存泄露。 但是,并不是所有的事件都要被限为 10 个。 emitter.setMaxListeners() 方法允许修改指定的 EventEmitter 实例的限制。 值设为 Infinity(或 0)表明不限制监听器的数量。
返回一个 EventEmitter 引用,可以链式调用。
# KeyEvent
### KeyEvent.getAction()
返回事件的动作。包括:
* KeyEvent.ACTION_DOWN 按下事件
* KeyEvent.ACTION_UP 弹起事件
### KeyEvent.getKeyCode()
返回按键的键值。包括:
* KeyEvent.KEYCODE_HOME 主页键
* KeyEvent.KEYCODE_BACK 返回键
* KeyEvent.KEYCODE_MENU 菜单键
* KeyEvent.KEYCODE_VOLUME_UP 音量上键
* KeyEvent.KEYCODE_VOLUME_DOWN 音量下键
### KeyEvent.getEventTime()
返回事件发生的时间戳。
### KeyEvent.getDownTime()
返回最近一次按下事件的时间戳。如果本身是按下事件,则与getEventTime()相同。
### KeyEvent.keyCodeToString(keyCode)
把键值转换为字符串。例如KEYCODE_HOME转换为"KEYCODE_HOME"。

View File

@@ -0,0 +1,66 @@
timer 模块暴露了一个全局的 API,用于在某个未来时间段调用调度函数。 因为定时器函数是全局的,所以使用该 API 无需调用 timer.***
Auto.js 中的计时器函数实现了与 Web 浏览器提供的定时器类似的 API,除了它使用了一个不同的内部实现,它是基于 Android Looper-Handler机制构建的。其实现机制与Node.js比较相似。
不同于Web浏览器和Node.js,在Auto.js中,必须显式地调用loop()函数开始Looper循环才能使所有计时器开始工作。loop()函数是一个"死循环", 在他之后的语句将不会被执行。
### loop()
开始Looper循环。使所有定时器工作所必须。
注意,何时调用loop不会影响setTimeout等函数的计时,例如:
```
setTimout(function(){
toast("hello");
exit();
}, 2000);
sleep(1000);
loop();
```
这段代码将在运行后大约2秒显示"hello"。
### setImmediate(callback\[, ...args\])
* callback \<Function\> 在 Auto.js 循环的当前回合结束时要调用的函数。
* ...args \<any\> 当调用 callback 时要传入的可选参数。
预定立即执行的 callback,它是在 I/O 事件的回调之后被触发。 返回一个用于 clearImmediate() 的 id。
当多次调用 setImmediate() 时,callback 函数会按照它们被创建的顺序依次执行。 每次事件循环迭代都会处理整个回调队列。 如果一个立即定时器是被一个正在执行的回调排入队列的,则该定时器直到下一次事件循环迭代才会被触发。
### setInterval(callback, delay\[, ...args\])
* callback \<Function\> 当定时器到点时要调用的函数。
* delay \<number\> 调用 callback 之前要等待的毫秒数。
* ...args \<any\> 当调用 callback 时要传入的可选参数。
预定每隔 delay 毫秒重复执行的 callback。 返回一个用于 clearInterval() 的 id。
当 delay 小于 0 时,delay 会被设为 0。
### setTimeout(callback, delay\[, ...args\])
* callback \<Function\> 当定时器到点时要调用的函数。
* delay \<number\> 调用 callback 之前要等待的毫秒数。
* ...args \<any\> 当调用 callback 时要传入的可选参数。
预定在 delay 毫秒之后执行的单次 callback。 返回一个用于 clearTimeout() 的 id。
callback 可能不会精确地在 delay 毫秒被调用。 Auto.js 不能保证回调被触发的确切时间,也不能保证它们的顺序。 回调会在尽可能接近所指定的时间上调用。
当 delay 小于 0 时,delay 会被设为 0。
setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。
### clearImmediate(id)#
* id \<Number\> 一个 setImmediate() 返回的 id。
取消一个由 setImmediate() 创建的 Immediate 对象。
### clearInterval(id)#
* id \<Number\> 一个 setInterval() 返回的 id。
取消一个由 setInterval() 创建的 Timeout 对象。
### clearTimeout(id)
* id \<Number\> 一个 setTimeout() 返回的 id。
取消一个由 setTimeout() 创建的 Timeout 对象。

View File

@@ -29,7 +29,7 @@
返回路径path是否是文件夹。
### files.isDirEmpty(path)
### files.isEmptyDir(path)
* path \<String\> 路径
返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回false。

View File

@@ -1,62 +1,305 @@
[ "toast",
"app",
"launchPackage",
"launch",
"launchApp",
"getPackageName",
"openAppSetting",
"sleep",
"isStopped",
"notStopped",
"stop",
"console",
"log",
"print",
"err",
"openConsole",
"clearConsole",
"shell",
"currentPackage",
"currentActivity",
"back",
"home",
"powerDialog",
"notifications",
"quickSettings",
"recents",
"splitScreen",
"click",
"longClick",
"scrollDown",
"scrollUp",
"input",
"setClip",
"SetScreenMetrics",
"Tap",
"Swipe",
"Screencap",
"KeyCode",
"Home",
"Back",
"Power",
"Up",
"Down",
"Left",
"Right",
"OK",
"VolumeUp",
"VolumeDown",
"Menu",
"Camera",
"Text",
"selector",
"open",
"newInjectableWebClient",
"newInjectableWebView",
"dialogs",
"rawInput",
"input",
"alert",
"confirm",
"prompt"
]
{
"global" : {
"modules": [
"app",
"automator",
"console",
"dialogs",
"events",
"images",
"files",
"timers",
"ui"
],
"app": [
"launchPackage",
"launch",
"launchApp",
"getPackageName",
"openAppSetting"
],
"automator": [
"click",
"longClick",
"press",
"swipe",
"gesture",
"gestures",
"gestureAsync",
"gesturesAsync",
"scrollDown",
"scrollUp",
"input",
"setText"
],
"console": [
"print",
"log",
"err",
"openConsole",
"clearConsole"
],
"dialogs":[
"rawInput",
"input",
"alert",
"confirm",
"prompt"
],
"images": [
"requestScreenCapture",
"captureScreen",
"findColor",
"findColorInRegion",
"findColorEquals"
],
"files": [
"open"
],
"selector": [
"id",
"idContains",
"idStartsWith",
"idEndsWith",
"idMatches",
"text",
"textContains",
"textStartsWith",
"textEndsWith",
"textMatches",
"desc",
"descContains",
"descStartsWith",
"descEndsWith",
"descMatches",
"className",
"classNameContains",
"classNameStartsWith",
"classNameEndsWith",
"classNameMatches",
"packageName",
"packageNameContains",
"packageNameStartsWith",
"packageNameEndsWith",
"packageNameMatches",
"bounds",
"boundsInside",
"boundsContains",
"drawingOrder",
"checkable",
"checked",
"focusable",
"focused",
"visibleToUser",
"accessibilityFocused",
"selected",
"clickable",
"longClickable",
"enabled",
"password",
"scrollable",
"editable",
"contentInvalid",
"contextClickable",
"multiLine",
"dismissable",
"checkable",
"checked",
"focusable",
"focused",
"visibleToUser",
"accessibilityFocused",
"selected",
"clickable",
"longClickable",
"enabled",
"password",
"scrollable",
"editable",
"contentInvalid",
"contextClickable",
"multiLine",
"dismissable"
],
"shell": [
"SetScreenMetrics",
"Tap",
"Swipe",
"Screencap",
"KeyCode",
"Home",
"Back",
"Power",
"Up",
"Down",
"Left",
"Right",
"OK",
"VolumeUp",
"VolumeDown",
"Menu",
"Camera",
"Text"
],
"timers": [
"loop",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"setImmediate",
"clearImmediate"
],
"web": [
"newInjectableWebClient",
"newInjectableWebView"
],
"general": [
"toast",
"toastLog",
"sleep",
"isStopped",
"notStopped",
"exit",
"setClip",
"getClip",
"currentPackage",
"currentActivity",
"waitForActivity",
"waitForPackage",
"setScreenMetrics"
],
"variables": [
"context",
"activity"
]
},
"app": [
"uninstall",
"viewFile",
"editFile",
"openUrl",
"launchPackage",
"launch",
"launchApp",
"getPackageName",
"openAppSetting"
],
"automator": [
"click",
"longClick",
"press",
"swipe",
"gesture",
"gestures",
"gestureAsync",
"gesturesAsync",
"scrollDown",
"scrollUp",
"input",
"setText"
],
"console": [
"show",
"hide",
"clear",
"verbose",
"print",
"info",
"log",
"warn",
"error",
"assert"
],
"dialogs":[
"select",
"singleChoice",
"multiChoice",
"rawInput",
"input",
"alert",
"confirm",
"prompt"
],
"images": [
"saveImage",
"pixel",
"read",
"requestScreenCapture",
"captureScreen",
"findColor",
"findColorInRegion",
"findColorEquals"
],
"colors": [
"red",
"green",
"blue",
"alpha",
"toString",
"rgb",
"argb"
],
"events": [
"emitter",
"observeKey",
"observeTouch",
"onKeyDown",
"onKeyUp",
"onceKeyDown",
"onceKeyUp",
"removeAllKeyDownListeners",
"removeAllKeyUpListeners",
"onTouch",
"removeAllTouchListeners",
"getTouchEventTimeout",
"setTouchEventTimeout",
"on",
"once",
"emit",
"getListeners",
"addListener",
"eventNames",
"listenerCount",
"listeners",
"prependListener",
"prependOnceListener",
"removeAllListeners",
"removeAllListeners",
"removeListener",
"setMaxListeners",
"getMaxListeners",
"defaultMaxListeners"
],
"files": [
"open",
"isFile",
"isDir",
"isEmptyDir",
"join",
"create",
"createIfNotExists",
"exists",
"ensureDir",
"read",
"write",
"copy",
"rename",
"renameWithoutExtension",
"getName",
"getExtension",
"remove",
"removeDir",
"getSdcardPath",
"listDir"
],
"timers": [
"loop",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"setImmediate",
"clearImmediate"
]
}

View File

@@ -27,6 +27,7 @@ while(true){
addAsFriend();
sleep(延迟);
}
list.scrollForward();
}
function isGroupMember(child){

View File

@@ -62,7 +62,7 @@ function isImage(item){
function notLiked(likeIcon){
var x = likeIcon.bounds().centerX();
var y = likeIcon.bounds().centerY();
return images.detectsColor(capture, x, y, 0x767886);
return images.detectsColor(capture, 0x767886, x, y);
}
toast("请打开QQ空间并慢慢下滑");

View File

@@ -7,5 +7,4 @@ sleep(500);
while(true){
setText("我喜欢你");
click("发送");
sleep(200);
}

View File

@@ -0,0 +1,10 @@
"auto";
toast("本脚本需要和名片点赞脚本同时运行才有效");
while(true){
if(currentActivity() == "com.tencent.mobileqq.activity.VisitorsActivity"){
click("取消");
sleep(300);
}
}

View File

@@ -0,0 +1,34 @@
"auto";
events.observeKey();
var keyNames = {
"KEYCODE_VOLUME_UP": "音量上键",
"KEYCODE_VOLUME_DOWN": "音量下键",
"KEYCODE_HOME": "Home键",
"KEYCODE_BACK": "返回键",
"KEYCODE_MENU": "菜单键",
"KEYCODE_POWER": "电源键",
};
events.on("key", function(code, event){
var keyName = getKeyName(code, event);
if(event.getAction() == event.ACTION_DOWN){
toast(keyName + "被按下");
}else if(event.getAction() == event.ACTION_UP){
toast(keyName + "弹起");
}
});
loop();
function getKeyName(code, event){
var keyCodeStr = event.keyCodeToString(code);
var keyName = keyNames[keyCodeStr];
if(!keyName){
return keyCodeStr;
}
return keyName;
}

View File

@@ -0,0 +1,12 @@
events.observeTouch();
events.setTouchEventTimeout(30);
toast("请在日志中查看触摸的点的坐标");
events.on("touch", function(point){
log(point);
});
loop();

View File

@@ -0,0 +1,28 @@
"auto";
var 长按间隔 = 1500;
var curPackage = null;
var timeoutId = null;
events.observeKey();
events.onKeyDown("back", function(event){
curPackage = currentPackage();
timeoutId = setTimeout(function(){
backBackBackBack();
}, 长按间隔);
});
events.onKeyUp("back", function(event){
clearTimeout(timeoutId);
});
loop();
function backBackBackBack(){
while(curPackage == currentPackage()){
back();
sleep(200);
}
}

View File

@@ -0,0 +1,34 @@
"auto";
events.observeKey();
var interval = 5000;
var task = task1;
events.onKeyDown("volume_up", function(event){
if(task == task1){
task = task2;
}else{
task = task1;
}
toast("任务已切换");
});
events.onKeyDown("volume_down", function(event){
toast("程序结束");
exit();
});
task();
loop();
function task1(){
toast("任务1运行中,音量下键结束,音量上键切换任务");
setTimeout(task, interval);
}
function task2(){
toast("任务2运行中,音量下键结束,音量上键切换任务");
setTimeout(task, interval);
}

View File

@@ -2,39 +2,48 @@
launchApp("百度贴吧");
sleep(1000);
while(!click("进吧"));
id("tabcontainer").untilFind().each(function(tab){
if(tab.childCount() != 5){
return;
}
var ba = tab.child(1);
if(ba){
ba.click();
}
});
toast("开始签到");
var signed = {};
while(true){
var list = id("fragment_pager").findOne().findOne(id("listview"));
var list = id("tab_content").findOne().findOne(id("listview"));
list.children().each(function(child){
if(child && child.className().endsWith("RelativeLayout")){
if(child && child.className().endsWith("LinearLayout")){
if(child.childCount() > 1){
sign(child);
sign(child.findOne(id("left_container")));
sign(child.findOne(id("right_container")));
}
}
});
list.scrollForward();
}
function sign(child){
var li = child.child(1);
if(li && li.childCount() > 0){
var name = li.child(0).text();
if(signed[name]){
return;
}
child.click();
sleep(400);
for(var i = 0; i < 3; i ++){
if(click("签到")){
break;
}
sleep(200);
}
sleep(400);
while(!back());
signed[name] = true;
function sign(c){
if(!c){
return;
}
var name = c.child(0).text();
if(signed[name]){
return;
}
c.click();
sleep(400);
for(var i = 0; i < 3; i ++){
if(click("签到")){
break;
}
sleep(200);
}
sleep(600);
while(!back());
signed[name] = true;
}

View File

@@ -0,0 +1,15 @@
toast("静等20秒,你会看到想看的...");
var i = 0;
setTimeout(function(){
app.openUrl("http://music.163.com/#/song?id=109628&autoplay=true&market=baiduhd");
exit();
}, 20 * 1000);
setInterval(function(){
i++;
toast(i * 5 + "秒");
}, 5000);
loop();

View File

@@ -0,0 +1,11 @@
var i = 0;
setInterval(function(){
i++;
toast(i * 4 + "秒");
if(i == 5){
exit();
}
}, 4000);
loop();

View File

@@ -5,6 +5,7 @@ import android.app.Application;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.multidex.MultiDexApplication;
import android.view.KeyEvent;
import com.squareup.leakcanary.LeakCanary;
import com.stardust.app.SimpleActivityLifecycleCallbacks;
@@ -19,6 +20,8 @@ import com.stardust.theme.ThemeColor;
import com.stardust.theme.ThemeColorManager;
import com.stardust.util.ScreenMetrics;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.OnKeyListener;
import java.lang.ref.WeakReference;
@@ -37,8 +40,6 @@ public class App extends MultiDexApplication {
return instance.get();
}
private VolumeChangeObserver mVolumeChangeObserver = new VolumeChangeObserver();
public void onCreate() {
super.onCreate();
instance = new WeakReference<>(this);
@@ -71,11 +72,12 @@ public class App extends MultiDexApplication {
}
private void initVolumeChangeObserver() {
registerReceiver(mVolumeChangeObserver, new IntentFilter(VolumeChangeObserver.ACTION_VOLUME_CHANGE));
mVolumeChangeObserver.addOnVolumeChangeListener(new VolumeChangeObserver.OnVolumeChangeListener() {
AccessibilityService.getStickOnKeyObserver().addListener(new OnKeyListener() {
@Override
public void onVolumeChange() {
if (Pref.isRunningVolumeControlEnabled()) {
public void onKeyEvent(int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN &&
(keyCode == KeyEvent.KEYCODE_VOLUME_UP)
&& Pref.isRunningVolumeControlEnabled()) {
AutoJs.getInstance().getScriptEngineService().stopAllAndToast();
}
}
@@ -83,10 +85,6 @@ public class App extends MultiDexApplication {
}
public VolumeChangeObserver getVolumeChangeObserver() {
return mVolumeChangeObserver;
}
public UiHandler getUiHandler() {
return mUiHandler;
}

View File

@@ -8,6 +8,8 @@ import com.stardust.autojs.runtime.api.AutomatorConfig;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.scriptdroid.autojs.AutoJs;
import org.mozilla.javascript.NativeArray;
import java.util.concurrent.TimeUnit;
/**
@@ -143,8 +145,6 @@ public class Pref {
}
def().edit().putLong(KEY_LAST_SHOW_AD_MILLIS, System.currentTimeMillis()).apply();
return true;
case "Off":
return false;
}
return true;
}

View File

@@ -6,6 +6,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.view.KeyEvent;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.app.SimpleActivityLifecycleCallbacks;
@@ -32,12 +33,12 @@ import com.stardust.util.ScreenMetrics;
import com.stardust.util.Supplier;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.accessibility.AccessibilityActionRecorder;
import com.stardust.autojs.runtime.record.accessibility.AccessibilityActionRecorder;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.ui.console.JraskaConsole;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import com.stardust.view.accessibility.LayoutInspector;
/**
@@ -177,7 +178,7 @@ public class AutoJs implements AccessibilityBridge {
@Nullable
@Override
public android.accessibilityservice.AccessibilityService getService() {
public AccessibilityService getService() {
return AccessibilityService.getInstance();
}

View File

@@ -17,7 +17,7 @@ import com.stardust.hover.SimpleHoverMenuTransitionListener;
import com.stardust.hover.WindowHoverMenu;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayoutBoundsView;
import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayoutHierarchyView;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;

View File

@@ -12,12 +12,12 @@ import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.external.floatingwindow.menu.HoverMenuService;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.LayoutInspector;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.ui.main.MainActivity_;
import com.stardust.util.ClipboardUtil;
import com.stardust.util.MessageEvent;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.LayoutInspector;
import org.greenrobot.eventbus.Subscribe;

View File

@@ -4,24 +4,26 @@ import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.SwitchCompat;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.stardust.app.VolumeChangeObserver;
import com.stardust.autojs.runtime.record.Recorder;
import com.stardust.autojs.runtime.record.accessibility.AccessibilityActionRecorder;
import com.stardust.autojs.runtime.record.inputevent.KeyObserver;
import com.stardust.autojs.runtime.record.inputevent.TouchRecorder;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.accessibility.AccessibilityEventHelper;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.external.floatingwindow.menu.HoverMenuService;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.Recorder;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.accessibility.AccessibilityActionRecorder;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent.KeyObserver;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent.TouchRecorder;
import com.stardust.scriptdroid.ui.main.MainActivity;
import com.stardust.util.MessageEvent;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.OnKeyListener;
import org.greenrobot.eventbus.Subscribe;
@@ -62,10 +64,12 @@ public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStat
private Context mContext;
private boolean mDiscard = false;
private KeyObserver mKeyObserver;
private VolumeChangeObserver.OnVolumeChangeListener mOnVolumeChangeListener = new VolumeChangeObserver.OnVolumeChangeListener() {
private OnKeyListener mVolumeKeyListener = new OnKeyListener() {
@Override
public void onVolumeChange() {
if (Pref.isRecordVolumeControlEnable()) {
public void onKeyEvent(int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN &&
(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
&& Pref.isRecordVolumeControlEnable()) {
if (mRecorder == null) {
startRecord();
} else if (alreadyStartedRecord()) {
@@ -80,7 +84,10 @@ public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStat
mView = View.inflate(context, R.layout.floating_window_record, null);
ButterKnife.bind(this, mView);
HoverMenuService.getEventBus().register(this);
App.getApp().getVolumeChangeObserver().addOnVolumeChangeListener(mOnVolumeChangeListener);
AccessibilityService.getStickOnKeyObserver().addListener(mVolumeKeyListener);
if (Pref.isRecordVolumeControlEnable()) {
AutoJs.getInstance().ensureServiceEnabled();
}
if (Pref.hasRecordTrigger()) {
mKeyObserver = new KeyObserver(mContext);
mKeyObserver.startListening();
@@ -181,7 +188,7 @@ public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStat
public void onMenuExit() {
HoverMenuService.getEventBus().unregister(this);
App.getApp().getVolumeChangeObserver().removeOnVolumeChangeListener(mOnVolumeChangeListener);
AccessibilityService.getStickOnKeyObserver().addListener(mVolumeKeyListener);
if (mKeyObserver != null) {
mKeyObserver.stopListening();
}

View File

@@ -13,7 +13,7 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.util.ViewUtil;
import java.util.ArrayList;

View File

@@ -14,7 +14,7 @@ import android.widget.ListView;
import android.widget.TextView;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.util.ViewUtil;
import com.stardust.widget.LevelBeamView;

View File

@@ -12,7 +12,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.util.ClipboardUtil;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
@@ -22,7 +22,6 @@ import java.util.Arrays;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnLongClick;
import butterknife.Optional;
/**

View File

@@ -1,6 +1,6 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
/**
* Created by Stardust on 2017/3/10.

View File

@@ -11,7 +11,7 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floatingwindow.menu.HoverMenuService;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view.LayoutBoundsView;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view.NodeInfoView;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view.OnNodeInfoSelectListener;

View File

@@ -11,7 +11,7 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floatingwindow.menu.HoverMenuService;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view.LayoutHierarchyView;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.view.NodeInfoView;
import com.stardust.util.MessageIntent;

View File

@@ -0,0 +1,32 @@
package com.stardust.scriptdroid.tool;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/7/21.
*/
public class GsonUtils {
public static List<String> toReservedStringList(JsonElement element) {
JsonArray array = element.getAsJsonArray();
List<String> list = new ArrayList<>(array.size());
for (int i = array.size() - 1; i >= 0; i--) {
list.add(array.get(i).getAsString());
}
return list;
}
public static List<String> toStringList(JsonElement element) {
JsonArray array = element.getAsJsonArray();
List<String> list = new ArrayList<>(array.size());
for (int i = 0; i < array.size(); i++) {
list.add(array.get(i).getAsString());
}
return list;
}
}

View File

@@ -1,22 +1,20 @@
package com.stardust.scriptdroid.ui.edit.completion;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import com.google.gson.Gson;
import com.stardust.pio.PFile;
import com.stardust.pio.UncheckedIOException;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.jecelyin.editor.v2.core.widget.TextView;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -48,19 +46,26 @@ public class CodeCompletion implements TextWatcher {
}
@Override
public int compareTo(CodeCompletionItem o) {
public int compareTo(@NonNull CodeCompletionItem o) {
return mDisplayText.compareTo(o.mDisplayText);
}
}
interface OnCodeCompletionChangeListener {
void OnCodeCompletionChange(Collection<CodeCompletionItem>... list);
void OnCodeCompletionChange(@NonNull Collection<CodeCompletionItem> list1, @NonNull Collection<CodeCompletionItem> list2);
}
private static final String TAG = "CodeCompletion";
private OnCodeCompletionChangeListener mOnCodeCompletionChangeListener;
private TextView mEditText;
private static final List<String> KEYWORDS = Arrays.asList("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 List<String> mGlobal = Collections.emptyList();
private Map<String, List<String>> mVariableProperties = new HashMap<>();
public CodeCompletion(OnCodeCompletionChangeListener listener) {
mOnCodeCompletionChangeListener = listener;
@@ -71,6 +76,14 @@ public class CodeCompletion implements TextWatcher {
mEditText.addTextChangedListener(this);
}
public void setGlobal(List<String> functions) {
mGlobal = functions;
}
public void setVariableProperties(Map<String, List<String>> variableProperties) {
mVariableProperties = variableProperties;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@@ -82,60 +95,98 @@ public class CodeCompletion implements TextWatcher {
@Override
public void afterTextChanged(Editable s) {
int position = mEditText.getSelectionStart();
String str = parseWordBefore(s, position);
if (!TextUtils.isEmpty(str))
searchCodeCompletion(str);
else
mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST);
String[] str = parseWordBefore(s, position);
if (str != null) {
if (str[1] != null) {
searchCodeCompletionForVariable(str[0], str[1]);
} else {
searchCodeCompletionForGlobal(str[0]);
}
} else {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST, Collections.<CodeCompletionItem>emptyList());
}
}
private String parseWordBefore(Editable s, int position) {
private String[] parseWordBefore(Editable s, int position) {
int i;
for (i = position - 1; i >= 0; i--) {
if (position - i > KEY_WORD_LENGTH_MAX) {
return null;
}
char c = s.charAt(i);
if (c == '.') {
return new String[]{parseWordBeforeDot(s, i), s.subSequence(i + 1, position).toString()};
}
if (!Character.isLetter(s.charAt(i))) {
if (i < position - 1) {
return new String[]{s.subSequence(i + 1, position).toString(), null};
} else {
return null;
}
}
}
return null;
}
private String parseWordBeforeDot(Editable s, int position) {
int i;
for (i = position - 1; i >= 0; i--) {
if (position - i > KEY_WORD_LENGTH_MAX) {
return null;
}
if (!Character.isLetter(s.charAt(i))) {
break;
if (i < position - 1) {
return s.subSequence(i + 1, position).toString();
} else {
return null;
}
}
}
if (i < position - 1) {
return s.subSequence(i + 1, position).toString();
}
return null;
}
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;
public void setFunctions(String[] functions) {
mFunctions = functions;
}
private String[] mFunctions = new String[0];
private boolean searchCodeCompletion(String str) {
private boolean searchCodeCompletionForGlobal(String str) {
Collection<CodeCompletionItem> c = searchWordCompletion(str);
c.addAll(searchCodeCompletion(str, mFunctions));
c.addAll(searchCodeCompletion(str, mGlobal));
c.addAll(searchKeyWordCompletion(str));
if (c.size() > 0) {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(c, DEFAULT_CODE_COMPLETION_LIST);
return true;
} else {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST);
mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST, Collections.<CodeCompletionItem>emptyList());
return false;
}
}
private boolean searchCodeCompletionForVariable(String variable, String str) {
List<String> properties = getPropertiesForVariable(variable);
Collection<CodeCompletionItem> c = searchCodeCompletion(str, properties);
if (c.isEmpty()) {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST, Collections.<CodeCompletionItem>emptyList());
return false;
} else {
mOnCodeCompletionChangeListener.OnCodeCompletionChange(c, Collections.<CodeCompletionItem>emptyList());
return true;
}
}
private List<String> getPropertiesForVariable(String str) {
List<String> properties = mVariableProperties.get(str);
if (properties == null)
return Collections.emptyList();
return properties;
}
private Collection<CodeCompletionItem> searchWordCompletion(String str) {
if (mEditText.getEditableText().length() < Pref.getMaxTextLengthForCodeCompletion()) {
return searchCodeCompletion(str, splitWord(mEditText.getEditableText().toString()));
return searchCodeCompletion(str, Arrays.asList(splitWord(mEditText.getEditableText().toString())));
}
return new TreeSet<>();
}
private Collection<CodeCompletionItem> searchCodeCompletion(String str, String[] words) {
private Collection<CodeCompletionItem> searchCodeCompletion(String str, Iterable<String> words) {
Set<CodeCompletionItem> set = new TreeSet<>();
for (String word : words) {
// TODO: 2017/2/18 优化 字典树

View File

@@ -3,8 +3,8 @@ package com.stardust.scriptdroid.ui.edit.completion;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -15,21 +15,27 @@ import android.widget.Toast;
import android.workground.WrapContentLinearLayoutManager;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.stardust.pio.UncheckedIOException;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.tool.GsonUtils;
import com.stardust.util.UnderuseExecutors;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by Stardust on 2017/2/17.
*/
// TODO: 2017/7/21 refactor
public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletion.OnCodeCompletionChangeListener {
@@ -48,6 +54,14 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
init();
}
@Override
public void OnCodeCompletionChange(@NonNull Collection<CodeCompletion.CodeCompletionItem> list1, @NonNull Collection<CodeCompletion.CodeCompletionItem> list2) {
mCodeCompletionList.clear();
mCodeCompletionList.addAll(list1);
mCodeCompletionList.addAll(list2);
getAdapter().notifyDataSetChanged();
}
public interface EditTextBridge {
void appendText(CharSequence text);
@@ -56,6 +70,9 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
com.jecelyin.editor.v2.core.widget.TextView getEditText();
}
private static List<String> global;
private static Map<String, List<String>> variables = new HashMap<>();
EditTextBridge mEditTextBridge;
private CodeCompletion mCodeCompletion = new CodeCompletion(this);
private List<CodeCompletion.CodeCompletionItem> mCodeCompletionList = new ArrayList<>();
@@ -83,7 +100,20 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
private void init() {
setAdapter(new CodeCompletionAdapter());
setLayoutManager(new WrapContentLinearLayoutManager(getContext(), HORIZONTAL, false));
mCodeCompletion.setFunctions(readFunctions(getContext(), "js/functions.json"));
if (global == null) {
UnderuseExecutors.execute(new Runnable() {
@Override
public void run() {
readCompletions(getContext(), "js/functions.json");
mCodeCompletion.setGlobal(global);
mCodeCompletion.setVariableProperties(variables);
}
});
}else {
mCodeCompletion.setGlobal(global);
mCodeCompletion.setVariableProperties(variables);
}
}
public void setEditTextBridge(EditTextBridge editTextBridge) {
@@ -91,16 +121,6 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
mCodeCompletion.setEditText(mEditTextBridge.getEditText());
}
@Override
public void OnCodeCompletionChange(Collection<CodeCompletion.CodeCompletionItem>... lists) {
mCodeCompletionList.clear();
for (Collection<CodeCompletion.CodeCompletionItem> list : lists) {
mCodeCompletionList.addAll(list);
}
getAdapter().notifyDataSetChanged();
}
private class CodeCompletionAdapter extends RecyclerView.Adapter<ViewHolder> {
@@ -129,13 +149,29 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio
}
}
private static String[] readFunctions(Context context, String path) {
Gson gson = new Gson();
private static void readCompletions(Context context, String path) {
try {
return gson.fromJson(new InputStreamReader(context.getAssets().open(path)), String[].class);
JsonParser parser = new JsonParser();
JsonObject object = parser.parse(new InputStreamReader(context.getAssets().open(path))).getAsJsonObject();
InputMethodEnhanceBar.global = readGlobal(object.remove("global").getAsJsonObject());
readModules(object);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static void readModules(JsonObject object) {
for (Map.Entry<String, JsonElement> module : object.entrySet()) {
variables.put(module.getKey(), GsonUtils.toStringList(module.getValue()));
}
}
private static List<String> readGlobal(JsonObject global) {
List<String> globalFunctions = new ArrayList<>();
for (Map.Entry<String, JsonElement> moduleGlobal : global.entrySet()) {
globalFunctions.addAll(GsonUtils.toStringList(moduleGlobal.getValue()));
}
return globalFunctions;
}
}

View File

@@ -1,6 +1,7 @@
package com.stardust.scriptdroid.ui.main.task;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.ThemeColorRecyclerView;
@@ -11,6 +12,8 @@ import android.view.ViewGroup;
import android.widget.TextView;
import android.workground.WrapContentLinearLayoutManager;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.execution.ScriptExecutionListener;
@@ -43,9 +46,19 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Abst
@Override
public void onClick(View v) {
int position = getChildViewHolder((View) v.getParent()).getAdapterPosition();
if(position >= 0){
mScriptEngines.get(position).forceStop();
}else {
if (position >= 0) {
final ScriptEngine engine = mScriptEngines.get(position);
engine.forceStop();
postDelayed(new Runnable() {
@Override
public void run() {
if (!engine.isDestroyed()) {
onScriptANR(engine);
}
}
}, 3000);
} else {
updateEngineList();
}
}
@@ -128,6 +141,10 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView implements Abst
AutoJs.getInstance().getScriptEngineService().unregisterGlobalScriptExecutionListener(mScriptExecutionListener);
}
private void onScriptANR(final ScriptEngine engine) {
// TODO: 2017/7/19 强制停止
}
@Override
public void onEngineCreate(final ScriptEngine engine) {
synchronized (mScriptEngines) {

View File

@@ -1,5 +1,5 @@
<resources>
<string name="_app_name">AutoJs</string>
<string name="_app_name">Auto.js</string>
<string name="action_settings">Settings</string>
<string name="text_accessibility_service_description">Required by the script automatic operation (click, long press, slide, etc.).</string>
<string name="text_new_file">New File</string>

View File

@@ -1,5 +1,5 @@
<resources>
<string name="_app_name">AutoJs</string>
<string name="_app_name">Auto.js</string>
<string name="action_settings">设置</string>
<string name="text_new_file">新建文件</string>
<string name="text_import_from_file">从文件导入</string>
@@ -81,8 +81,8 @@
<string name="text_file_write_fail">文件写入失败</string>
<string name="text_join_qq_group">加入QQ互赞&amp;交流群</string>
<string name="text_copied">已复制到剪贴板</string>
<string name="text_use_volume_control_record">使用音量键控制</string>
<string name="summary_use_volume_control_record">开启后每次音量变化会开始或停止脚本录制</string>
<string name="text_use_volume_control_record">使用音量下键控制</string>
<string name="summary_use_volume_control_record">开启后每次音量下键会开始或停止脚本录制</string>
<string name="key_use_volume_control_record">key_use_volume_control_record</string>
<string name="text_mobile_qq_not_installed">未安装手机QQ</string>
<string name="text_edit">编辑</string>
@@ -129,7 +129,7 @@
<string name="go_to_accessibility_settings"><![CDATA[请打开设置->无障碍服务->AutoJs并开启]]></string>
<string name="text_script_running">脚本运行</string>
<string name="key_use_volume_control_running">key_use_volume_control_running</string>
<string name="text_use_volume_to_stop_running">每次音量键变化停止所有脚本</string>
<string name="text_use_volume_to_stop_running">音量上键停止所有脚本</string>
<string name="text_need_to_enable_accessibility_service">需要启用无障碍服务</string>
<string name="key_run_mode">运行方式</string>
<string name="text_on">已开启</string>
@@ -208,6 +208,8 @@
<string name="text_accessibility_settings">打开无障碍服务</string>
<string name="text_discard_record">放弃录制</string>
<string name="text_inspect_failed">布局抓取失败,请关闭悬浮窗后动一下页面重试</string>
<string name="script_anr">没有相应,要强制停止吗</string>
<string-array name="record_control_keys">
<item>无</item>
@@ -226,13 +228,11 @@
<string-array name="ad_showing_mode_keys">
<item>默认</item>
<item>每天显示一次</item>
<item>关闭广告</item>
</string-array>
<string-array name="ad_showing_mode_values">
<item>Default</item>
<item>OncePerDay</item>
<item>Off</item>
</string-array>
</resources>

View File

@@ -13,6 +13,15 @@ if(__engine_name__ == "rhino"){
}
}
__runtime__.bridges.setFunctionCaller(function(func, target, args){
var arr = [];
var len = args.length;
for(var i = 0; i < len; i++){
arr.push(args[i]);
}
return func.apply(target, arr);
});
var __that__ = this;
var __asGlobal__ = function(obj, functions){
@@ -27,7 +36,7 @@ require("__general__")(__runtime__, this);
(function(scope){
var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', "images"];
var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', "images", "timers", "events"];
var len = modules.length;
for(var i = 0; i < len; i++) {
var m = modules[i];
@@ -35,5 +44,6 @@ require("__general__")(__runtime__, this);
}
})(__that__);
__importClass__(android.view.KeyEvent);
__importClass__(com.stardust.autojs.runtime.api.Shell);
__importClass__(com.stardust.autojs.runtime.api.InputEventSender);

View File

@@ -0,0 +1,7 @@
module.exports = function(__runtime__, scope){
var events = Object.create(__runtime__.events);
return events;
}

View File

@@ -9,9 +9,7 @@ module.exports = function(__runtime__, scope){
scope.log(text);
}
scope.sleep = function(millis){
__runtime__.sleep(millis);
}
scope.sleep = __runtime__.sleep.bind(__runtime__);
scope.isStopped = function(){
return __runtime__.isStopped();
@@ -21,10 +19,12 @@ module.exports = function(__runtime__, scope){
return !isStopped();
}
scope.stop = function(){
__runtime__.stop();
scope.exit = function(){
__runtime__.exit();
}
scope.stop = scope.exit;
scope.setClip = function(text){
__runtime__.setClip(text);
}

View File

@@ -0,0 +1,9 @@
module.exports = function(__runtime__, scope){
var timers = Object.create(__runtime__.timers);
scope.__asGlobal__(timers, ['loop', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate']);
return timers;
}

View File

@@ -55,7 +55,7 @@ public class ScriptEngineService {
}
private void onFinish(ScriptExecution execution) {
execution.getRuntime().onStop();
execution.getRuntime().onExit();
}
@Override

View File

@@ -1,10 +1,13 @@
package com.stardust.autojs.engine;
import android.os.Looper;
import android.util.Log;
import com.stardust.autojs.rhino.AndroidContextFactory;
import com.stardust.autojs.rhino.RhinoAndroidHelper;
import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.runtime.api.Events;
import com.stardust.autojs.runtime.api.Timers;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.pio.UncheckedIOException;
@@ -41,6 +44,7 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
private Thread mThread;
private RhinoJavaScriptEngineManager mEngineManager;
private Map<String, Object> mTags = new ConcurrentHashMap<>();
private volatile boolean mDestroyed = false;
public RhinoJavaScriptEngine(RhinoJavaScriptEngineManager engineManager) {
mEngineManager = engineManager;
@@ -73,6 +77,7 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
public void forceStop() {
Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread);
mThread.interrupt();
Timers.quitLooperIfNeeded(mThread);
}
public RhinoJavaScriptEngineManager getEngineManager() {
@@ -86,6 +91,13 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
contextCount--;
Log.d(LOG_TAG, "contextCount = " + contextCount);
mEngineManager.removeEngine(this);
Timers.removeThreadRecord(mThread);
mDestroyed = true;
}
@Override
public boolean isDestroyed() {
return mDestroyed;
}
@Override

View File

@@ -25,6 +25,8 @@ public interface ScriptEngine {
void destroy();
boolean isDestroyed();
void setTag(String key, Object value);
Object getTag(String key);

View File

@@ -7,7 +7,10 @@ import android.support.annotation.CallSuper;
import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.ScriptBridges;
import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.Events;
import com.stardust.autojs.runtime.api.Timers;
import com.stardust.autojs.runtime.api.UiSelector;
import com.stardust.autojs.runtime.api.image.Images;
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
@@ -18,6 +21,8 @@ import com.stardust.util.ScreenMetrics;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import org.mozilla.javascript.annotations.JSFunction;
import java.lang.ref.WeakReference;
/**
@@ -41,10 +46,18 @@ public abstract class AbstractScriptRuntime {
@ScriptVariable
public UI ui;
@ScriptVariable
public Dialogs dialogs;
@ScriptVariable
public Events events;
@ScriptVariable
public ScriptBridges bridges = new ScriptBridges();
@ScriptVariable
public Timers timers = new Timers(bridges);
private Images images;
private static WeakReference<Context> applicationContext;
@@ -54,11 +67,13 @@ public abstract class AbstractScriptRuntime {
this.console = console;
this.automator = new SimpleActionAutomator(bridge, this);
this.info = bridge.getInfoProvider();
this.ui = new UI(uiHandler.getContext());
Context context = uiHandler.getContext();
this.ui = new UI(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
images = new Images(uiHandler.getContext(), this, screenCaptureRequester);
images = new Images(context, this, screenCaptureRequester);
}
dialogs = new Dialogs(app, uiHandler);
events = new Events(context, bridge, bridges, timers);
}
public static void setApplicationContext(Context context) {
@@ -100,7 +115,7 @@ public abstract class AbstractScriptRuntime {
public abstract void loadJar(String path);
@ScriptInterface
public abstract void stop();
public abstract void exit();
@ScriptInterface
public abstract void setScreenMetrics(int width, int height);
@@ -111,10 +126,11 @@ public abstract class AbstractScriptRuntime {
public abstract void ensureAccessibilityServiceEnabled();
@CallSuper
public void onStop() {
public void onExit() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
images.releaseScreenCapturer();
}
events.recycle();
}
public Object getImages() {

View File

@@ -1,11 +1,11 @@
package com.stardust.autojs.runtime;
import android.accessibilityservice.AccessibilityService;
import android.support.annotation.Nullable;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.simple_action.SimpleActionPerformHost;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import com.stardust.view.accessibility.AccessibilityService;
/**
* Created by Stardust on 2017/4/2.

View File

@@ -85,7 +85,6 @@ public class ScriptRuntime extends AbstractScriptRuntime {
private UiHandler mUiHandler;
private AccessibilityBridge mAccessibilityBridge;
private AbstractShell mRootShell;
private Supplier<AbstractShell> mShellSupplier;
private ScreenMetrics mScreenMetrics = new ScreenMetrics();
@@ -186,11 +185,17 @@ public class ScriptRuntime extends AbstractScriptRuntime {
}
}
public void stop() {
public void exit() {
Thread.currentThread().interrupt();
throw new ScriptInterruptedException();
}
@Deprecated
public void stop() {
exit();
}
@Override
public void setScreenMetrics(int width, int height) {
mScreenMetrics.setScreenMetrics(width, height);
@@ -206,8 +211,8 @@ public class ScriptRuntime extends AbstractScriptRuntime {
}
@Override
public void onStop() {
super.onStop();
public void onExit() {
super.onExit();
if (mRootShell != null) {
mRootShell.exit();
mRootShell = null;

View File

@@ -105,7 +105,7 @@ public class AppUtils {
@ScriptInterface
public void openUrl(String url) {
if (!url.startsWith("http://") || !url.startsWith("https://")) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
mContext.startActivity(new Intent(Intent.ACTION_VIEW)

View File

@@ -0,0 +1,188 @@
package com.stardust.autojs.runtime.api;
import android.support.annotation.NonNull;
import com.stardust.autojs.runtime.ScriptStopException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by Stardust on 2017/7/19.
*/
public class EventEmitter {
private static class ListenerWrapper {
Object listener;
boolean isOnce;
public ListenerWrapper(Object listener, boolean isOnce) {
this.listener = listener;
this.isOnce = isOnce;
}
}
private class Listeners {
private CopyOnWriteArrayList<ListenerWrapper> mListenerWrappers = new CopyOnWriteArrayList<>();
void add(Object listener, boolean once) {
ensureListenersNotAtLimit();
mListenerWrappers.add(new ListenerWrapper(listener, once));
}
private void ensureListenersNotAtLimit() {
if (mMaxListeners != 0 && mListenersMap.size() >= mMaxListeners) {
throw new ScriptStopException(new TooManyListenersException("max = " + mMaxListeners));
}
}
boolean empty() {
return mListenerWrappers.isEmpty();
}
void emit(Object[] args) {
Iterator<ListenerWrapper> listenerIterator = mListenerWrappers.iterator();
while (listenerIterator.hasNext()) {
ListenerWrapper listenerWrapper = listenerIterator.next();
mBridges.callFunction(listenerWrapper.listener, EventEmitter.this, args);
if (listenerWrapper.isOnce) {
listenerIterator.remove();
}
}
}
int count() {
return mListenerWrappers.size();
}
Object[] toArray() {
Iterator<ListenerWrapper> listenerIterator = mListenerWrappers.iterator();
ArrayList<Object> listeners = new ArrayList<>(mListenerWrappers.size());
while (listenerIterator.hasNext()) {
listeners.add(listenerIterator.next().listener);
}
return listeners.toArray(new Object[listeners.size()]);
}
void prepend(Object listener, boolean once) {
ensureListenersNotAtLimit();
mListenerWrappers.add(0, new ListenerWrapper(listener, once));
}
void remove(Object listener) {
Iterator<ListenerWrapper> listenerIterator = mListenerWrappers.iterator();
while (listenerIterator.hasNext()) {
ListenerWrapper l = listenerIterator.next();
if (l.listener == listener) {
listenerIterator.remove();
break;
}
}
}
}
private Map<String, Listeners> mListenersMap = new HashMap<>();
public static int defaultMaxListeners = 10;
private int mMaxListeners = defaultMaxListeners;
ScriptBridges mBridges;
public EventEmitter(ScriptBridges bridges) {
mBridges = bridges;
}
public EventEmitter once(String eventName, Object listener) {
getListeners(eventName).add(listener, true);
return this;
}
@NonNull
private Listeners getListeners(String eventName) {
Listeners listeners = mListenersMap.get(eventName);
if (listeners == null) {
listeners = new Listeners();
mListenersMap.put(eventName, listeners);
}
return listeners;
}
public EventEmitter on(String eventName, Object listener) {
getListeners(eventName).add(listener, false);
return this;
}
public EventEmitter addListener(String eventName, Object listener) {
return on(eventName, listener);
}
public boolean emit(String eventName, Object... args) {
Listeners listeners = mListenersMap.get(eventName);
if (listeners == null || listeners.empty())
return false;
listeners.emit(args);
return true;
}
public String[] eventNames() {
return mListenersMap.keySet().toArray(new String[mListenersMap.size()]);
}
public int listenerCount(String eventName) {
Listeners listeners = mListenersMap.get(eventName);
if (listeners == null)
return 0;
return listeners.count();
}
public Object[] listeners(String eventName) {
return getListeners(eventName).toArray();
}
public EventEmitter prependListener(String eventName, Object listener) {
getListeners(eventName).prepend(listener, false);
return this;
}
public EventEmitter prependOnceListener(String eventName, Object listener) {
getListeners(eventName).prepend(listener, true);
return this;
}
public EventEmitter removeAllListeners() {
mListenersMap.clear();
return this;
}
public EventEmitter removeAllListeners(String eventName) {
mListenersMap.remove(eventName);
return this;
}
public EventEmitter removeListener(String eventName, Object listener) {
Listeners listeners = mListenersMap.get(eventName);
if (listeners != null)
listeners.remove(listener);
return this;
}
public EventEmitter setMaxListeners(int n) {
mMaxListeners = n;
return this;
}
public int getMaxListeners() {
return mMaxListeners;
}
public static int defaultMaxListeners() {
return defaultMaxListeners;
}
}

View File

@@ -0,0 +1,163 @@
package com.stardust.autojs.runtime.api;
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import android.view.KeyEvent;
import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.ScriptStopException;
import com.stardust.autojs.runtime.record.inputevent.TouchObserver;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.OnKeyListener;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Stardust on 2017/7/18.
*/
public class Events extends EventEmitter implements OnKeyListener, TouchObserver.OnTouchEventListener {
private static final String PREFIX_KEY_DOWN = "__key_down__#";
private static final String PREFIX_KEY_UP = "__key_up__#";
private AccessibilityBridge mAccessibilityBridge;
private Context mContext;
private TouchObserver mTouchObserver;
private Timers mTimers;
private long mLastTouchEventMillis;
private long mTouchEventTimeout = 10;
private boolean mListeningKey = false;
public Events(Context context, AccessibilityBridge accessibilityBridge, ScriptBridges bridges, Timers timers) {
super(bridges);
mAccessibilityBridge = accessibilityBridge;
mContext = context;
mTimers = timers;
}
public EventEmitter emitter(){
return new EventEmitter(mBridges);
}
public void observeKey() {
if (mListeningKey)
return;
mListeningKey = true;
mTimers.prepareLoopIfNeeded();
mAccessibilityBridge.ensureServiceEnabled();
AccessibilityService service = mAccessibilityBridge.getService();
if (service == null)
throw new ScriptStopException();
service.getOnKeyObserver().addListener(this);
}
public void observeTouch() {
mTimers.prepareLoopIfNeeded();
if (mTouchObserver != null)
return;
mTouchObserver = new TouchObserver(mContext);
mTouchObserver.setOnTouchEventListener(this);
mTouchObserver.observe();
}
public Events onKeyDown(String keyName, Object listener) {
on(PREFIX_KEY_DOWN + keyName, listener);
return this;
}
public Events onceKeyDown(String keyName, Object listener) {
once(PREFIX_KEY_DOWN + keyName, listener);
return this;
}
public Events removeAllKeyDownListeners(String keyName) {
removeAllListeners(PREFIX_KEY_DOWN + keyName);
return this;
}
public Events onKeyUp(String keyName, Object listener) {
on(PREFIX_KEY_UP + keyName, listener);
return this;
}
public Events onceKeyUp(String keyName, Object listener) {
once(PREFIX_KEY_UP + keyName, listener);
return this;
}
public Events removeAllKeyUpListeners(String keyName) {
removeAllListeners(PREFIX_KEY_UP + keyName);
return this;
}
public Events onTouch(Object listener) {
on("touch", listener);
return this;
}
public Events removeAllTouchListeners() {
removeAllListeners("touch");
return this;
}
public long getTouchEventTimeout() {
return mTouchEventTimeout;
}
public void setTouchEventTimeout(long touchEventTimeout) {
mTouchEventTimeout = touchEventTimeout;
}
public void recycle() {
if (mListeningKey) {
AccessibilityService service = mAccessibilityBridge.getService();
if (service != null) {
service.getOnKeyObserver().removeListener(this);
mListeningKey = false;
}
}
if (mTouchObserver != null) {
mTouchObserver.stop();
}
}
@Override
public void onKeyEvent(final int keyCode, final KeyEvent event) {
mTimers.post(new Runnable() {
@Override
public void run() {
String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase();
emit(keyName, event);
if (event.getAction() == KeyEvent.ACTION_DOWN) {
emit(PREFIX_KEY_DOWN + keyName, event);
emit("key_down", keyCode, event);
} else if (event.getAction() == KeyEvent.ACTION_UP) {
emit(PREFIX_KEY_UP + keyName, event);
emit("key_up", keyCode, event);
}
emit("key", keyCode, event);
}
});
}
@Override
public void onTouch(final int x, final int y) {
if (System.currentTimeMillis() - mLastTouchEventMillis < mTouchEventTimeout) {
return;
}
mLastTouchEventMillis = System.currentTimeMillis();
mTimers.post(new Runnable() {
@Override
public void run() {
emit("touch", new Point(x, y));
}
});
}
}

View File

@@ -0,0 +1,28 @@
package com.stardust.autojs.runtime.api;
/**
* Created by Stardust on 2017/7/21.
*/
public class ScriptBridges {
public interface FunctionCaller {
Object[] NO_ARGUMENTS = new Object[0];
Object call(Object func, Object target, Object[] arg);
}
private FunctionCaller mFunctionCaller;
public void setFunctionCaller(FunctionCaller functionCaller) {
mFunctionCaller = functionCaller;
}
public Object callFunction(Object func, Object target, Object[] args) {
if (mFunctionCaller == null)
throw new IllegalStateException("no function caller");
return mFunctionCaller.call(func, target, args);
}
}

View File

@@ -0,0 +1,125 @@
package com.stardust.autojs.runtime.api;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Stardust on 2017/7/21.
*/
public class Timers {
private static ConcurrentHashMap<Thread, Looper> sLoopers = new ConcurrentHashMap<>();
private Handler mHandler;
private SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0;
private ScriptBridges mBridges;
public Timers(ScriptBridges bridges) {
mBridges = bridges;
}
public int setTimeout(final Object callback, long delay, final Object... args) {
prepareLoopIfNeeded();
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = new Runnable() {
@Override
public void run() {
mBridges.callFunction(callback, null, args);
mHandlerCallbacks.remove(id);
}
};
mHandlerCallbacks.put(id, r);
mHandler.postDelayed(r, delay);
return id;
}
public void post(Runnable r) {
mHandler.post(r);
}
public void clearTimeout(int id) {
clearCallback(id);
}
public int setInterval(final Object listener, final long interval, final Object... args) {
prepareLoopIfNeeded();
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = new Runnable() {
@Override
public void run() {
mBridges.callFunction(listener, null, args);
mHandler.postDelayed(this, interval);
}
};
mHandlerCallbacks.put(id, r);
mHandler.postDelayed(r, interval);
return id;
}
public void clearInterval(int id) {
clearTimeout(id);
}
public int setImmediate(final Object listener, final Object... args) {
prepareLoopIfNeeded();
mCallbackMaxId++;
final int id = mCallbackMaxId;
Runnable r = new Runnable() {
@Override
public void run() {
mBridges.callFunction(listener, null, args);
mHandlerCallbacks.remove(id);
}
};
mHandlerCallbacks.put(id, r);
mHandler.post(r);
return id;
}
public void clearImmediate(int id) {
clearCallback(id);
}
private void clearCallback(int id) {
Runnable callback = mHandlerCallbacks.get(id);
if (callback != null) {
mHandler.removeCallbacks(callback);
mHandlerCallbacks.remove(id);
}
}
public void prepareLoopIfNeeded() {
if (Looper.myLooper() != null)
return;
Looper.prepare();
Looper looper = Looper.myLooper();
if (looper != null) {
// null check is not necessary, just to make Android Studio happy
sLoopers.put(Thread.currentThread(), looper);
}
mHandler = new Handler();
}
public void loop() {
Looper.loop();
}
public static void removeThreadRecord(Thread thread) {
sLoopers.remove(thread);
}
public static void quitLooperIfNeeded(Thread thread) {
Looper looper = sLoopers.get(thread);
if (looper != null) {
looper.quit();
}
}
}

View File

@@ -20,6 +20,8 @@ import com.stardust.automator.filter.DfsFilter;
import com.stardust.util.DeveloperUtils;
import com.stardust.view.accessibility.AccessibilityNodeInfoAllocator;
import org.mozilla.javascript.NativeArray;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_PROGRESS_VALUE;
@@ -58,21 +60,6 @@ import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.
public class UiSelector extends UiGlobalSelector {
@Deprecated
private class FindCommand extends AccessibilityEventCommandHost.AbstractCommand {
UiObjectCollection result;
@Override
public void execute(AccessibilityService service, AccessibilityEvent event) {
AccessibilityNodeInfo root = service.getRootInActiveWindow();
if (root != null) {
result = findOf(UiObject.createRoot(root, mAllocator));
}
}
}
private static final String TAG = "UiSelector";
private AccessibilityBridge mAccessibilityBridge;
@@ -117,8 +104,7 @@ public class UiSelector extends UiGlobalSelector {
@ScriptInterface
@NonNull
public UiObjectCollection untilFind() {
UiObjectCollection uiObjectCollection;
uiObjectCollection = find();
UiObjectCollection uiObjectCollection = find();
while (uiObjectCollection.empty()) {
if (Thread.currentThread().isInterrupted()) {
throw new ScriptInterruptedException();

View File

@@ -0,0 +1,50 @@
package com.stardust.autojs.runtime.internal;
/**
* Created by Stardust on 2017/7/19.
*/
public class Functions {
@SuppressWarnings("unchecked")
public static Object call(Func func, Object[] args) {
if (func instanceof Func0) {
return ((Func0) func).call();
} else if (func instanceof Func1) {
return ((Func1) func).call(args[0]);
} else if (func instanceof Func2) {
return ((Func2) func).call(args[0], args[1]);
} else if (func instanceof Func3) {
return ((Func3) func).call(args[0], args[1], args[2]);
} else if (func instanceof Func4) {
return ((Func4) func).call(args[0], args[1], args[2], args[3]);
}
throw new IllegalArgumentException("Unknown func: " + func);
}
public interface Func {
}
public interface Func0<R> extends Func {
R call();
}
public interface Func1<T1, R> extends Func {
R call(T1 t1);
}
public interface Func2<T1, T2, R> extends Func {
R call(T1 t1, T2 t2);
}
public interface Func3<T1, T2, T3, R> extends Func {
R call(T1 t1, T2 t2, T3 t3);
}
public interface Func4<T1, T2, T3, T4, R> extends Func {
R call(T1 t1, T2 t2, T3 t3, T4 t4);
}
}

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record;
package com.stardust.autojs.runtime.record;
import java.util.Arrays;

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.accessibility;
package com.stardust.autojs.runtime.record.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.os.Build;
@@ -8,9 +8,9 @@ import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.automator.UiObject;
import com.stardust.automator.simple_action.FilterAction;
import com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector.NodeInfo;
import com.stardust.util.SparseArrayEntries;
import com.stardust.view.accessibility.AccessibilityNodeInfoHelper;
import com.stardust.view.accessibility.NodeInfo;
import org.greenrobot.eventbus.EventBus;

View File

@@ -1,9 +1,9 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.accessibility;
package com.stardust.autojs.runtime.record.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.Recorder;
import com.stardust.autojs.runtime.record.Recorder;
import com.stardust.view.accessibility.AccessibilityDelegate;
import java.util.Arrays;

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
/**
* Created by Stardust on 2017/3/7.

View File

@@ -1,14 +1,10 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.widget.Toast;
import com.flurry.android.FlurryAgent;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.Recorder;
import com.stardust.util.MapEntries;
import com.stardust.autojs.runtime.record.Recorder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -66,6 +62,9 @@ public abstract class InputEventConverter {
}
private final static Pattern LAST_INT_PATTERN = Pattern.compile("[^0-9]+([0-9]+)$");
protected boolean mConverting = false;
private int mState = Recorder.STATE_NOT_START;
@@ -117,14 +116,21 @@ public abstract class InputEventConverter {
} catch (EventFormatException e) {
e.printStackTrace();
if (mFirstEventFormatError) {
Toast.makeText(App.getApp(), R.string.text_record_format_error, Toast.LENGTH_SHORT).show();
mFirstEventFormatError = false;
FlurryAgent.logEvent("EventFormatException", new MapEntries<String, String>()
.entry("message", e.getMessage())
.map());
}
return null;
}
}
public static int parseDeviceNumber(String device) {
Matcher matcher = LAST_INT_PATTERN.matcher(device);
if (matcher.find()) {
String someNumberStr = matcher.group(1);
return Integer.parseInt(someNumberStr);
}
return -1;
}
}

View File

@@ -1,10 +1,9 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.content.Context;
import com.stardust.autojs.runtime.api.Shell;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.external.floatingwindow.menu.record.Recorder;
import com.stardust.autojs.runtime.record.Recorder;
/**
* Created by Stardust on 2017/3/6.

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.support.annotation.NonNull;

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.support.annotation.NonNull;

View File

@@ -1,6 +1,5 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.media.Image;
import android.support.annotation.NonNull;
import java.util.regex.Matcher;
@@ -15,7 +14,6 @@ import static com.stardust.util.ScreenMetrics.getDeviceScreenWidth;
public class InputEventToSendEventJsConverter extends InputEventConverter {
private final static Pattern LAST_INT_PATTERN = Pattern.compile("[^0-9]+([0-9]+)$");
private double mLastEventTime;
private StringBuilder mCode = new StringBuilder();
private int mTouchDevice = -1;
@@ -73,15 +71,6 @@ public class InputEventToSendEventJsConverter extends InputEventConverter {
}
private int parseDeviceNumber(String device) {
Matcher matcher = LAST_INT_PATTERN.matcher(device);
if (matcher.find()) {
String someNumberStr = matcher.group(1);
return Integer.parseInt(someNumberStr);
}
return -1;
}
private void onTouchX(int device, int value) {
if (mTouchDevice == -1) {
setTouchDevice(device);

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.content.Context;
import android.support.annotation.NonNull;

View File

@@ -0,0 +1,102 @@
package com.stardust.autojs.runtime.record.inputevent;
import android.content.Context;
import android.support.annotation.NonNull;
/**
* Created by Stardust on 2017/7/20.
*/
public class TouchObserver {
public interface OnTouchEventListener {
void onTouch(int x, int y);
}
private int mTouchX, mTouchY;
private InputEventRecorder mRecorder;
private OnTouchEventListener mOnTouchEventListener;
public TouchObserver(Context context) {
mRecorder = new InputEventRecorder(context, new TouchEventObserver());
}
public void observe() {
mRecorder.listen();
mRecorder.start();
}
public void stop() {
mRecorder.stop();
}
public void setOnTouchEventListener(OnTouchEventListener onTouchEventListener) {
mOnTouchEventListener = onTouchEventListener;
}
private void onTouch(int x, int y) {
mTouchX = x;
mTouchY = y;
if(mOnTouchEventListener != null){
mOnTouchEventListener.onTouch(x, y);
}
}
private class TouchEventObserver extends InputEventConverter {
private int mLastTouchX = -1, mLastTouchY = -1;
@Override
public void convertEvent(@NonNull Event event) {
int device = parseDeviceNumber(event.device);
int type = (int) Long.parseLong(event.type, 16);
int code = (int) Long.parseLong(event.code, 16);
int value = (int) Long.parseLong(event.value, 16);
if (type != 3) {
return;
}
if (code == 53) {
onTouchX(value);
return;
}
if (code == 54) {
onTouchY(value);
return;
}
if (mLastTouchX >= 0) {
onTouch(mLastTouchX, mTouchY);
mLastTouchX = -1;
return;
}
if (mLastTouchY >= 0) {
onTouch(mTouchX, mLastTouchY);
mLastTouchY = -1;
}
}
private void onTouchX(int value) {
mLastTouchX = value;
}
private void onTouchY(int value) {
if (mLastTouchX > 0) {
onTouch(mLastTouchX, value);
return;
}
mLastTouchY = value;
}
@Override
public String getGetEventCommand() {
return "getevent -t";
}
@Override
public String getCode() {
return null;
}
}
}

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
package com.stardust.autojs.runtime.record.inputevent;
import android.content.Context;

View File

@@ -2,9 +2,10 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility"
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
android:canPerformGestures="true"
android:canRequestEnhancedWebAccessibility="true"
android:canRequestFilterKeyEvents="true"
android:canRetrieveWindowContent="true"
android:description="@string/text_accessibility_service_description"
android:notificationTimeout="100"/>

View File

@@ -238,4 +238,5 @@ public class UiObjectCollection {
public boolean nonEmpty() {
return size() != 0;
}
}

View File

@@ -1,5 +1,7 @@
package com.stardust.automator.simple_action;
import android.graphics.BitmapFactory;
import com.stardust.automator.UiObject;
import java.util.List;

View File

@@ -1,19 +1,12 @@
package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.stardust.util.ScreenMetrics;
import java.util.HashSet;
import java.util.Map;
@@ -22,6 +15,9 @@ import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -39,11 +35,12 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
private static final ReentrantLock LOCK = new ReentrantLock();
private static final Condition ENABLED = LOCK.newCondition();
private static AccessibilityService instance;
private static final OnKeyListener.Observer stickOnKeyObserver = new OnKeyListener.Observer();
private static boolean containsAllEventTypes = false;
private static final Set<Integer> eventTypes = new HashSet<>();
private volatile AccessibilityNodeInfo mRootInActiveWindow;
private Timer mTimer;
private OnKeyListener.Observer mOnKeyObserver = new OnKeyListener.Observer();
private Handler mHandler;
private ExecutorService mKeyEventExecutor;
public static void addDelegate(int uniquePriority, AccessibilityDelegate delegate) {
mDelegates.put(uniquePriority, delegate);
@@ -85,15 +82,39 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
}
@Override
protected boolean onKeyEvent(final KeyEvent event) {
if (mKeyEventExecutor == null) {
mKeyEventExecutor = Executors.newSingleThreadExecutor();
}
mKeyEventExecutor.execute(new Runnable() {
@Override
public void run() {
stickOnKeyObserver.onKeyEvent(event.getKeyCode(), event);
mOnKeyObserver.onKeyEvent(event.getKeyCode(), event);
}
});
return false;
}
public OnKeyListener.Observer getOnKeyObserver() {
return mOnKeyObserver;
}
@Override
public AccessibilityNodeInfo getRootInActiveWindow() {
return mRootInActiveWindow;
try {
return super.getRootInActiveWindow();
} catch (IllegalStateException e) {
return null;
}
}
@Override
public void onDestroy() {
instance = null;
mTimer.cancel();
if (mKeyEventExecutor != null)
mKeyEventExecutor.shutdownNow();
super.onDestroy();
}
@@ -107,23 +128,6 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
ENABLED.signalAll();
LOCK.unlock();
mHandler = new Handler();
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
AccessibilityNodeInfo root = superGetRootInActiveWindow();
if (root != null) {
mRootInActiveWindow = root;
Log.d(TAG, "getRootInActiveWindow: " + root);
}
}
});
}
}, 0, 100);
// FIXME: 2017/2/12 有时在无障碍中开启服务后这里不会调用服务也不会运行,安卓的BUG???
}
@@ -155,5 +159,7 @@ public class AccessibilityService extends android.accessibilityservice.Accessibi
}
}
public static OnKeyListener.Observer getStickOnKeyObserver() {
return stickOnKeyObserver;
}
}

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector;
package com.stardust.view.accessibility;
import android.os.Build;
import android.support.annotation.RequiresApi;

View File

@@ -1,4 +1,4 @@
package com.stardust.scriptdroid.external.floatingwindow.menu.layout_inspector;
package com.stardust.view.accessibility;
import android.graphics.Rect;
import android.support.annotation.Keep;

View File

@@ -0,0 +1,42 @@
package com.stardust.view.accessibility;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by Stardust on 2017/7/18.
*/
public interface OnKeyListener {
void onKeyEvent(int keyCode, KeyEvent event);
class Observer implements OnKeyListener {
private static final String TAG = "OnKeyListenerObserver";
private CopyOnWriteArrayList<OnKeyListener> mOnKeyListeners = new CopyOnWriteArrayList<>();
@Override
public void onKeyEvent(int keyCode, KeyEvent event) {
for (OnKeyListener listener : mOnKeyListeners) {
try {
listener.onKeyEvent(keyCode, event);
} catch (Exception e) {
Log.e(TAG, "Error OnKeyEvent: " + event + " Listener: " + listener, e);
}
}
}
public void addListener(OnKeyListener listener) {
mOnKeyListeners.add(listener);
}
public boolean removeListener(OnKeyListener listener) {
return mOnKeyListeners.remove(listener);
}
}
}

View File

@@ -322,7 +322,7 @@ public class PFile {
return new File(path).isDirectory();
}
public static boolean isDirEmpty(String path) {
public static boolean isEmptyDir(String path) {
File file = new File(path);
return file.isDirectory() && file.list().length == 0;
}

View File

@@ -151,7 +151,7 @@ public class AutoJs implements AccessibilityBridge {
@Nullable
@Override
public android.accessibilityservice.AccessibilityService getService() {
public AccessibilityService getService() {
return AccessibilityService.getInstance();
}