diff --git a/app/build.gradle b/app/build.gradle
index d98cb5dd..68f0e579 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.stardust.scriptdroid"
minSdkVersion 17
targetSdkVersion 23
- versionCode 226
- versionName "3.0.0 Alpha26"
+ versionCode 227
+ versionName "3.0.0 Alpha27"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
@@ -115,8 +115,6 @@ dependencies {
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
- //JDeferred
- compile 'org.jdeferred:jdeferred-android-aar:1.2.6'
//Glide
compile('com.github.bumptech.glide:glide:4.2.0', {
exclude group: 'com.android.support'
diff --git a/app/src/main/assets/sample/对话框/UI模式下使用对话框.js b/app/src/main/assets/sample/对话框/UI模式下使用对话框.js
new file mode 100644
index 00000000..2c5d31cb
--- /dev/null
+++ b/app/src/main/assets/sample/对话框/UI模式下使用对话框.js
@@ -0,0 +1,70 @@
+"ui";
+
+ui.layout(
+
+
+
+
+
+);
+
+ui.callback.click(()=>{
+ dialogs.confirm("要弹出输入框吗?", "", function(b){
+ if(b){
+ dialogs.rawInput("输入", "", function(str){
+ alert("您输入的是:" + str);
+ });
+ }else{
+ ui.finish();
+ }
+ });
+});
+
+ui.promise.click(()=>{
+ dialogs.confirm("要弹出输入框吗")
+ .then(function(b){
+ if(b){
+ return dialogs.rawInput("输入");
+ }else{
+ ui.finish();
+ }
+ }).then(function(str){
+ alert("您输入的是:" + str);
+ });
+});
+
+
+ui.calc.click(()=>{
+ let num1, num2, op;
+ dialogs.input("请输入第一个数字")
+ .then(n => {
+ num1 = n;
+ return dialogs.singleChoice("请选择运算", ["加", "减", "乘", "除", "幂"]);
+ })
+ .then(o => {
+ op = o;
+ return dialogs.input("请输入第二个数字");
+ })
+ .then(n => {
+ num2 = n;
+ var result;
+ switch(op){
+ case 0:
+ result = num1 + num2;
+ break;
+ case 1:
+ result = num1 - num2;
+ break;
+ case 2:
+ result = num1 * num2;
+ break;
+ case 3:
+ result = num1 / num2;
+ break;
+ case 4:
+ result = Math.pow(num1, num2);
+ break;
+ }
+ alert("运算结果", result);
+ });
+});
\ No newline at end of file
diff --git a/autojs/build.gradle b/autojs/build.gradle
index fb6ec486..6ca9c70f 100644
--- a/autojs/build.gradle
+++ b/autojs/build.gradle
@@ -51,6 +51,9 @@ dependencies {
compile 'com.github.hyb1996:EnhancedFloaty:0.17'
compile 'com.github.hyb1996:OpenCvLib:2.4.13.4-imgproc'
compile 'com.makeramen:roundedimageview:2.3.0'
+ //JDeferred
+ compile 'org.jdeferred:jdeferred-android-aar:1.2.6'
+
// Gson
compile 'com.google.code.gson:gson:2.8.0'
// Terminal emulator
diff --git a/autojs/src/main/assets/javascript_engine_init.js b/autojs/src/main/assets/javascript_engine_init.js
index 98eaaee2..062f8a9f 100644
--- a/autojs/src/main/assets/javascript_engine_init.js
+++ b/autojs/src/main/assets/javascript_engine_init.js
@@ -46,8 +46,9 @@ __runtime__.bridges.setBridges({
});
var __that__ = this;
-JSON = require('__json2__.js');
-util = require('__util__.js');
+var Promise = require('promise.js');
+var JSON = require('__json2__.js');
+var util = require('__util__.js');
var __asGlobal__ = function(obj, functions){
var len = functions.length;
diff --git a/autojs/src/main/assets/modules/__dialogs__.js b/autojs/src/main/assets/modules/__dialogs__.js
index 6f54be53..fa84a3d0 100644
--- a/autojs/src/main/assets/modules/__dialogs__.js
+++ b/autojs/src/main/assets/modules/__dialogs__.js
@@ -2,43 +2,107 @@
module.exports = function(__runtime__, scope){
var dialogs = {};
- dialogs.rawInput = function(title, prefill){
+ dialogs.rawInput = function(title, prefill, callback){
prefill = prefill || "";
- var s = __runtime__.dialogs.rawInput(title, prefill);
- return s ? String(s) : null;
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().rawInput(title, prefill, function(){
+ resolve.apply(null, Array.prototype.slice.call(arguments));
+ });
+ });
+ }
+ return rtDialogs().rawInput(title, prefill, callback ? callback : null);
};
- dialogs.input = function(title, prefill){
- return eval(dialogs.rawInput(title, prefill));
+ dialogs.input = function(title, prefill, callback){
+ prefill = prefill || "";
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().rawInput(title, prefill, function(str){
+ resolve(eval(str));
+ });
+ });
+ }
+ if(callback){
+ dialogs.rawInput(title, prefill, function(str){
+ callback(eval(str));
+ });
+ return;
+ }
+ return eval(dialogs.rawInput(title, prefill), callback ? callback : null);
}
dialogs.prompt = dialogs.rawInput;
- dialogs.alert = function(title, prefill){
+ dialogs.alert = function(title, prefill, callback){
prefill = prefill || "";
- return __runtime__.dialogs.alert(title, prefill);
- }
-
- dialogs.confirm = function(title, prefill){
- prefill = prefill || "";
- return __runtime__.dialogs.confirm(title, prefill);
- }
-
- dialogs.select = function(title, items){
- if(items instanceof Array){
- return __runtime__.dialogs.select(title, items);
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().alert(title, prefill, function(){
+ resolve.apply(null, Array.prototype.slice.call(arguments));
+ });
+ });
}
- return __runtime__.dialogs.select(title, [].slice.call(arguments, 1));
+ return rtDialogs().alert(title, prefill, callback ? callback : null);
}
- dialogs.singleChoice = function(title, items, index){
+ dialogs.confirm = function(title, prefill, callback){
+ prefill = prefill || "";
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().confirm(title, prefill, function(){
+ resolve.apply(null, Array.prototype.slice.call(arguments));
+ });
+ });
+ }
+ return rtDialogs().confirm(title, prefill, callback ? callback : null);
+ }
+
+ dialogs.select = function(title, items, callback){
+ if(items instanceof Array){
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().select(title, items, function(){
+ resolve.apply(null, Array.prototype.slice.call(arguments));
+ });
+ });
+ }
+ return rtDialogs().select(title, items, callback ? callback : null);
+ }
+ return rtDialogs().select(title, [].slice.call(arguments, 1));
+ }
+
+ dialogs.singleChoice = function(title, items, index, callback){
index = index || 0;
- return __runtime__.dialogs.singleChoice(title, index, items);
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().singleChoice(title, index, items, function(){
+ resolve.apply(null, Array.prototype.slice.call(arguments));
+ });
+ });
+ }
+ return rtDialogs().singleChoice(title, index, items, callback ? callback : null);
}
- dialogs.multiChoice = function(title, items, index){
+ dialogs.multiChoice = function(title, items, index, callback){
index = index || [];
- var javaArray = __runtime__.dialogs.multiChoice(title, index, items);
+ if(isUiThread() && !callback){
+ return new Promise(function(resolve, reject){
+ rtDialogs().singleChoice(title, index, items, function(r){
+ resolve.apply(null, toJsArray(r));
+ });
+ });
+ }
+ if(callback){
+ return rtDialogs().multiChoice(title, index, items, function(r){
+ callback(toJsArray(r));
+ });
+ }
+ return toJsArray(rtDialogs().multiChoice(title, index, items, null));
+
+ }
+
+ function toJsArray(javaArray){
var jsArray = [];
var len = javaArray.length;
for (var i = 0;i < len;i++){
@@ -47,21 +111,26 @@ module.exports = function(__runtime__, scope){
return jsArray;
}
- scope.rawInput = function(title, prefill){
- return dialogs.rawInput(title, prefill);
+ function rtDialogs(){
+ var d = __runtime__.dialogs;
+ if(!isUiThread()){
+ return d.nonUiDialogs;
+ }else{
+ return d;
+ }
}
- scope.alert = function(title, prefill){
- dialogs.alert(title, prefill);
+ function isUiThread(){
+ return android.os.Looper.myLooper() == android.os.Looper.getMainLooper();
}
- scope.confirm = function(title, prefill){
- return dialogs.confirm(title, prefill);
- }
+ scope.rawInput = dialogs.rawInput.bind(dialogs);
- scope.prompt = function(title, prefill){
- return dialogs.prompt(title, prefill);
- }
+ scope.alert = dialogs.alert.bind(dialogs);
+
+ scope.confirm = dialogs.confirm.bind(dialogs);
+
+ scope.prompt = dialogs.prompt.bind(dialogs);
return dialogs;
}
\ No newline at end of file
diff --git a/autojs/src/main/assets/modules/promise.js b/autojs/src/main/assets/modules/promise.js
new file mode 100644
index 00000000..8412d601
--- /dev/null
+++ b/autojs/src/main/assets/modules/promise.js
@@ -0,0 +1,215 @@
+'use strict';
+
+function asap(action){
+ action();
+}
+
+function noop() {}
+
+// States:
+//
+// 0 - pending
+// 1 - fulfilled with _value
+// 2 - rejected with _value
+// 3 - adopted the state of another promise, _value
+//
+// once the state is no longer pending (0) it is immutable
+
+// All `_` prefixed properties will be reduced to `_{random number}`
+// at build time to obfuscate them and discourage their use.
+// We don't use symbols or Object.defineProperty to fully hide them
+// because the performance isn't good enough.
+
+
+// to avoid using try/catch inside critical functions, we
+// extract them to here.
+var LAST_ERROR = null;
+var IS_ERROR = {};
+function getThen(obj) {
+ try {
+ return obj.then;
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+
+function tryCallOne(fn, a) {
+ try {
+ return fn(a);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+function tryCallTwo(fn, a, b) {
+ try {
+ fn(a, b);
+ } catch (ex) {
+ LAST_ERROR = ex;
+ return IS_ERROR;
+ }
+}
+
+module.exports = Promise;
+
+function Promise(fn) {
+ if (typeof this !== 'object') {
+ throw new TypeError('Promises must be constructed via new');
+ }
+ if (typeof fn !== 'function') {
+ throw new TypeError('Promise constructor\'s argument is not a function');
+ }
+ this._deferredState = 0;
+ this._state = 0;
+ this._value = null;
+ this._deferreds = null;
+ if (fn === noop) return;
+ doResolve(fn, this);
+}
+Promise._onHandle = null;
+Promise._onReject = null;
+Promise._noop = noop;
+
+Promise.prototype.then = function(onFulfilled, onRejected) {
+ if (this.constructor !== Promise) {
+ return safeThen(this, onFulfilled, onRejected);
+ }
+ var res = new Promise(noop);
+ handle(this, new Handler(onFulfilled, onRejected, res));
+ return res;
+};
+
+function safeThen(self, onFulfilled, onRejected) {
+ return new self.constructor(function (resolve, reject) {
+ var res = new Promise(noop);
+ res.then(resolve, reject);
+ handle(self, new Handler(onFulfilled, onRejected, res));
+ });
+}
+function handle(self, deferred) {
+ while (self._state === 3) {
+ self = self._value;
+ }
+ if (Promise._onHandle) {
+ Promise._onHandle(self);
+ }
+ if (self._state === 0) {
+ if (self._deferredState === 0) {
+ self._deferredState = 1;
+ self._deferreds = deferred;
+ return;
+ }
+ if (self._deferredState === 1) {
+ self._deferredState = 2;
+ self._deferreds = [self._deferreds, deferred];
+ return;
+ }
+ self._deferreds.push(deferred);
+ return;
+ }
+ handleResolved(self, deferred);
+}
+
+function handleResolved(self, deferred) {
+ asap(function() {
+ var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+ if (cb === null) {
+ if (self._state === 1) {
+ resolve(deferred.promise, self._value);
+ } else {
+ reject(deferred.promise, self._value);
+ }
+ return;
+ }
+ var ret = tryCallOne(cb, self._value);
+ if (ret === IS_ERROR) {
+ reject(deferred.promise, LAST_ERROR);
+ } else {
+ resolve(deferred.promise, ret);
+ }
+ });
+}
+function resolve(self, newValue) {
+ // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+ if (newValue === self) {
+ return reject(
+ self,
+ new TypeError('A promise cannot be resolved with itself.')
+ );
+ }
+ if (
+ newValue &&
+ (typeof newValue === 'object' || typeof newValue === 'function')
+ ) {
+ var then = getThen(newValue);
+ if (then === IS_ERROR) {
+ return reject(self, LAST_ERROR);
+ }
+ if (
+ then === self.then &&
+ newValue instanceof Promise
+ ) {
+ self._state = 3;
+ self._value = newValue;
+ finale(self);
+ return;
+ } else if (typeof then === 'function') {
+ doResolve(then.bind(newValue), self);
+ return;
+ }
+ }
+ self._state = 1;
+ self._value = newValue;
+ finale(self);
+}
+
+function reject(self, newValue) {
+ self._state = 2;
+ self._value = newValue;
+ if (Promise._onReject) {
+ Promise._onReject(self, newValue);
+ }
+ finale(self);
+}
+function finale(self) {
+ if (self._deferredState === 1) {
+ handle(self, self._deferreds);
+ self._deferreds = null;
+ }
+ if (self._deferredState === 2) {
+ for (var i = 0; i < self._deferreds.length; i++) {
+ handle(self, self._deferreds[i]);
+ }
+ self._deferreds = null;
+ }
+}
+
+function Handler(onFulfilled, onRejected, promise){
+ this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+ this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+ this.promise = promise;
+}
+
+/**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+function doResolve(fn, promise) {
+ var done = false;
+ var res = tryCallTwo(fn, function (value) {
+ if (done) return;
+ done = true;
+ resolve(promise, value);
+ }, function (reason) {
+ if (done) return;
+ done = true;
+ reject(promise, reason);
+ });
+ if (!done && res === IS_ERROR) {
+ done = true;
+ reject(promise, LAST_ERROR);
+ }
+ }
\ No newline at end of file
diff --git a/autojs/src/main/java/com/stardust/autojs/core/inputevent/RootAutomator.java b/autojs/src/main/java/com/stardust/autojs/core/inputevent/RootAutomator.java
index e69ed078..bd5e3cfd 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/inputevent/RootAutomator.java
+++ b/autojs/src/main/java/com/stardust/autojs/core/inputevent/RootAutomator.java
@@ -164,8 +164,8 @@ public class RootAutomator {
public void touchUp(int id) throws IOException {
sendEvent(EV_ABS, ABS_MT_TRACKING_ID, id);
- // sendEvent(EV_KEY, BTN_TOUCH, 0x00000000);
- // sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000);
+ sendEvent(EV_KEY, BTN_TOUCH, 0x00000000);
+ sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000);
sendEvent(EV_SYN, SYN_REPORT, 0x00000000);
}
diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/BlockedMaterialDialog.java b/autojs/src/main/java/com/stardust/autojs/core/ui/BlockedMaterialDialog.java
index 1577fcfe..17e5f8f8 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/ui/BlockedMaterialDialog.java
+++ b/autojs/src/main/java/com/stardust/autojs/core/ui/BlockedMaterialDialog.java
@@ -3,8 +3,7 @@ package com.stardust.autojs.core.ui;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
-import android.content.DialogInterface;
-import android.support.annotation.NonNull;
+import android.os.Looper;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.WindowManager;
@@ -12,9 +11,14 @@ import android.view.WindowManager;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
-import com.stardust.concurrent.VolatileBox;
+import com.stardust.autojs.runtime.ScriptBridges;
+import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
+import com.stardust.concurrent.VolatileDispose;
+import com.stardust.util.ArrayUtils;
import com.stardust.util.UiHandler;
+import org.jdeferred.impl.DeferredObject;
+
/**
* Created by Stardust on 2017/5/8.
*/
@@ -48,98 +52,121 @@ public class BlockedMaterialDialog extends MaterialDialog {
public static class Builder extends MaterialDialog.Builder {
+ private VolatileDispose