mirror of
https://github.com/TonyJiangWJ/Auto.js.git
synced 2026-06-21 21:01:43 +08:00
change JavaScriptEngine to LoopBasedJavaScriptEngine
Everything works like Node.js. loop() will not need be called explicitly any more. Including some changes: * Every thread of script has a looper. * Use a servant thread in gestures functions of GlobalActionAutomator and image available listener of ScreenCapture. * For keeping events key and touch listener alive, add Loopers.waitWhenIdle to control whether or not Looper should quit when it has nothing to do
This commit is contained in:
parent
11b87d9a51
commit
12037ba488
16
app/src/main/assets/help/developerguide/如何写自动操作脚本.md
Normal file
16
app/src/main/assets/help/developerguide/如何写自动操作脚本.md
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
## 控件
|
||||
|
||||
对于一般软件而言,其界面是由控件组成的。
|
||||
|
||||
一段文本、一张图片、一个列表都是一个控件(widget),文本用文本控件(TextView)显示,图片用图片控件(ImageView)显示。
|
||||
|
||||
此外还有一类特殊的控件,其本身不显示内容,而是作为其他控件的"容器"使用,决定在他里面的控件的位置和排放。这类控件称为"布局"(layout)。
|
||||
|
||||
常见的布局有线性布局(LinearLayout), 相对布局(RelativeLayout), 帧布局(FrameLayout), 表格布局(GridLayout)等。
|
||||
|
||||
例如线性布局,在他里面的控件是线性(垂直或平行)地摆放的。比如一个水平的线性布局,里面依次有一个文本控件和一个图片控件,那么显示时图片将会在文本的右边。
|
||||
|
||||
## 控件的属性
|
||||
|
||||
打开悬浮窗,找到
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
events也是一个\[EventEmitter\](#EventEmitter),可以使用EventEmitter的所有函数,这里只介绍其自身的函数。
|
||||
|
||||
注意,events内置的触摸、按键事件必须调用loop()函数才能正常工作。例如:
|
||||
```
|
||||
events.observeKey();
|
||||
events.onKeyDown("volume_up", function(event){
|
||||
@ -18,7 +17,6 @@ events.onKeyDown("menu", function(event){
|
||||
toast("菜单键被按下了");
|
||||
});
|
||||
|
||||
loop();
|
||||
//后面的语句不会再执行了
|
||||
```
|
||||
|
||||
|
||||
@ -2,22 +2,6 @@ timer 模块暴露了一个全局的 API,用于在某个未来时间段调用
|
||||
|
||||
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 时要传入的可选参数。
|
||||
@ -49,12 +33,12 @@ callback 可能不会精确地在 delay 毫秒被调用。 Auto.js 不能保证
|
||||
setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。
|
||||
|
||||
|
||||
### clearImmediate(id)#
|
||||
### clearImmediate(id)
|
||||
* id \<Number\> 一个 setImmediate() 返回的 id。
|
||||
|
||||
取消一个由 setImmediate() 创建的 Immediate 对象。
|
||||
|
||||
### clearInterval(id)#
|
||||
### clearInterval(id)
|
||||
* id \<Number\> 一个 setInterval() 返回的 id。
|
||||
|
||||
取消一个由 setInterval() 创建的 Timeout 对象。
|
||||
|
||||
@ -292,6 +292,12 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
|
||||
mDrawerHeaderBackgroundSaver.select(this, mActivityResultMediator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
stopService(new Intent(this, DownloadService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@ -477,6 +483,11 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
|
||||
return mActivityResultMediator;
|
||||
}
|
||||
|
||||
public static void onRecordStop(Context context, String script) {
|
||||
Intent intent = new Intent(context, MainActivity_.class)
|
||||
@ -488,11 +499,5 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
|
||||
return mActivityResultMediator;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -44,7 +44,6 @@ import java.util.concurrent.Executor;
|
||||
@EFragment(R.layout.fragment_side_menu)
|
||||
public class SideMenuFragment extends android.support.v4.app.Fragment {
|
||||
|
||||
|
||||
public static void setFragment(FragmentActivity activity, int viewId) {
|
||||
SideMenuFragment fragment = new SideMenuFragment_();
|
||||
activity.getSupportFragmentManager().beginTransaction().replace(viewId, fragment).commit();
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
|
||||
__runtime__.init();
|
||||
|
||||
if(__engine_name__ == "rhino"){
|
||||
__importClass__ = importClass;
|
||||
var importClass = function(pack){
|
||||
|
||||
@ -2,7 +2,11 @@
|
||||
module.exports = function(__runtime__, scope){
|
||||
var timers = Object.create(__runtime__.timers);
|
||||
|
||||
scope.__asGlobal__(timers, ['loop', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate']);
|
||||
scope.__asGlobal__(timers, ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate']);
|
||||
|
||||
scope.loop = function(){
|
||||
console.warn("loop() has been deprecated and has no effect. Remove it from your code.");
|
||||
}
|
||||
|
||||
return timers;
|
||||
}
|
||||
|
||||
@ -183,10 +183,6 @@ public class ScriptEngineService {
|
||||
mUiHandler.toast(mContext.getString(R.string.text_no_running_script));
|
||||
}
|
||||
|
||||
public String[] getGlobalFunctions() {
|
||||
return mScriptEngineManager.getGlobalFunctions();
|
||||
}
|
||||
|
||||
public Set<ScriptEngine> getEngines() {
|
||||
return mScriptEngineManager.getEngines();
|
||||
}
|
||||
|
||||
@ -2,12 +2,6 @@ package com.stardust.autojs.engine;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.stardust.autojs.BuildConfig;
|
||||
import com.stardust.autojs.script.ScriptSource;
|
||||
import com.stardust.autojs.script.StringScriptSource;
|
||||
import com.stardust.pio.PFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@ -25,10 +19,10 @@ public abstract class AbstractScriptEngineManager implements ScriptEngineManager
|
||||
private final Set<ScriptEngine> mEngines = new HashSet<>();
|
||||
private EngineLifecycleCallback mEngineLifecycleCallback;
|
||||
|
||||
private android.content.Context mContext;
|
||||
private android.content.Context mAndroidContext;
|
||||
|
||||
public AbstractScriptEngineManager(Context context) {
|
||||
mContext = context;
|
||||
public AbstractScriptEngineManager(Context androidContext) {
|
||||
mAndroidContext = androidContext;
|
||||
}
|
||||
|
||||
public ScriptEngine createEngine() {
|
||||
@ -61,10 +55,8 @@ public abstract class AbstractScriptEngineManager implements ScriptEngineManager
|
||||
|
||||
protected abstract ScriptEngine createEngineInner();
|
||||
|
||||
public abstract String[] getGlobalFunctions();
|
||||
|
||||
public android.content.Context getContext() {
|
||||
return mContext;
|
||||
public android.content.Context getAndroidContext() {
|
||||
return mAndroidContext;
|
||||
}
|
||||
|
||||
protected void putProperties(ScriptEngine engine) {
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
package com.stardust.autojs.engine;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
|
||||
import com.stardust.autojs.runtime.api.Loopers;
|
||||
import com.stardust.autojs.script.ScriptSource;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/7/28.
|
||||
*/
|
||||
|
||||
public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine {
|
||||
|
||||
private Handler mHandler;
|
||||
private boolean mLooping = false;
|
||||
|
||||
public LoopBasedJavaScriptEngine(RhinoJavaScriptEngineManager engineManager) {
|
||||
super(engineManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(final ScriptSource source) {
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LoopBasedJavaScriptEngine.super.execute(source);
|
||||
}
|
||||
};
|
||||
mHandler.post(r);
|
||||
if (!mLooping && Looper.myLooper() != Looper.getMainLooper()) {
|
||||
mLooping = true;
|
||||
Looper.loop();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceStop() {
|
||||
Loopers.quitForThread(getThread());
|
||||
super.forceStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void destroy() {
|
||||
Loopers.quitForThread(getThread());
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Loopers.prepare();
|
||||
mHandler = new Handler();
|
||||
super.init();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -77,7 +77,6 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
|
||||
public void forceStop() {
|
||||
Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread);
|
||||
mThread.interrupt();
|
||||
Timers.quitLooperIfNeeded(mThread);
|
||||
}
|
||||
|
||||
public RhinoJavaScriptEngineManager getEngineManager() {
|
||||
@ -91,7 +90,6 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
|
||||
contextCount--;
|
||||
Log.d(LOG_TAG, "contextCount = " + contextCount);
|
||||
mEngineManager.removeEngine(this);
|
||||
Timers.removeThreadRecord(mThread);
|
||||
mDestroyed = true;
|
||||
}
|
||||
|
||||
@ -110,6 +108,10 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
|
||||
return mTags.get(key);
|
||||
}
|
||||
|
||||
public Thread getThread() {
|
||||
return mThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
ScriptableObject.putProperty(mScriptable, "__engine_name__", "rhino");
|
||||
@ -128,7 +130,7 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
|
||||
for (String path : mRequirePath) {
|
||||
list.add(new File(path).toURI());
|
||||
}
|
||||
AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getContext(), list);
|
||||
AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getAndroidContext(), list);
|
||||
new RequireBuilder()
|
||||
.setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider))
|
||||
.setSandboxed(false)
|
||||
@ -152,9 +154,9 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
|
||||
|
||||
protected Context createContext() {
|
||||
if (!ContextFactory.hasExplicitGlobal()) {
|
||||
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mEngineManager.getContext().getCacheDir(), "classes")));
|
||||
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mEngineManager.getAndroidContext().getCacheDir(), "classes")));
|
||||
}
|
||||
Context context = new RhinoAndroidHelper(mEngineManager.getContext()).enterContext();
|
||||
Context context = new RhinoAndroidHelper(mEngineManager.getAndroidContext()).enterContext();
|
||||
contextCount++;
|
||||
context.setOptimizationLevel(-1);
|
||||
context.setLanguageVersion(Context.VERSION_ES6);
|
||||
|
||||
@ -7,22 +7,7 @@ import com.stardust.autojs.script.SequenceScriptSource;
|
||||
import com.stardust.autojs.script.StringScriptSource;
|
||||
import com.stardust.pio.PFile;
|
||||
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.commonjs.module.RequireBuilder;
|
||||
import org.mozilla.javascript.commonjs.module.provider.ModuleSource;
|
||||
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider;
|
||||
import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/1.
|
||||
@ -40,7 +25,7 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
|
||||
}
|
||||
|
||||
protected RhinoJavaScriptEngine createEngineInner() {
|
||||
RhinoJavaScriptEngine engine = new RhinoJavaScriptEngine(this);
|
||||
RhinoJavaScriptEngine engine = new LoopBasedJavaScriptEngine(this);
|
||||
return engine;
|
||||
}
|
||||
|
||||
@ -58,7 +43,7 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
|
||||
|
||||
private String readInitScript() {
|
||||
try {
|
||||
return PFile.read(getContext().getAssets().open("javascript_engine_init.js"));
|
||||
return PFile.read(getAndroidContext().getAssets().open("javascript_engine_init.js"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -76,28 +61,5 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
|
||||
return mInitScript;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getGlobalFunctions() {
|
||||
if (mFunctions == null)
|
||||
mFunctions = getGlobalFunctionsInner();
|
||||
return mFunctions;
|
||||
}
|
||||
|
||||
private String[] getGlobalFunctionsInner() {
|
||||
ScriptEngine engine = createEngine();
|
||||
ScriptSource source = new StringScriptSource("this", "this");
|
||||
engine.setTag("script", source);
|
||||
engine.init();
|
||||
Scriptable scriptable = (Scriptable) engine.execute(source);
|
||||
Object[] ids = scriptable.getIds();
|
||||
String[] functions = new String[ids.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
functions[i] = ids[i].toString();
|
||||
}
|
||||
engine.destroy();
|
||||
return functions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -26,8 +26,6 @@ public interface ScriptEngineManager {
|
||||
|
||||
Set<ScriptEngine> getEngines();
|
||||
|
||||
String[] getGlobalFunctions();
|
||||
|
||||
int stopAll();
|
||||
|
||||
}
|
||||
|
||||
@ -2,11 +2,13 @@ package com.stardust.autojs.runtime;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
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.Loopers;
|
||||
import com.stardust.autojs.runtime.api.ScriptBridges;
|
||||
import com.stardust.autojs.runtime.api.Console;
|
||||
import com.stardust.autojs.runtime.api.Events;
|
||||
@ -21,8 +23,6 @@ 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;
|
||||
|
||||
/**
|
||||
@ -56,15 +56,22 @@ public abstract class AbstractScriptRuntime {
|
||||
public ScriptBridges bridges = new ScriptBridges();
|
||||
|
||||
@ScriptVariable
|
||||
public Timers timers = new Timers(bridges);
|
||||
public Loopers loopers;
|
||||
|
||||
@ScriptVariable
|
||||
public Timers timers;
|
||||
|
||||
private Images images;
|
||||
|
||||
private UiHandler mUiHandler;
|
||||
private AccessibilityBridge mAccessibilityBridge;
|
||||
private static WeakReference<Context> applicationContext;
|
||||
|
||||
public AbstractScriptRuntime(UiHandler uiHandler, Console console, AccessibilityBridge bridge, AppUtils appUtils, ScreenCaptureRequester screenCaptureRequester) {
|
||||
this.app = appUtils;
|
||||
this.console = console;
|
||||
mAccessibilityBridge = bridge;
|
||||
mUiHandler = uiHandler;
|
||||
this.automator = new SimpleActionAutomator(bridge, this);
|
||||
this.info = bridge.getInfoProvider();
|
||||
Context context = uiHandler.getContext();
|
||||
@ -73,7 +80,18 @@ public abstract class AbstractScriptRuntime {
|
||||
images = new Images(context, this, screenCaptureRequester);
|
||||
}
|
||||
dialogs = new Dialogs(app, uiHandler);
|
||||
events = new Events(context, bridge, bridges, timers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call in init.js
|
||||
*/
|
||||
@CallSuper
|
||||
public void init() {
|
||||
if (loopers != null)
|
||||
throw new IllegalStateException("already initialized");
|
||||
loopers = new Loopers();
|
||||
events = new Events(mUiHandler.getContext(), mAccessibilityBridge, bridges, loopers);
|
||||
timers = new Timers(bridges);
|
||||
}
|
||||
|
||||
public static void setApplicationContext(Context context) {
|
||||
@ -130,10 +148,15 @@ public abstract class AbstractScriptRuntime {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
images.releaseScreenCapturer();
|
||||
}
|
||||
events.recycle();
|
||||
if (events != null)
|
||||
events.recycle();
|
||||
if (loopers != null)
|
||||
loopers.quitAll();
|
||||
}
|
||||
|
||||
public Object getImages() {
|
||||
return images;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -27,27 +27,29 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
|
||||
private AccessibilityBridge mAccessibilityBridge;
|
||||
private Context mContext;
|
||||
private TouchObserver mTouchObserver;
|
||||
private Timers mTimers;
|
||||
private long mLastTouchEventMillis;
|
||||
private long mTouchEventTimeout = 10;
|
||||
private boolean mListeningKey = false;
|
||||
private Loopers mLoopers;
|
||||
private Handler mHandler;
|
||||
|
||||
public Events(Context context, AccessibilityBridge accessibilityBridge, ScriptBridges bridges, Timers timers) {
|
||||
public Events(Context context, AccessibilityBridge accessibilityBridge, ScriptBridges bridges, Loopers loopers) {
|
||||
super(bridges);
|
||||
mAccessibilityBridge = accessibilityBridge;
|
||||
mContext = context;
|
||||
mTimers = timers;
|
||||
mLoopers = loopers;
|
||||
}
|
||||
|
||||
public EventEmitter emitter(){
|
||||
public EventEmitter emitter() {
|
||||
return new EventEmitter(mBridges);
|
||||
}
|
||||
|
||||
public void observeKey() {
|
||||
if (mListeningKey)
|
||||
return;
|
||||
ensureHandler();
|
||||
mLoopers.waitWhenIdle(true);
|
||||
mListeningKey = true;
|
||||
mTimers.prepareLoopIfNeeded();
|
||||
mAccessibilityBridge.ensureServiceEnabled();
|
||||
AccessibilityService service = mAccessibilityBridge.getService();
|
||||
if (service == null)
|
||||
@ -55,10 +57,17 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
|
||||
service.getOnKeyObserver().addListener(this);
|
||||
}
|
||||
|
||||
private void ensureHandler() {
|
||||
if(mHandler == null){
|
||||
mHandler = new Handler();
|
||||
}
|
||||
}
|
||||
|
||||
public void observeTouch() {
|
||||
mTimers.prepareLoopIfNeeded();
|
||||
if (mTouchObserver != null)
|
||||
return;
|
||||
ensureHandler();
|
||||
mLoopers.waitWhenIdle(true);
|
||||
mTouchObserver = new TouchObserver(mContext);
|
||||
mTouchObserver.setOnTouchEventListener(this);
|
||||
mTouchObserver.observe();
|
||||
@ -128,7 +137,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
|
||||
|
||||
@Override
|
||||
public void onKeyEvent(final int keyCode, final KeyEvent event) {
|
||||
mTimers.post(new Runnable() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase();
|
||||
@ -151,7 +160,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
|
||||
return;
|
||||
}
|
||||
mLastTouchEventMillis = System.currentTimeMillis();
|
||||
mTimers.post(new Runnable() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
emit("touch", new Point(x, y));
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
package com.stardust.autojs.runtime.api;
|
||||
|
||||
import android.graphics.Paint;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.lang.ThreadCompat;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/7/29.
|
||||
*/
|
||||
|
||||
public class Loopers {
|
||||
|
||||
private volatile Looper mServantLooper;
|
||||
private static volatile ConcurrentHashMap<Thread, Looper> sLoopers = new ConcurrentHashMap<>();
|
||||
|
||||
public volatile boolean waitWhenIdle = false;
|
||||
|
||||
public Loopers() {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
waitWhenIdle = true;
|
||||
}
|
||||
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
|
||||
@Override
|
||||
public boolean queueIdle() {
|
||||
Looper l = Looper.myLooper();
|
||||
if (l != null && !waitWhenIdle)
|
||||
l.quit();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void initServantThread() {
|
||||
new ThreadCompat(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
final Object lock = Loopers.this;
|
||||
mServantLooper = Looper.myLooper();
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
Looper.loop();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public Looper getServantLooper() {
|
||||
if (mServantLooper == null) {
|
||||
initServantThread();
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new ScriptInterruptedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return mServantLooper;
|
||||
}
|
||||
|
||||
public void quitServantLooper() {
|
||||
if (mServantLooper == null)
|
||||
return;
|
||||
mServantLooper.quit();
|
||||
}
|
||||
|
||||
public void waitWhenIdle(boolean b) {
|
||||
waitWhenIdle = b;
|
||||
}
|
||||
|
||||
public void quitAll() {
|
||||
quitServantLooper();
|
||||
}
|
||||
|
||||
public static void prepare() {
|
||||
if(Looper.myLooper() == Looper.getMainLooper())
|
||||
return;
|
||||
if (Looper.myLooper() == null)
|
||||
Looper.prepare();
|
||||
Looper l = Looper.myLooper();
|
||||
if (l != null)
|
||||
sLoopers.put(Thread.currentThread(), l);
|
||||
}
|
||||
|
||||
public static void quitForThread(Thread thread) {
|
||||
Looper looper = sLoopers.remove(thread);
|
||||
if (looper != null)
|
||||
looper.quit();
|
||||
}
|
||||
}
|
||||
@ -12,18 +12,23 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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;
|
||||
private Handler mHandler;
|
||||
|
||||
public Timers(ScriptBridges bridges) {
|
||||
mBridges = bridges;
|
||||
}
|
||||
|
||||
private void ensureHander(){
|
||||
if(mHandler == null){
|
||||
mHandler = new Handler();
|
||||
}
|
||||
}
|
||||
|
||||
public int setTimeout(final Object callback, long delay, final Object... args) {
|
||||
prepareLoopIfNeeded();
|
||||
ensureHander();
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
Runnable r = new Runnable() {
|
||||
@ -39,6 +44,7 @@ public class Timers {
|
||||
}
|
||||
|
||||
public void post(Runnable r) {
|
||||
ensureHander();
|
||||
mHandler.post(r);
|
||||
}
|
||||
|
||||
@ -47,7 +53,7 @@ public class Timers {
|
||||
}
|
||||
|
||||
public int setInterval(final Object listener, final long interval, final Object... args) {
|
||||
prepareLoopIfNeeded();
|
||||
ensureHander();
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
Runnable r = new Runnable() {
|
||||
@ -67,7 +73,7 @@ public class Timers {
|
||||
}
|
||||
|
||||
public int setImmediate(final Object listener, final Object... args) {
|
||||
prepareLoopIfNeeded();
|
||||
ensureHander();
|
||||
mCallbackMaxId++;
|
||||
final int id = mCallbackMaxId;
|
||||
Runnable r = new Runnable() {
|
||||
@ -94,32 +100,5 @@ public class Timers {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -10,12 +10,14 @@ import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.media.Image;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
|
||||
import com.stardust.autojs.runtime.AbstractScriptRuntime;
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.autojs.runtime.ScriptVariable;
|
||||
import com.stardust.autojs.runtime.api.Loopers;
|
||||
import com.stardust.concurrent.VolatileBox;
|
||||
import com.stardust.pio.UncheckedIOException;
|
||||
import com.stardust.util.ScreenMetrics;
|
||||
@ -45,6 +47,7 @@ public class Images {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public boolean requestScreenCapture(final int width, final int height) {
|
||||
mScriptRuntime.requiresApi(21);
|
||||
colorFinder.prestartThreads();
|
||||
@ -54,7 +57,8 @@ public class Images {
|
||||
@Override
|
||||
public void onRequestResult(int result, Intent data) {
|
||||
if (result == Activity.RESULT_OK) {
|
||||
mScreenCapturer = new ScreenCapturer(mContext, data, width, height);
|
||||
mScreenCapturer = new ScreenCapturer(mContext, data, width, height, ScreenMetrics.getDeviceScreenDensity(),
|
||||
new Handler(mScriptRuntime.loopers.getServantLooper()));
|
||||
requestResult.setAndNotify(true);
|
||||
} else {
|
||||
requestResult.setAndNotify(false);
|
||||
@ -65,6 +69,7 @@ public class Images {
|
||||
return requestResult.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public boolean requestScreenCapture() {
|
||||
return requestScreenCapture(ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
|
||||
}
|
||||
@ -72,7 +77,7 @@ public class Images {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public Image captureScreen() {
|
||||
mScriptRuntime.requiresApi(21);
|
||||
if(mScreenCapturer == null){
|
||||
if (mScreenCapturer == null) {
|
||||
throw new SecurityException("No screen capture permission");
|
||||
}
|
||||
colorFinder.prestartThreads();
|
||||
|
||||
@ -3,6 +3,7 @@ package com.stardust.autojs.runtime.api.image;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
@ -11,17 +12,15 @@ import android.media.ImageReader;
|
||||
import android.media.projection.MediaProjection;
|
||||
import android.media.projection.MediaProjectionManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.util.ScreenMetrics;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/17.
|
||||
*/
|
||||
@ -36,15 +35,25 @@ public class ScreenCapturer {
|
||||
private final Object mImageWaitingLock = new Object();
|
||||
private volatile Image mImage;
|
||||
private volatile Image mLatestImage;
|
||||
private final int mScreenWidth;
|
||||
private final int mScreenHeight;
|
||||
private final int mScreenDensity;
|
||||
private Handler mHandler;
|
||||
private volatile boolean mImageAvailable = false;
|
||||
|
||||
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight, int screenDensity) {
|
||||
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight, int screenDensity, Handler handler) {
|
||||
mScreenWidth = screenWidth;
|
||||
mScreenHeight = screenHeight;
|
||||
mScreenDensity = screenDensity;
|
||||
mHandler = handler;
|
||||
MediaProjectionManager manager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
|
||||
initVirtualDisplay(manager, data, screenWidth, screenHeight, screenDensity);
|
||||
mHandler = handler;
|
||||
startAcquireImageLoop();
|
||||
}
|
||||
|
||||
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight) {
|
||||
this(context, data, screenWidth, screenHeight, ScreenMetrics.getDeviceScreenDensity());
|
||||
this(context, data, screenWidth, screenHeight, ScreenMetrics.getDeviceScreenDensity(), null);
|
||||
}
|
||||
|
||||
public ScreenCapturer(Context context, Intent data) {
|
||||
@ -57,47 +66,61 @@ public class ScreenCapturer {
|
||||
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
|
||||
screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
|
||||
mImageReader.getSurface(), null, null);
|
||||
|
||||
}
|
||||
|
||||
private void startAcquireImageLoop() {
|
||||
if (mHandler != null) {
|
||||
setImageListener(mHandler);
|
||||
return;
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
mImageAcquireLooper = Looper.myLooper();
|
||||
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
|
||||
@Override
|
||||
public void onImageAvailable(ImageReader reader) {
|
||||
if (mLatestImage != null) {
|
||||
mLatestImage.close();
|
||||
}
|
||||
mLatestImage = reader.acquireNextImage();
|
||||
if (mLatestImage != null) {
|
||||
synchronized (mImageWaitingLock) {
|
||||
mImageWaitingLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
setImageListener(new Handler());
|
||||
Looper.loop();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public Image capture() {
|
||||
if (mLatestImage == null) {
|
||||
if (mImage != null) {
|
||||
return mImage;
|
||||
private void setImageListener(Handler handler) {
|
||||
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
|
||||
@Override
|
||||
public void onImageAvailable(ImageReader reader) {
|
||||
if (mImageAvailable) {
|
||||
return;
|
||||
}
|
||||
mLatestImage = reader.acquireLatestImage();
|
||||
if (mLatestImage != null) {
|
||||
mImageAvailable = true;
|
||||
synchronized (mImageWaitingLock) {
|
||||
mImageWaitingLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, handler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Image capture() {
|
||||
if (!mImageAvailable) {
|
||||
waitForImageAvailable();
|
||||
return mLatestImage;
|
||||
}
|
||||
if (mImage != null) {
|
||||
mImage.close();
|
||||
if (mLatestImage != null) {
|
||||
tryClose(mLatestImage);
|
||||
}
|
||||
mLatestImage = mImageReader.acquireLatestImage();
|
||||
return mLatestImage;
|
||||
}
|
||||
|
||||
private void tryClose(Image image) {
|
||||
try {
|
||||
image.close();
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
mImage = mLatestImage;
|
||||
mLatestImage = null;
|
||||
return mImage;
|
||||
}
|
||||
|
||||
private void waitForImageAvailable() {
|
||||
@ -111,6 +134,17 @@ public class ScreenCapturer {
|
||||
}
|
||||
}
|
||||
|
||||
public int getScreenWidth() {
|
||||
return mScreenWidth;
|
||||
}
|
||||
|
||||
public int getScreenHeight() {
|
||||
return mScreenHeight;
|
||||
}
|
||||
|
||||
public int getScreenDensity() {
|
||||
return mScreenDensity;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public void release() {
|
||||
|
||||
@ -4,6 +4,7 @@ import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.GestureDescription;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
@ -12,6 +13,7 @@ import com.stardust.autojs.runtime.AbstractScriptRuntime;
|
||||
import com.stardust.autojs.runtime.AccessibilityBridge;
|
||||
import com.stardust.autojs.runtime.ScriptInterface;
|
||||
import com.stardust.autojs.runtime.api.AutomatorConfig;
|
||||
import com.stardust.autojs.runtime.api.Loopers;
|
||||
import com.stardust.automator.GlobalActionAutomator;
|
||||
import com.stardust.automator.UiObject;
|
||||
import com.stardust.automator.simple_action.ActionFactory;
|
||||
@ -30,7 +32,8 @@ public class SimpleActionAutomator {
|
||||
|
||||
private AccessibilityBridge mAccessibilityBridge;
|
||||
private AbstractScriptRuntime mScriptRuntime;
|
||||
private GlobalActionAutomator mGlobalActionAutomator = new GlobalActionAutomator();
|
||||
private GlobalActionAutomator mGlobalActionAutomator;
|
||||
private ScreenMetrics mScreenMetrics;
|
||||
|
||||
public SimpleActionAutomator(AccessibilityBridge accessibilityBridge, AbstractScriptRuntime scriptRuntime) {
|
||||
mAccessibilityBridge = accessibilityBridge;
|
||||
@ -190,6 +193,10 @@ public class SimpleActionAutomator {
|
||||
private void prepareForGesture() {
|
||||
ensureAccessibilityServiceEnabled();
|
||||
mScriptRuntime.requiresApi(24);
|
||||
if (mGlobalActionAutomator != null)
|
||||
return;
|
||||
mGlobalActionAutomator = new GlobalActionAutomator(new Handler(mScriptRuntime.loopers.getServantLooper()));
|
||||
mGlobalActionAutomator.setScreenMetrics(mScreenMetrics);
|
||||
mGlobalActionAutomator.setService(mAccessibilityBridge.getService());
|
||||
}
|
||||
|
||||
@ -229,8 +236,10 @@ public class SimpleActionAutomator {
|
||||
return service.performGlobalAction(action);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@ScriptInterface
|
||||
public boolean paste(ActionTarget target) {
|
||||
mScriptRuntime.requiresApi(18);
|
||||
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
|
||||
}
|
||||
|
||||
@ -260,7 +269,9 @@ public class SimpleActionAutomator {
|
||||
}
|
||||
|
||||
public void setScreenMetrics(ScreenMetrics metrics) {
|
||||
mGlobalActionAutomator.setScreenMetrics(metrics);
|
||||
mScreenMetrics = metrics;
|
||||
if (mGlobalActionAutomator != null)
|
||||
mGlobalActionAutomator.setScreenMetrics(metrics);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import android.graphics.Path;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
@ -20,6 +21,11 @@ public class GlobalActionAutomator {
|
||||
|
||||
private AccessibilityService mService;
|
||||
private ScreenMetrics mScreenMetrics;
|
||||
private Handler mHandler;
|
||||
|
||||
public GlobalActionAutomator(@Nullable Handler handler) {
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public void setService(AccessibilityService service) {
|
||||
mService = service;
|
||||
@ -95,10 +101,36 @@ public class GlobalActionAutomator {
|
||||
for (GestureDescription.StrokeDescription stroke : strokes) {
|
||||
builder.addStroke(stroke);
|
||||
}
|
||||
if (mHandler == null) {
|
||||
return gesturesWithoutHandler(builder.build());
|
||||
} else {
|
||||
return gesturesWithHandler(builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
private boolean gesturesWithHandler(GestureDescription description) {
|
||||
final VolatileBox<Boolean> result = new VolatileBox<>(false);
|
||||
mService.dispatchGesture(description, new AccessibilityService.GestureResultCallback() {
|
||||
@Override
|
||||
public void onCompleted(GestureDescription gestureDescription) {
|
||||
result.setAndNotify(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelled(GestureDescription gestureDescription) {
|
||||
result.setAndNotify(false);
|
||||
}
|
||||
}, mHandler);
|
||||
return result.blockedGet();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
private boolean gesturesWithoutHandler(GestureDescription description) {
|
||||
prepareLooperIfNeeded();
|
||||
final VolatileBox<Boolean> result = new VolatileBox<>(false);
|
||||
Handler handler = new Handler(Looper.myLooper());
|
||||
mService.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() {
|
||||
mService.dispatchGesture(description, new AccessibilityService.GestureResultCallback() {
|
||||
@Override
|
||||
public void onCompleted(GestureDescription gestureDescription) {
|
||||
result.set(true);
|
||||
@ -129,7 +161,7 @@ public class GlobalActionAutomator {
|
||||
private void quitLoop() {
|
||||
Looper looper = Looper.myLooper();
|
||||
if (looper != null) {
|
||||
looper.quitSafely();
|
||||
looper.quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user