add: promise support; fix: dialogs and setClip ANR on ui thread;

This commit is contained in:
hyb1996
2017-12-01 20:52:51 +08:00
parent 45bbc33ab7
commit 1413bf21a5
12 changed files with 607 additions and 184 deletions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.stardust.scriptdroid" applicationId "com.stardust.scriptdroid"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 23 targetSdkVersion 23
versionCode 226 versionCode 227
versionName "3.0.0 Alpha26" versionName "3.0.0 Alpha27"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
ndk { 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.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' compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
//JDeferred
compile 'org.jdeferred:jdeferred-android-aar:1.2.6'
//Glide //Glide
compile('com.github.bumptech.glide:glide:4.2.0', { compile('com.github.bumptech.glide:glide:4.2.0', {
exclude group: 'com.android.support' exclude group: 'com.android.support'

View File

@@ -0,0 +1,70 @@
"ui";
ui.layout(
<vertical>
<button id="callback" align="center">回调形式</button>
<button id="promise" align="center">Promise形式</button>
<button id="calc" align="center">简单计算器</button>
</vertical>
);
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);
});
});

View File

@@ -51,6 +51,9 @@ dependencies {
compile 'com.github.hyb1996:EnhancedFloaty:0.17' compile 'com.github.hyb1996:EnhancedFloaty:0.17'
compile 'com.github.hyb1996:OpenCvLib:2.4.13.4-imgproc' compile 'com.github.hyb1996:OpenCvLib:2.4.13.4-imgproc'
compile 'com.makeramen:roundedimageview:2.3.0' compile 'com.makeramen:roundedimageview:2.3.0'
//JDeferred
compile 'org.jdeferred:jdeferred-android-aar:1.2.6'
// Gson // Gson
compile 'com.google.code.gson:gson:2.8.0' compile 'com.google.code.gson:gson:2.8.0'
// Terminal emulator // Terminal emulator

View File

@@ -46,8 +46,9 @@ __runtime__.bridges.setBridges({
}); });
var __that__ = this; var __that__ = this;
JSON = require('__json2__.js'); var Promise = require('promise.js');
util = require('__util__.js'); var JSON = require('__json2__.js');
var util = require('__util__.js');
var __asGlobal__ = function(obj, functions){ var __asGlobal__ = function(obj, functions){
var len = functions.length; var len = functions.length;

View File

@@ -2,43 +2,107 @@
module.exports = function(__runtime__, scope){ module.exports = function(__runtime__, scope){
var dialogs = {}; var dialogs = {};
dialogs.rawInput = function(title, prefill){ dialogs.rawInput = function(title, prefill, callback){
prefill = prefill || ""; prefill = prefill || "";
var s = __runtime__.dialogs.rawInput(title, prefill); if(isUiThread() && !callback){
return s ? String(s) : null; 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){ dialogs.input = function(title, prefill, callback){
return eval(dialogs.rawInput(title, prefill)); 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.prompt = dialogs.rawInput;
dialogs.alert = function(title, prefill){ dialogs.alert = function(title, prefill, callback){
prefill = prefill || ""; prefill = prefill || "";
return __runtime__.dialogs.alert(title, prefill); if(isUiThread() && !callback){
return new Promise(function(resolve, reject){
rtDialogs().alert(title, prefill, function(){
resolve.apply(null, Array.prototype.slice.call(arguments));
});
});
}
return rtDialogs().alert(title, prefill, callback ? callback : null);
} }
dialogs.confirm = function(title, prefill){ dialogs.confirm = function(title, prefill, callback){
prefill = prefill || ""; prefill = prefill || "";
return __runtime__.dialogs.confirm(title, 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){ dialogs.select = function(title, items, callback){
if(items instanceof Array){ if(items instanceof Array){
return __runtime__.dialogs.select(title, items); if(isUiThread() && !callback){
return new Promise(function(resolve, reject){
rtDialogs().select(title, items, function(){
resolve.apply(null, Array.prototype.slice.call(arguments));
});
});
} }
return __runtime__.dialogs.select(title, [].slice.call(arguments, 1)); return rtDialogs().select(title, items, callback ? callback : null);
}
return rtDialogs().select(title, [].slice.call(arguments, 1));
} }
dialogs.singleChoice = function(title, items, index){ dialogs.singleChoice = function(title, items, index, callback){
index = index || 0; 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 || []; 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 jsArray = [];
var len = javaArray.length; var len = javaArray.length;
for (var i = 0;i < len;i++){ for (var i = 0;i < len;i++){
@@ -47,21 +111,26 @@ module.exports = function(__runtime__, scope){
return jsArray; return jsArray;
} }
scope.rawInput = function(title, prefill){ function rtDialogs(){
return dialogs.rawInput(title, prefill); var d = __runtime__.dialogs;
if(!isUiThread()){
return d.nonUiDialogs;
}else{
return d;
}
} }
scope.alert = function(title, prefill){ function isUiThread(){
dialogs.alert(title, prefill); return android.os.Looper.myLooper() == android.os.Looper.getMainLooper();
} }
scope.confirm = function(title, prefill){ scope.rawInput = dialogs.rawInput.bind(dialogs);
return dialogs.confirm(title, prefill);
}
scope.prompt = function(title, prefill){ scope.alert = dialogs.alert.bind(dialogs);
return dialogs.prompt(title, prefill);
} scope.confirm = dialogs.confirm.bind(dialogs);
scope.prompt = dialogs.prompt.bind(dialogs);
return dialogs; return dialogs;
} }

View File

@@ -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);
}
}

View File

@@ -164,8 +164,8 @@ public class RootAutomator {
public void touchUp(int id) throws IOException { public void touchUp(int id) throws IOException {
sendEvent(EV_ABS, ABS_MT_TRACKING_ID, id); sendEvent(EV_ABS, ABS_MT_TRACKING_ID, id);
// sendEvent(EV_KEY, BTN_TOUCH, 0x00000000); sendEvent(EV_KEY, BTN_TOUCH, 0x00000000);
// sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000); sendEvent(EV_KEY, BTN_TOOL_FINGER, 0x00000000);
sendEvent(EV_SYN, SYN_REPORT, 0x00000000); sendEvent(EV_SYN, SYN_REPORT, 0x00000000);
} }

View File

@@ -3,8 +3,7 @@ package com.stardust.autojs.core.ui;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.DialogInterface; import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
@@ -12,9 +11,14 @@ import android.view.WindowManager;
import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme; 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 com.stardust.util.UiHandler;
import org.jdeferred.impl.DeferredObject;
/** /**
* Created by Stardust on 2017/5/8. * Created by Stardust on 2017/5/8.
*/ */
@@ -48,99 +52,122 @@ public class BlockedMaterialDialog extends MaterialDialog {
public static class Builder extends MaterialDialog.Builder { public static class Builder extends MaterialDialog.Builder {
private VolatileDispose<Object> mResultBox;
private UiHandler mUiHandler; 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(context);
super.theme(Theme.LIGHT); super.theme(Theme.LIGHT);
mUiHandler = uiHandler; 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<String> result) { public MaterialDialog.Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, boolean allowEmptyInput) {
dismissListener(result); super.input(hint, prefill, allowEmptyInput, (dialog, input) -> setAndNotify(input.toString()));
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();
}
}
});
return this; return this;
} }
public Builder confirm(final VolatileBox<Boolean> result) { private void setAndNotify(Object r) {
onAny(new SingleButtonCallback() { if (mNotified) {
@Override return;
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { }
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) { if (which == DialogAction.POSITIVE) {
result.setAndNotify(true); setAndNotify(true);
} else { } else {
result.setAndNotify(false); setAndNotify(false);
}
} }
}); });
return this; return this;
} }
public MaterialDialog.Builder itemsCallback(final VolatileBox<Integer> result) { public MaterialDialog.Builder itemsCallback() {
dismissListener(result); dismissListener(dialog -> setAndNotify(-1));
super.itemsCallback(new ListCallback() { super.itemsCallback((dialog, itemView, position, text) -> setAndNotify(position));
@Override
public void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text) {
result.setAndNotify(position);
}
});
return this; return this;
} }
public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices, final VolatileBox<Integer[]> result) { public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices) {
dismissListener(result); dismissListener(dialog -> setAndNotify(new Integer[0]));
super.itemsCallbackMultiChoice(selectedIndices, new ListCallbackMultiChoice() { super.itemsCallbackMultiChoice(selectedIndices, (dialog, which, text) -> {
@Override setAndNotify(ArrayUtils.unbox(which));
public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) {
result.setAndNotify(which);
return true; return true;
}
}); });
return this; return this;
} }
public MaterialDialog.Builder itemsCallbackSingleChoice(int selectedIndex, final VolatileBox<Integer> result) { public MaterialDialog.Builder itemsCallbackSingleChoice(int selectedIndex) {
dismissListener(result); dismissListener(dialog -> setAndNotify(-1));
super.itemsCallbackSingleChoice(selectedIndex, new ListCallbackSingleChoice() { super.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> {
@Override setAndNotify(which);
public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) {
result.setAndNotify(which);
return true; return true;
}
}); });
return this; 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 Object showAndGet() {
public MaterialDialog show() { if (Looper.myLooper() == Looper.getMainLooper()) {
mUiHandler.post(new Runnable() { super.show();
@Override } else {
public void run() { mUiHandler.post(Builder.super::show);
Builder.super.show();
} }
}); if (mResultBox != null) {
return mResultBox.blockedGetOrThrow(ScriptInterruptedException.class);
} else {
return null; return null;
} }
}
@Override @Override
public MaterialDialog build() { public MaterialDialog build() {

View File

@@ -49,7 +49,7 @@ public interface AttributeHandler {
if (!attr.getNodeName().equals("style")) { if (!attr.getNodeName().equals("style")) {
layoutXml.append("android:"); 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"); .append("=\"").append(mapAttrValue(nodeName, attr.getNodeName(), attr.getNodeValue())).append("\"\n");
return true; return true;
} }

View File

@@ -13,7 +13,7 @@ public class ScriptBridges {
Object[] NO_ARGUMENTS = new Object[0]; 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); Object toArray(Object o);
@@ -26,7 +26,7 @@ public class ScriptBridges {
mBridges = bridges; mBridges = bridges;
} }
public Object callFunction(Object func, Object target, Object[] args) { public Object callFunction(Object func, Object target, Object args) {
checkBridges(); checkBridges();
return mBridges.call(func, target, args); return mBridges.call(func, target, args);
} }

View File

@@ -25,7 +25,6 @@ import com.stardust.autojs.runtime.exception.ScriptEnvironmentException;
import com.stardust.autojs.runtime.exception.ScriptException; import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.autojs.core.accessibility.SimpleActionAutomator; import com.stardust.autojs.core.accessibility.SimpleActionAutomator;
import com.stardust.concurrent.VolatileBox;
import com.stardust.autojs.runtime.api.UI; import com.stardust.autojs.runtime.api.UI;
import com.stardust.concurrent.VolatileDispose; import com.stardust.concurrent.VolatileDispose;
import com.stardust.pio.UncheckedIOException; import com.stardust.pio.UncheckedIOException;
@@ -38,8 +37,6 @@ import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider; import com.stardust.view.accessibility.AccessibilityInfoProvider;
import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -174,7 +171,7 @@ public class ScriptRuntime {
images = new Images(context, this, builder.mScreenCaptureRequester); images = new Images(context, this, builder.mScreenCaptureRequester);
} }
engines = new Engines(builder.mEngineService); engines = new Engines(builder.mEngineService);
dialogs = new Dialogs(app, mUiHandler); dialogs = new Dialogs(app, mUiHandler, bridges);
} }
public void init() { public void init() {
@@ -217,23 +214,16 @@ public class ScriptRuntime {
} }
public void setClip(final String text) { public void setClip(final String text) {
final Object lock = new Object(); if (Looper.myLooper() == Looper.getMainLooper()) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
ClipboardUtil.setClip(mUiHandler.getContext(), text); ClipboardUtil.setClip(mUiHandler.getContext(), text);
synchronized (lock) { return;
lock.notify();
}
} }
VolatileDispose<Object> dispose = new VolatileDispose<>();
mUiHandler.post(() -> {
ClipboardUtil.setClip(mUiHandler.getContext(), text);
dispose.setAndNotify(text);
}); });
synchronized (lock) { dispose.blockedGet();
try {
lock.wait();
} catch (InterruptedException e) {
throw new ScriptInterruptedException();
}
}
} }
public String getClip() { public String getClip() {

View File

@@ -9,10 +9,9 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme; import com.afollestad.materialdialogs.Theme;
import com.stardust.autojs.R; import com.stardust.autojs.R;
import com.stardust.autojs.annotation.ScriptInterface; import com.stardust.autojs.annotation.ScriptInterface;
import com.stardust.autojs.annotation.ScriptVariable;
import com.stardust.autojs.core.ui.BlockedMaterialDialog; import com.stardust.autojs.core.ui.BlockedMaterialDialog;
import com.stardust.autojs.runtime.exception.ScriptInterruptedException; import com.stardust.autojs.runtime.ScriptBridges;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.concurrent.VolatileBox;
import com.stardust.util.ArrayUtils; import com.stardust.util.ArrayUtils;
import com.stardust.util.UiHandler; import com.stardust.util.UiHandler;
@@ -25,50 +24,49 @@ public class Dialogs {
private AppUtils mAppUtils; private AppUtils mAppUtils;
private UiHandler mUiHandler; private UiHandler mUiHandler;
private ContextThemeWrapper mThemeWrapper; 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; mAppUtils = appUtils;
mUiHandler = uiHandler; mUiHandler = uiHandler;
mScriptBridges = scriptBridges;
} }
@ScriptInterface @ScriptInterface
public String rawInput(String title, String prefill) { public Object rawInput(String title, String prefill, Object callback) {
VolatileBox<String> result = new VolatileBox<>(null); return ((BlockedMaterialDialog.Builder) dialogBuilder(callback)
dialogBuilder() .input(null, prefill, true)
.input(null, prefill, true, result) .title(title))
.title(title) .showAndGet();
.show();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
} }
@ScriptInterface @ScriptInterface
public void alert(String title, String content) { public Object alert(String title, String content, Object callback) {
VolatileBox<Void> lock = new VolatileBox<>(); MaterialDialog.Builder builder = dialogBuilder(callback)
MaterialDialog.Builder builder = dialogBuilder() .alert()
.dismissListener(lock)
.title(title) .title(title)
.positiveText(R.string.ok); .positiveText(R.string.ok);
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
builder.content(content); builder.content(content);
} }
builder.show(); return ((BlockedMaterialDialog.Builder) builder).showAndGet();
lock.blockedGetOrThrow(ScriptInterruptedException.class);
} }
@ScriptInterface @ScriptInterface
public boolean confirm(String title, String content) { public Object confirm(String title, String content, Object callback) {
VolatileBox<Boolean> result = new VolatileBox<>(false); MaterialDialog.Builder builder = dialogBuilder(callback)
MaterialDialog.Builder builder = dialogBuilder() .confirm()
.dismissListener(result)
.confirm(result)
.title(title) .title(title)
.positiveText(R.string.ok) .positiveText(R.string.ok)
.negativeText(R.string.cancel); .negativeText(R.string.cancel);
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
builder.content(content); builder.content(content);
} }
builder.show(); return ((BlockedMaterialDialog.Builder) builder).showAndGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
} }
private Context getContext() { private Context getContext() {
@@ -79,47 +77,99 @@ public class Dialogs {
} }
@ScriptInterface @ScriptInterface
public int select(String title, String... items) { public Object select(String title, Object... args) {
VolatileBox<Integer> result = new VolatileBox<>(-1); Object callback = getCallback(args);
dialogBuilder() String[] items = getItems(args);
.itemsCallback(result) return ((BlockedMaterialDialog.Builder) dialogBuilder(callback)
.itemsCallback()
.title(title) .title(title)
.items((CharSequence[]) items) .items((CharSequence[]) items))
.show(); .showAndGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class); }
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 @ScriptInterface
public int singleChoice(String title, int selectedIndex, String... items) { public Object singleChoice(String title, int selectedIndex, String[] items, Object callback) {
VolatileBox<Integer> result = new VolatileBox<>(-1); return ((BlockedMaterialDialog.Builder) dialogBuilder(callback)
dialogBuilder() .itemsCallbackSingleChoice(selectedIndex)
.itemsCallbackSingleChoice(selectedIndex, result)
.title(title) .title(title)
.positiveText(R.string.ok) .positiveText(R.string.ok)
.items((CharSequence[]) items) .items((CharSequence[]) items))
.show(); .showAndGet();
return result.blockedGetOrThrow(ScriptInterruptedException.class);
} }
@ScriptInterface @ScriptInterface
public int[] multiChoice(String title, int[] indices, String... items) { public Object multiChoice(String title, int[] indices, String[] items, Object callback) {
VolatileBox<Integer[]> result = new VolatileBox<>(new Integer[0]); return ((BlockedMaterialDialog.Builder) dialogBuilder(callback)
dialogBuilder() .itemsCallbackMultiChoice(ArrayUtils.box(indices))
.itemsCallbackMultiChoice(ArrayUtils.box(indices), result)
.title(title) .title(title)
.positiveText(R.string.ok) .positiveText(R.string.ok)
.items((CharSequence[]) items) .items((CharSequence[]) items))
.show(); .showAndGet();
return ArrayUtils.unbox(result.blockedGetOrThrow(ScriptInterruptedException.class));
} }
private BlockedMaterialDialog.Builder dialogBuilder() { private BlockedMaterialDialog.Builder dialogBuilder(Object callback) {
Context context = mAppUtils.getCurrentActivity(); Context context = mAppUtils.getCurrentActivity();
if (context == null || ((Activity) context).isFinishing()) { if (context == null || ((Activity) context).isFinishing()) {
context = getContext(); context = getContext();
} }
return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mUiHandler) return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mUiHandler, mScriptBridges, callback)
.theme(Theme.LIGHT); .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);
}
}
} }