From 1413bf21a5caea048069dc9936366b2eb6740690 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Fri, 1 Dec 2017 20:52:51 +0800 Subject: [PATCH] add: promise support; fix: dialogs and setClip ANR on ui thread; --- app/build.gradle | 6 +- .../sample/对话框/UI模式下使用对话框.js | 70 ++++++ autojs/build.gradle | 3 + .../src/main/assets/javascript_engine_init.js | 5 +- autojs/src/main/assets/modules/__dialogs__.js | 133 ++++++++--- autojs/src/main/assets/modules/promise.js | 215 ++++++++++++++++++ .../autojs/core/inputevent/RootAutomator.java | 4 +- .../autojs/core/ui/BlockedMaterialDialog.java | 175 ++++++++------ .../autojs/core/ui/xml/AttributeHandler.java | 2 +- .../autojs/runtime/ScriptBridges.java | 4 +- .../autojs/runtime/ScriptRuntime.java | 30 +-- .../stardust/autojs/runtime/api/Dialogs.java | 144 ++++++++---- 12 files changed, 607 insertions(+), 184 deletions(-) create mode 100644 app/src/main/assets/sample/对话框/UI模式下使用对话框.js create mode 100644 autojs/src/main/assets/modules/promise.js 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 mResultBox; private UiHandler mUiHandler; + private Object mCallback; + private ScriptBridges mScriptBridges; + private boolean mNotified = false; - public Builder(Context context, UiHandler uiHandler) { + public Builder(Context context, UiHandler uiHandler, ScriptBridges scriptBridges, Object callback) { super(context); super.theme(Theme.LIGHT); mUiHandler = uiHandler; + mScriptBridges = scriptBridges; + mCallback = callback; + if (Looper.getMainLooper() != Looper.myLooper()) { + mResultBox = new VolatileDispose<>(); + } } - public MaterialDialog.Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, boolean allowEmptyInput, final VolatileBox result) { - dismissListener(result); - super.input(hint, prefill, allowEmptyInput, new MaterialDialog.InputCallback() { - @Override - public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { - result.set(input.toString()); - synchronized (result) { - result.notify(); - } + public MaterialDialog.Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, boolean allowEmptyInput) { + super.input(hint, prefill, allowEmptyInput, (dialog, input) -> setAndNotify(input.toString())); + return this; + } + + private void setAndNotify(Object r) { + if (mNotified) { + return; + } + mNotified = true; + if (mCallback != null) { + mScriptBridges.callFunction(mCallback, null, new Object[]{r}); + } + if (mResultBox != null) { + mResultBox.setAndNotify(r); + } + } + + private void setAndNotify(int r) { + if (mNotified) { + return; + } + mNotified = true; + if (mCallback != null) { + mScriptBridges.callFunction(mCallback, null, new int[]{r}); + } + if (mResultBox != null) { + mResultBox.setAndNotify(r); + } + } + + private void setAndNotify(boolean r) { + if (mNotified) { + return; + } + mNotified = true; + if (mCallback != null) { + mScriptBridges.callFunction(mCallback, null, new boolean[]{r}); + } + if (mResultBox != null) { + mResultBox.setAndNotify(r); + } + } + + public Builder alert() { + dismissListener(dialog -> setAndNotify(null)); + onAny((dialog, which) -> setAndNotify(null)); + return this; + } + + public Builder confirm() { + dismissListener(dialog -> setAndNotify(false)); + onAny((dialog, which) -> { + if (which == DialogAction.POSITIVE) { + setAndNotify(true); + } else { + setAndNotify(false); } }); return this; } - public Builder confirm(final VolatileBox result) { - onAny(new SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - if (which == DialogAction.POSITIVE) { - result.setAndNotify(true); - } else { - result.setAndNotify(false); - } - } + public MaterialDialog.Builder itemsCallback() { + dismissListener(dialog -> setAndNotify(-1)); + super.itemsCallback((dialog, itemView, position, text) -> setAndNotify(position)); + return this; + } + + public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices) { + dismissListener(dialog -> setAndNotify(new Integer[0])); + super.itemsCallbackMultiChoice(selectedIndices, (dialog, which, text) -> { + setAndNotify(ArrayUtils.unbox(which)); + return true; }); return this; } - public MaterialDialog.Builder itemsCallback(final VolatileBox result) { - dismissListener(result); - super.itemsCallback(new ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text) { - result.setAndNotify(position); - } + public MaterialDialog.Builder itemsCallbackSingleChoice(int selectedIndex) { + dismissListener(dialog -> setAndNotify(-1)); + super.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> { + setAndNotify(which); + return true; }); return this; } - public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices, final VolatileBox result) { - dismissListener(result); - super.itemsCallbackMultiChoice(selectedIndices, new ListCallbackMultiChoice() { - @Override - public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) { - result.setAndNotify(which); - return true; - } - }); - return this; - } - public MaterialDialog.Builder itemsCallbackSingleChoice(int selectedIndex, final VolatileBox result) { - dismissListener(result); - super.itemsCallbackSingleChoice(selectedIndex, new ListCallbackSingleChoice() { - @Override - public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { - result.setAndNotify(which); - return true; - } - }); - return this; - } - - public Builder dismissListener(final VolatileBox result) { - super.dismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - synchronized (result) { - result.notify(); - } - } - }); - return this; - } - - @Override - public MaterialDialog show() { - mUiHandler.post(new Runnable() { - @Override - public void run() { - Builder.super.show(); - } - }); - return null; + public Object showAndGet() { + if (Looper.myLooper() == Looper.getMainLooper()) { + super.show(); + } else { + mUiHandler.post(Builder.super::show); + } + if (mResultBox != null) { + return mResultBox.blockedGetOrThrow(ScriptInterruptedException.class); + } else { + return null; + } } @Override diff --git a/autojs/src/main/java/com/stardust/autojs/core/ui/xml/AttributeHandler.java b/autojs/src/main/java/com/stardust/autojs/core/ui/xml/AttributeHandler.java index 477b931f..1fc0f34e 100644 --- a/autojs/src/main/java/com/stardust/autojs/core/ui/xml/AttributeHandler.java +++ b/autojs/src/main/java/com/stardust/autojs/core/ui/xml/AttributeHandler.java @@ -49,7 +49,7 @@ public interface AttributeHandler { if (!attr.getNodeName().equals("style")) { layoutXml.append("android:"); } - layoutXml.append(mapAttrName(nodeName, attr.getLocalName())) + layoutXml.append(mapAttrName(nodeName, attr.getNodeName())) .append("=\"").append(mapAttrValue(nodeName, attr.getNodeName(), attr.getNodeValue())).append("\"\n"); return true; } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java index 120497c3..570088cf 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptBridges.java @@ -13,7 +13,7 @@ public class ScriptBridges { Object[] NO_ARGUMENTS = new Object[0]; - Object call(Object func, Object target, Object[] arg); + Object call(Object func, Object target, Object arg); Object toArray(Object o); @@ -26,7 +26,7 @@ public class ScriptBridges { mBridges = bridges; } - public Object callFunction(Object func, Object target, Object[] args) { + public Object callFunction(Object func, Object target, Object args) { checkBridges(); return mBridges.call(func, target, args); } diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java index f1e78ec9..b9e8c056 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java @@ -25,7 +25,6 @@ import com.stardust.autojs.runtime.exception.ScriptEnvironmentException; import com.stardust.autojs.runtime.exception.ScriptException; import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.autojs.core.accessibility.SimpleActionAutomator; -import com.stardust.concurrent.VolatileBox; import com.stardust.autojs.runtime.api.UI; import com.stardust.concurrent.VolatileDispose; import com.stardust.pio.UncheckedIOException; @@ -38,8 +37,6 @@ import com.stardust.util.UiHandler; import com.stardust.view.accessibility.AccessibilityInfoProvider; import org.mozilla.javascript.ContextFactory; -import org.mozilla.javascript.Scriptable; -import org.mozilla.javascript.ScriptableObject; import java.io.File; import java.io.IOException; @@ -174,7 +171,7 @@ public class ScriptRuntime { images = new Images(context, this, builder.mScreenCaptureRequester); } engines = new Engines(builder.mEngineService); - dialogs = new Dialogs(app, mUiHandler); + dialogs = new Dialogs(app, mUiHandler, bridges); } public void init() { @@ -217,23 +214,16 @@ public class ScriptRuntime { } public void setClip(final String text) { - final Object lock = new Object(); - mUiHandler.post(new Runnable() { - @Override - public void run() { - ClipboardUtil.setClip(mUiHandler.getContext(), text); - synchronized (lock) { - lock.notify(); - } - } - }); - synchronized (lock) { - try { - lock.wait(); - } catch (InterruptedException e) { - throw new ScriptInterruptedException(); - } + if (Looper.myLooper() == Looper.getMainLooper()) { + ClipboardUtil.setClip(mUiHandler.getContext(), text); + return; } + VolatileDispose dispose = new VolatileDispose<>(); + mUiHandler.post(() -> { + ClipboardUtil.setClip(mUiHandler.getContext(), text); + dispose.setAndNotify(text); + }); + dispose.blockedGet(); } public String getClip() { diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java index 65d3013a..cfe428c9 100644 --- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java +++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Dialogs.java @@ -9,10 +9,9 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.Theme; import com.stardust.autojs.R; import com.stardust.autojs.annotation.ScriptInterface; +import com.stardust.autojs.annotation.ScriptVariable; import com.stardust.autojs.core.ui.BlockedMaterialDialog; -import com.stardust.autojs.runtime.exception.ScriptInterruptedException; -import com.stardust.autojs.runtime.api.AppUtils; -import com.stardust.concurrent.VolatileBox; +import com.stardust.autojs.runtime.ScriptBridges; import com.stardust.util.ArrayUtils; import com.stardust.util.UiHandler; @@ -25,50 +24,49 @@ public class Dialogs { private AppUtils mAppUtils; private UiHandler mUiHandler; private ContextThemeWrapper mThemeWrapper; + private ScriptBridges mScriptBridges; - public Dialogs(AppUtils appUtils, UiHandler uiHandler) { + @ScriptVariable + public final NonUiDialogs nonUiDialogs = new NonUiDialogs(); + + public Dialogs(AppUtils appUtils, UiHandler uiHandler, ScriptBridges scriptBridges) { mAppUtils = appUtils; mUiHandler = uiHandler; + mScriptBridges = scriptBridges; } @ScriptInterface - public String rawInput(String title, String prefill) { - VolatileBox result = new VolatileBox<>(null); - dialogBuilder() - .input(null, prefill, true, result) - .title(title) - .show(); - return result.blockedGetOrThrow(ScriptInterruptedException.class); + public Object rawInput(String title, String prefill, Object callback) { + return ((BlockedMaterialDialog.Builder) dialogBuilder(callback) + .input(null, prefill, true) + .title(title)) + .showAndGet(); } + @ScriptInterface - public void alert(String title, String content) { - VolatileBox lock = new VolatileBox<>(); - MaterialDialog.Builder builder = dialogBuilder() - .dismissListener(lock) + public Object alert(String title, String content, Object callback) { + MaterialDialog.Builder builder = dialogBuilder(callback) + .alert() .title(title) .positiveText(R.string.ok); if (!TextUtils.isEmpty(content)) { builder.content(content); } - builder.show(); - lock.blockedGetOrThrow(ScriptInterruptedException.class); + return ((BlockedMaterialDialog.Builder) builder).showAndGet(); } @ScriptInterface - public boolean confirm(String title, String content) { - VolatileBox result = new VolatileBox<>(false); - MaterialDialog.Builder builder = dialogBuilder() - .dismissListener(result) - .confirm(result) + public Object confirm(String title, String content, Object callback) { + MaterialDialog.Builder builder = dialogBuilder(callback) + .confirm() .title(title) .positiveText(R.string.ok) .negativeText(R.string.cancel); if (!TextUtils.isEmpty(content)) { builder.content(content); } - builder.show(); - return result.blockedGetOrThrow(ScriptInterruptedException.class); + return ((BlockedMaterialDialog.Builder) builder).showAndGet(); } private Context getContext() { @@ -79,47 +77,99 @@ public class Dialogs { } @ScriptInterface - public int select(String title, String... items) { - VolatileBox result = new VolatileBox<>(-1); - dialogBuilder() - .itemsCallback(result) + public Object select(String title, Object... args) { + Object callback = getCallback(args); + String[] items = getItems(args); + return ((BlockedMaterialDialog.Builder) dialogBuilder(callback) + .itemsCallback() .title(title) - .items((CharSequence[]) items) - .show(); - return result.blockedGetOrThrow(ScriptInterruptedException.class); + .items((CharSequence[]) items)) + .showAndGet(); + } + + private String[] getItems(Object[] args) { + int len = 0; + if (args.length > 1) { + if (args[args.length - 1] instanceof CharSequence) { + len = args.length; + } else { + len = args.length - 1; + } + } + String[] items = new String[len]; + for (int i = 0; i < len; i++) { + items[i] = args[i] == null ? null : args[i].toString(); + } + return items; + } + + private Object getCallback(Object[] args) { + if (args.length > 1 && !(args[args.length - 1] instanceof CharSequence)) { + return args[args.length - 1]; + } + return null; } @ScriptInterface - public int singleChoice(String title, int selectedIndex, String... items) { - VolatileBox result = new VolatileBox<>(-1); - dialogBuilder() - .itemsCallbackSingleChoice(selectedIndex, result) + public Object singleChoice(String title, int selectedIndex, String[] items, Object callback) { + return ((BlockedMaterialDialog.Builder) dialogBuilder(callback) + .itemsCallbackSingleChoice(selectedIndex) .title(title) .positiveText(R.string.ok) - .items((CharSequence[]) items) - .show(); - return result.blockedGetOrThrow(ScriptInterruptedException.class); + .items((CharSequence[]) items)) + .showAndGet(); } @ScriptInterface - public int[] multiChoice(String title, int[] indices, String... items) { - VolatileBox result = new VolatileBox<>(new Integer[0]); - dialogBuilder() - .itemsCallbackMultiChoice(ArrayUtils.box(indices), result) + public Object multiChoice(String title, int[] indices, String[] items, Object callback) { + return ((BlockedMaterialDialog.Builder) dialogBuilder(callback) + .itemsCallbackMultiChoice(ArrayUtils.box(indices)) .title(title) .positiveText(R.string.ok) - .items((CharSequence[]) items) - .show(); - return ArrayUtils.unbox(result.blockedGetOrThrow(ScriptInterruptedException.class)); + .items((CharSequence[]) items)) + .showAndGet(); } - private BlockedMaterialDialog.Builder dialogBuilder() { + private BlockedMaterialDialog.Builder dialogBuilder(Object callback) { Context context = mAppUtils.getCurrentActivity(); if (context == null || ((Activity) context).isFinishing()) { context = getContext(); } - return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mUiHandler) + return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mUiHandler, mScriptBridges, callback) .theme(Theme.LIGHT); } + + public class NonUiDialogs { + + public String rawInput(String title, String prefill, Object callback) { + return (String) Dialogs.this.rawInput(title, prefill, callback); + } + + @ScriptInterface + public boolean confirm(String title, String content, Object callback) { + return (boolean) Dialogs.this.confirm(title, content, callback); + } + + @ScriptInterface + public int select(String title, Object... args) { + return (Integer) Dialogs.this.select(title, args); + } + + @ScriptInterface + public int singleChoice(String title, int selectedIndex, String[] items, Object callback) { + return (int) Dialogs.this.singleChoice(title, selectedIndex, items, callback); + } + + @ScriptInterface + public int[] multiChoice(String title, int[] indices, String[] items, Object callback) { + return (int[]) Dialogs.this.multiChoice(title, indices, items, callback); + } + + @ScriptInterface + public Object alert(String title, String content, Object callback) { + return Dialogs.this.alert(title, content, callback); + } + + } }