@@ -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 {
|
||||
|
||||
@@ -49,6 +49,16 @@
|
||||
"type": "markdown",
|
||||
"path":"documentation"
|
||||
},
|
||||
{
|
||||
"title": "事件与按键、触摸监听",
|
||||
"type": "markdown",
|
||||
"path":"documentation"
|
||||
},
|
||||
{
|
||||
"title": "定时器",
|
||||
"type": "markdown",
|
||||
"path":"documentation"
|
||||
},
|
||||
{
|
||||
"title": "shell命令",
|
||||
"type": "markdown",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
### sleep(n)
|
||||
### sleep(\[n\])
|
||||
* n \<Number\> 毫秒数
|
||||
|
||||
暂停运行n**毫秒**的时间。1秒等于1000毫秒。
|
||||
如果不加参数n,则会一直暂停,通常用于有事件监听的脚本中使用。
|
||||
|
||||
### launchPackage(packageName)
|
||||
* packageName \<String\> 应用包名。参见《Shell命令:应用包名》章节。
|
||||
|
||||
380
app/src/main/assets/help/documentation/事件与按键、触摸监听.md
Normal file
380
app/src/main/assets/help/documentation/事件与按键、触摸监听.md
Normal 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"。
|
||||
66
app/src/main/assets/help/documentation/定时器.md
Normal file
66
app/src/main/assets/help/documentation/定时器.md
Normal 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 对象。
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
返回路径path是否是文件夹。
|
||||
|
||||
### files.isDirEmpty(path)
|
||||
### files.isEmptyDir(path)
|
||||
* path \<String\> 路径
|
||||
|
||||
返回文件夹path是否为空文件夹。如果该路径并非文件夹,则直接返回false。
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ while(true){
|
||||
addAsFriend();
|
||||
sleep(延迟);
|
||||
}
|
||||
list.scrollForward();
|
||||
}
|
||||
|
||||
function isGroupMember(child){
|
||||
|
||||
@@ -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空间并慢慢下滑");
|
||||
|
||||
@@ -7,5 +7,4 @@ sleep(500);
|
||||
while(true){
|
||||
setText("我喜欢你");
|
||||
click("发送");
|
||||
sleep(200);
|
||||
}
|
||||
10
app/src/main/assets/sample/QQ与微信/自动取消金豆.js
Normal file
10
app/src/main/assets/sample/QQ与微信/自动取消金豆.js
Normal file
@@ -0,0 +1,10 @@
|
||||
"auto";
|
||||
|
||||
toast("本脚本需要和名片点赞脚本同时运行才有效");
|
||||
|
||||
while(true){
|
||||
if(currentActivity() == "com.tencent.mobileqq.activity.VisitorsActivity"){
|
||||
click("取消");
|
||||
sleep(300);
|
||||
}
|
||||
}
|
||||
34
app/src/main/assets/sample/事件与按键、触摸监听/按键监听.js
Normal file
34
app/src/main/assets/sample/事件与按键、触摸监听/按键监听.js
Normal 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;
|
||||
}
|
||||
12
app/src/main/assets/sample/事件与按键、触摸监听/触摸监听.js
Normal file
12
app/src/main/assets/sample/事件与按键、触摸监听/触摸监听.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
events.observeTouch();
|
||||
|
||||
events.setTouchEventTimeout(30);
|
||||
|
||||
toast("请在日志中查看触摸的点的坐标");
|
||||
|
||||
events.on("touch", function(point){
|
||||
log(point);
|
||||
});
|
||||
|
||||
loop();
|
||||
28
app/src/main/assets/sample/事件与按键、触摸监听/长按返回退出当前程序.js
Normal file
28
app/src/main/assets/sample/事件与按键、触摸监听/长按返回退出当前程序.js
Normal 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);
|
||||
}
|
||||
}
|
||||
34
app/src/main/assets/sample/事件与按键、触摸监听/音量键控制程序.js
Normal file
34
app/src/main/assets/sample/事件与按键、触摸监听/音量键控制程序.js
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
15
app/src/main/assets/sample/定时器/定时执行.js
Normal file
15
app/src/main/assets/sample/定时器/定时执行.js
Normal 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();
|
||||
11
app/src/main/assets/sample/定时器/循环执行.js
Normal file
11
app/src/main/assets/sample/定时器/循环执行.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var i = 0;
|
||||
|
||||
setInterval(function(){
|
||||
i++;
|
||||
toast(i * 4 + "秒");
|
||||
if(i == 5){
|
||||
exit();
|
||||
}
|
||||
}, 4000);
|
||||
|
||||
loop();
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 优化 字典树
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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互赞&交流群</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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
7
autojs/src/main/assets/modules/__events__.js
Normal file
7
autojs/src/main/assets/modules/__events__.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
module.exports = function(__runtime__, scope){
|
||||
var events = Object.create(__runtime__.events);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
9
autojs/src/main/assets/modules/__timers__.js
Normal file
9
autojs/src/main/assets/modules/__timers__.js
Normal 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;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ScriptEngineService {
|
||||
}
|
||||
|
||||
private void onFinish(ScriptExecution execution) {
|
||||
execution.getRuntime().onStop();
|
||||
execution.getRuntime().onExit();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,6 +25,8 @@ public interface ScriptEngine {
|
||||
|
||||
void destroy();
|
||||
|
||||
boolean isDestroyed();
|
||||
|
||||
void setTag(String key, Object value);
|
||||
|
||||
Object getTag(String key);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
163
autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java
Normal file
163
autojs/src/main/java/com/stardust/autojs/runtime/api/Events.java
Normal 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
125
autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java
Normal file
125
autojs/src/main/java/com/stardust/autojs/runtime/api/Timers.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.stardust.scriptdroid.external.floatingwindow.menu.record;
|
||||
package com.stardust.autojs.runtime.record;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.stardust.scriptdroid.external.floatingwindow.menu.record.inputevent;
|
||||
package com.stardust.autojs.runtime.record.inputevent;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -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"/>
|
||||
@@ -238,4 +238,5 @@ public class UiObjectCollection {
|
||||
public boolean nonEmpty() {
|
||||
return size() != 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.stardust.automator.simple_action;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import com.stardust.automator.UiObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public class AutoJs implements AccessibilityBridge {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public android.accessibilityservice.AccessibilityService getService() {
|
||||
public AccessibilityService getService() {
|
||||
return AccessibilityService.getInstance();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user