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:
hyb1996
2017-07-29 17:46:23 +08:00
parent 11b87d9a51
commit 12037ba488
21 changed files with 382 additions and 173 deletions

View File

@@ -0,0 +1,16 @@
## 控件
对于一般软件而言,其界面是由控件组成的。
一段文本、一张图片、一个列表都是一个控件(widget),文本用文本控件(TextView)显示,图片用图片控件(ImageView)显示。
此外还有一类特殊的控件,其本身不显示内容,而是作为其他控件的"容器"使用,决定在他里面的控件的位置和排放。这类控件称为"布局"(layout)。
常见的布局有线性布局(LinearLayout), 相对布局(RelativeLayout), 帧布局(FrameLayout), 表格布局(GridLayout)等。
例如线性布局,在他里面的控件是线性(垂直或平行)地摆放的。比如一个水平的线性布局,里面依次有一个文本控件和一个图片控件,那么显示时图片将会在文本的右边。
## 控件的属性
打开悬浮窗,找到

View File

@@ -8,7 +8,6 @@
events也是一个\[EventEmitter\](#EventEmitter)可以使用EventEmitter的所有函数这里只介绍其自身的函数。 events也是一个\[EventEmitter\](#EventEmitter)可以使用EventEmitter的所有函数这里只介绍其自身的函数。
注意events内置的触摸、按键事件必须调用loop()函数才能正常工作。例如:
``` ```
events.observeKey(); events.observeKey();
events.onKeyDown("volume_up", function(event){ events.onKeyDown("volume_up", function(event){
@@ -18,7 +17,6 @@ events.onKeyDown("menu", function(event){
toast("菜单键被按下了"); toast("菜单键被按下了");
}); });
loop();
//后面的语句不会再执行了 //后面的语句不会再执行了
``` ```

View File

@@ -2,22 +2,6 @@ timer 模块暴露了一个全局的 API用于在某个未来时间段调用
Auto.js 中的计时器函数实现了与 Web 浏览器提供的定时器类似的 API除了它使用了一个不同的内部实现它是基于 Android Looper-Handler机制构建的。其实现机制与Node.js比较相似。 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\]) ### setImmediate(callback\[, ...args\])
* callback \<Function\> 在 Auto.js 循环的当前回合结束时要调用的函数。 * callback \<Function\> 在 Auto.js 循环的当前回合结束时要调用的函数。
* ...args \<any\> 当调用 callback 时要传入的可选参数。 * ...args \<any\> 当调用 callback 时要传入的可选参数。
@@ -49,12 +33,12 @@ callback 可能不会精确地在 delay 毫秒被调用。 Auto.js 不能保证
setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。 setImmediate()、setInterval() 和 setTimeout() 方法每次都会返回表示预定的计时器的id。 它们可用于取消定时器并防止触发。
### clearImmediate(id)# ### clearImmediate(id)
* id \<Number\> 一个 setImmediate() 返回的 id。 * id \<Number\> 一个 setImmediate() 返回的 id。
取消一个由 setImmediate() 创建的 Immediate 对象。 取消一个由 setImmediate() 创建的 Immediate 对象。
### clearInterval(id)# ### clearInterval(id)
* id \<Number\> 一个 setInterval() 返回的 id。 * id \<Number\> 一个 setInterval() 返回的 id。
取消一个由 setInterval() 创建的 Timeout 对象。 取消一个由 setInterval() 创建的 Timeout 对象。

View File

@@ -292,6 +292,12 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
mDrawerHeaderBackgroundSaver.select(this, mActivityResultMediator); mDrawerHeaderBackgroundSaver.select(this, mActivityResultMediator);
} }
@Override
protected void onPause() {
super.onPause();
stopService(new Intent(this, DownloadService.class));
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
@@ -477,6 +483,11 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }
@NonNull
@Override
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
return mActivityResultMediator;
}
public static void onRecordStop(Context context, String script) { public static void onRecordStop(Context context, String script) {
Intent intent = new Intent(context, MainActivity_.class) Intent intent = new Intent(context, MainActivity_.class)
@@ -488,11 +499,5 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
context.startActivity(intent); context.startActivity(intent);
} }
@NonNull
@Override
public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
return mActivityResultMediator;
}
} }

View File

@@ -44,7 +44,6 @@ import java.util.concurrent.Executor;
@EFragment(R.layout.fragment_side_menu) @EFragment(R.layout.fragment_side_menu)
public class SideMenuFragment extends android.support.v4.app.Fragment { public class SideMenuFragment extends android.support.v4.app.Fragment {
public static void setFragment(FragmentActivity activity, int viewId) { public static void setFragment(FragmentActivity activity, int viewId) {
SideMenuFragment fragment = new SideMenuFragment_(); SideMenuFragment fragment = new SideMenuFragment_();
activity.getSupportFragmentManager().beginTransaction().replace(viewId, fragment).commit(); activity.getSupportFragmentManager().beginTransaction().replace(viewId, fragment).commit();

View File

@@ -1,4 +1,6 @@
__runtime__.init();
if(__engine_name__ == "rhino"){ if(__engine_name__ == "rhino"){
__importClass__ = importClass; __importClass__ = importClass;
var importClass = function(pack){ var importClass = function(pack){

View File

@@ -2,7 +2,11 @@
module.exports = function(__runtime__, scope){ module.exports = function(__runtime__, scope){
var timers = Object.create(__runtime__.timers); 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; return timers;
} }

View File

@@ -183,10 +183,6 @@ public class ScriptEngineService {
mUiHandler.toast(mContext.getString(R.string.text_no_running_script)); mUiHandler.toast(mContext.getString(R.string.text_no_running_script));
} }
public String[] getGlobalFunctions() {
return mScriptEngineManager.getGlobalFunctions();
}
public Set<ScriptEngine> getEngines() { public Set<ScriptEngine> getEngines() {
return mScriptEngineManager.getEngines(); return mScriptEngineManager.getEngines();
} }

View File

@@ -2,12 +2,6 @@ package com.stardust.autojs.engine;
import android.content.Context; 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.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@@ -25,10 +19,10 @@ public abstract class AbstractScriptEngineManager implements ScriptEngineManager
private final Set<ScriptEngine> mEngines = new HashSet<>(); private final Set<ScriptEngine> mEngines = new HashSet<>();
private EngineLifecycleCallback mEngineLifecycleCallback; private EngineLifecycleCallback mEngineLifecycleCallback;
private android.content.Context mContext; private android.content.Context mAndroidContext;
public AbstractScriptEngineManager(Context context) { public AbstractScriptEngineManager(Context androidContext) {
mContext = context; mAndroidContext = androidContext;
} }
public ScriptEngine createEngine() { public ScriptEngine createEngine() {
@@ -61,10 +55,8 @@ public abstract class AbstractScriptEngineManager implements ScriptEngineManager
protected abstract ScriptEngine createEngineInner(); protected abstract ScriptEngine createEngineInner();
public abstract String[] getGlobalFunctions(); public android.content.Context getAndroidContext() {
return mAndroidContext;
public android.content.Context getContext() {
return mContext;
} }
protected void putProperties(ScriptEngine engine) { protected void putProperties(ScriptEngine engine) {

View File

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

View File

@@ -77,7 +77,6 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
public void forceStop() { public void forceStop() {
Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread); Log.d(LOG_TAG, "forceStop: interrupt Thread: " + mThread);
mThread.interrupt(); mThread.interrupt();
Timers.quitLooperIfNeeded(mThread);
} }
public RhinoJavaScriptEngineManager getEngineManager() { public RhinoJavaScriptEngineManager getEngineManager() {
@@ -91,7 +90,6 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
contextCount--; contextCount--;
Log.d(LOG_TAG, "contextCount = " + contextCount); Log.d(LOG_TAG, "contextCount = " + contextCount);
mEngineManager.removeEngine(this); mEngineManager.removeEngine(this);
Timers.removeThreadRecord(mThread);
mDestroyed = true; mDestroyed = true;
} }
@@ -110,6 +108,10 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
return mTags.get(key); return mTags.get(key);
} }
public Thread getThread() {
return mThread;
}
@Override @Override
public void init() { public void init() {
ScriptableObject.putProperty(mScriptable, "__engine_name__", "rhino"); ScriptableObject.putProperty(mScriptable, "__engine_name__", "rhino");
@@ -128,7 +130,7 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
for (String path : mRequirePath) { for (String path : mRequirePath) {
list.add(new File(path).toURI()); list.add(new File(path).toURI());
} }
AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getContext(), list); AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(getEngineManager().getAndroidContext(), list);
new RequireBuilder() new RequireBuilder()
.setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider)) .setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider))
.setSandboxed(false) .setSandboxed(false)
@@ -152,9 +154,9 @@ public class RhinoJavaScriptEngine implements ScriptEngine {
protected Context createContext() { protected Context createContext() {
if (!ContextFactory.hasExplicitGlobal()) { 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++; contextCount++;
context.setOptimizationLevel(-1); context.setOptimizationLevel(-1);
context.setLanguageVersion(Context.VERSION_ES6); context.setLanguageVersion(Context.VERSION_ES6);

View File

@@ -7,22 +7,7 @@ import com.stardust.autojs.script.SequenceScriptSource;
import com.stardust.autojs.script.StringScriptSource; import com.stardust.autojs.script.StringScriptSource;
import com.stardust.pio.PFile; 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.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. * Created by Stardust on 2017/3/1.
@@ -40,7 +25,7 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
} }
protected RhinoJavaScriptEngine createEngineInner() { protected RhinoJavaScriptEngine createEngineInner() {
RhinoJavaScriptEngine engine = new RhinoJavaScriptEngine(this); RhinoJavaScriptEngine engine = new LoopBasedJavaScriptEngine(this);
return engine; return engine;
} }
@@ -58,7 +43,7 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
private String readInitScript() { private String readInitScript() {
try { try {
return PFile.read(getContext().getAssets().open("javascript_engine_init.js")); return PFile.read(getAndroidContext().getAssets().open("javascript_engine_init.js"));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -76,28 +61,5 @@ public class RhinoJavaScriptEngineManager extends AbstractScriptEngineManager {
return mInitScript; 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;
}
} }

View File

@@ -26,8 +26,6 @@ public interface ScriptEngineManager {
Set<ScriptEngine> getEngines(); Set<ScriptEngine> getEngines();
String[] getGlobalFunctions();
int stopAll(); int stopAll();
} }

View File

@@ -2,11 +2,13 @@ package com.stardust.autojs.runtime;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Looper;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import com.stardust.autojs.engine.ScriptEngine; import com.stardust.autojs.engine.ScriptEngine;
import com.stardust.autojs.runtime.api.AbstractShell; import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils; 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.ScriptBridges;
import com.stardust.autojs.runtime.api.Console; import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.Events; import com.stardust.autojs.runtime.api.Events;
@@ -21,8 +23,6 @@ import com.stardust.util.ScreenMetrics;
import com.stardust.util.UiHandler; import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider; import com.stardust.view.accessibility.AccessibilityInfoProvider;
import org.mozilla.javascript.annotations.JSFunction;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
/** /**
@@ -56,15 +56,22 @@ public abstract class AbstractScriptRuntime {
public ScriptBridges bridges = new ScriptBridges(); public ScriptBridges bridges = new ScriptBridges();
@ScriptVariable @ScriptVariable
public Timers timers = new Timers(bridges); public Loopers loopers;
@ScriptVariable
public Timers timers;
private Images images; private Images images;
private UiHandler mUiHandler;
private AccessibilityBridge mAccessibilityBridge;
private static WeakReference<Context> applicationContext; private static WeakReference<Context> applicationContext;
public AbstractScriptRuntime(UiHandler uiHandler, Console console, AccessibilityBridge bridge, AppUtils appUtils, ScreenCaptureRequester screenCaptureRequester) { public AbstractScriptRuntime(UiHandler uiHandler, Console console, AccessibilityBridge bridge, AppUtils appUtils, ScreenCaptureRequester screenCaptureRequester) {
this.app = appUtils; this.app = appUtils;
this.console = console; this.console = console;
mAccessibilityBridge = bridge;
mUiHandler = uiHandler;
this.automator = new SimpleActionAutomator(bridge, this); this.automator = new SimpleActionAutomator(bridge, this);
this.info = bridge.getInfoProvider(); this.info = bridge.getInfoProvider();
Context context = uiHandler.getContext(); Context context = uiHandler.getContext();
@@ -73,7 +80,18 @@ public abstract class AbstractScriptRuntime {
images = new Images(context, this, screenCaptureRequester); images = new Images(context, this, screenCaptureRequester);
} }
dialogs = new Dialogs(app, uiHandler); 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) { public static void setApplicationContext(Context context) {
@@ -130,10 +148,15 @@ public abstract class AbstractScriptRuntime {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
images.releaseScreenCapturer(); images.releaseScreenCapturer();
} }
events.recycle(); if (events != null)
events.recycle();
if (loopers != null)
loopers.quitAll();
} }
public Object getImages() { public Object getImages() {
return images; return images;
} }
} }

View File

@@ -27,27 +27,29 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
private AccessibilityBridge mAccessibilityBridge; private AccessibilityBridge mAccessibilityBridge;
private Context mContext; private Context mContext;
private TouchObserver mTouchObserver; private TouchObserver mTouchObserver;
private Timers mTimers;
private long mLastTouchEventMillis; private long mLastTouchEventMillis;
private long mTouchEventTimeout = 10; private long mTouchEventTimeout = 10;
private boolean mListeningKey = false; 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); super(bridges);
mAccessibilityBridge = accessibilityBridge; mAccessibilityBridge = accessibilityBridge;
mContext = context; mContext = context;
mTimers = timers; mLoopers = loopers;
} }
public EventEmitter emitter(){ public EventEmitter emitter() {
return new EventEmitter(mBridges); return new EventEmitter(mBridges);
} }
public void observeKey() { public void observeKey() {
if (mListeningKey) if (mListeningKey)
return; return;
ensureHandler();
mLoopers.waitWhenIdle(true);
mListeningKey = true; mListeningKey = true;
mTimers.prepareLoopIfNeeded();
mAccessibilityBridge.ensureServiceEnabled(); mAccessibilityBridge.ensureServiceEnabled();
AccessibilityService service = mAccessibilityBridge.getService(); AccessibilityService service = mAccessibilityBridge.getService();
if (service == null) if (service == null)
@@ -55,10 +57,17 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
service.getOnKeyObserver().addListener(this); service.getOnKeyObserver().addListener(this);
} }
private void ensureHandler() {
if(mHandler == null){
mHandler = new Handler();
}
}
public void observeTouch() { public void observeTouch() {
mTimers.prepareLoopIfNeeded();
if (mTouchObserver != null) if (mTouchObserver != null)
return; return;
ensureHandler();
mLoopers.waitWhenIdle(true);
mTouchObserver = new TouchObserver(mContext); mTouchObserver = new TouchObserver(mContext);
mTouchObserver.setOnTouchEventListener(this); mTouchObserver.setOnTouchEventListener(this);
mTouchObserver.observe(); mTouchObserver.observe();
@@ -128,7 +137,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
@Override @Override
public void onKeyEvent(final int keyCode, final KeyEvent event) { public void onKeyEvent(final int keyCode, final KeyEvent event) {
mTimers.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase(); String keyName = KeyEvent.keyCodeToString(keyCode).substring(8).toLowerCase();
@@ -151,7 +160,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
return; return;
} }
mLastTouchEventMillis = System.currentTimeMillis(); mLastTouchEventMillis = System.currentTimeMillis();
mTimers.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
emit("touch", new Point(x, y)); emit("touch", new Point(x, y));

View File

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

View File

@@ -12,18 +12,23 @@ import java.util.concurrent.ConcurrentHashMap;
public class Timers { public class Timers {
private static ConcurrentHashMap<Thread, Looper> sLoopers = new ConcurrentHashMap<>();
private Handler mHandler;
private SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>(); private SparseArray<Runnable> mHandlerCallbacks = new SparseArray<>();
private int mCallbackMaxId = 0; private int mCallbackMaxId = 0;
private ScriptBridges mBridges; private ScriptBridges mBridges;
private Handler mHandler;
public Timers(ScriptBridges bridges) { public Timers(ScriptBridges bridges) {
mBridges = bridges; mBridges = bridges;
} }
private void ensureHander(){
if(mHandler == null){
mHandler = new Handler();
}
}
public int setTimeout(final Object callback, long delay, final Object... args) { public int setTimeout(final Object callback, long delay, final Object... args) {
prepareLoopIfNeeded(); ensureHander();
mCallbackMaxId++; mCallbackMaxId++;
final int id = mCallbackMaxId; final int id = mCallbackMaxId;
Runnable r = new Runnable() { Runnable r = new Runnable() {
@@ -39,6 +44,7 @@ public class Timers {
} }
public void post(Runnable r) { public void post(Runnable r) {
ensureHander();
mHandler.post(r); mHandler.post(r);
} }
@@ -47,7 +53,7 @@ public class Timers {
} }
public int setInterval(final Object listener, final long interval, final Object... args) { public int setInterval(final Object listener, final long interval, final Object... args) {
prepareLoopIfNeeded(); ensureHander();
mCallbackMaxId++; mCallbackMaxId++;
final int id = mCallbackMaxId; final int id = mCallbackMaxId;
Runnable r = new Runnable() { Runnable r = new Runnable() {
@@ -67,7 +73,7 @@ public class Timers {
} }
public int setImmediate(final Object listener, final Object... args) { public int setImmediate(final Object listener, final Object... args) {
prepareLoopIfNeeded(); ensureHander();
mCallbackMaxId++; mCallbackMaxId++;
final int id = mCallbackMaxId; final int id = mCallbackMaxId;
Runnable r = new Runnable() { 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();
}
}
} }

View File

@@ -10,12 +10,14 @@ import android.graphics.Matrix;
import android.graphics.Point; import android.graphics.Point;
import android.media.Image; import android.media.Image;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.util.Log; import android.util.Log;
import com.stardust.autojs.runtime.AbstractScriptRuntime; import com.stardust.autojs.runtime.AbstractScriptRuntime;
import com.stardust.autojs.runtime.ScriptInterruptedException; import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.runtime.ScriptVariable; import com.stardust.autojs.runtime.ScriptVariable;
import com.stardust.autojs.runtime.api.Loopers;
import com.stardust.concurrent.VolatileBox; import com.stardust.concurrent.VolatileBox;
import com.stardust.pio.UncheckedIOException; import com.stardust.pio.UncheckedIOException;
import com.stardust.util.ScreenMetrics; import com.stardust.util.ScreenMetrics;
@@ -45,6 +47,7 @@ public class Images {
mContext = context; mContext = context;
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean requestScreenCapture(final int width, final int height) { public boolean requestScreenCapture(final int width, final int height) {
mScriptRuntime.requiresApi(21); mScriptRuntime.requiresApi(21);
colorFinder.prestartThreads(); colorFinder.prestartThreads();
@@ -54,7 +57,8 @@ public class Images {
@Override @Override
public void onRequestResult(int result, Intent data) { public void onRequestResult(int result, Intent data) {
if (result == Activity.RESULT_OK) { 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); requestResult.setAndNotify(true);
} else { } else {
requestResult.setAndNotify(false); requestResult.setAndNotify(false);
@@ -65,6 +69,7 @@ public class Images {
return requestResult.blockedGetOrThrow(ScriptInterruptedException.class); return requestResult.blockedGetOrThrow(ScriptInterruptedException.class);
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean requestScreenCapture() { public boolean requestScreenCapture() {
return requestScreenCapture(ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight()); return requestScreenCapture(ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
} }
@@ -72,7 +77,7 @@ public class Images {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Image captureScreen() { public Image captureScreen() {
mScriptRuntime.requiresApi(21); mScriptRuntime.requiresApi(21);
if(mScreenCapturer == null){ if (mScreenCapturer == null) {
throw new SecurityException("No screen capture permission"); throw new SecurityException("No screen capture permission");
} }
colorFinder.prestartThreads(); colorFinder.prestartThreads();

View File

@@ -3,6 +3,7 @@ package com.stardust.autojs.runtime.api.image;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Paint;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplay;
@@ -11,17 +12,15 @@ import android.media.ImageReader;
import android.media.projection.MediaProjection; import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager; import android.media.projection.MediaProjectionManager;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.util.Log; import android.util.Log;
import android.widget.ImageView;
import com.stardust.autojs.runtime.ScriptInterruptedException; import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.util.ScreenMetrics; import com.stardust.util.ScreenMetrics;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Created by Stardust on 2017/5/17. * Created by Stardust on 2017/5/17.
*/ */
@@ -36,15 +35,25 @@ public class ScreenCapturer {
private final Object mImageWaitingLock = new Object(); private final Object mImageWaitingLock = new Object();
private volatile Image mImage; private volatile Image mImage;
private volatile Image mLatestImage; 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); MediaProjectionManager manager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
initVirtualDisplay(manager, data, screenWidth, screenHeight, screenDensity); initVirtualDisplay(manager, data, screenWidth, screenHeight, screenDensity);
mHandler = handler;
startAcquireImageLoop(); startAcquireImageLoop();
} }
public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight) { 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) { public ScreenCapturer(Context context, Intent data) {
@@ -57,47 +66,61 @@ public class ScreenCapturer {
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror", mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null); mImageReader.getSurface(), null, null);
} }
private void startAcquireImageLoop() { private void startAcquireImageLoop() {
if (mHandler != null) {
setImageListener(mHandler);
return;
}
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
Looper.prepare(); Looper.prepare();
mImageAcquireLooper = Looper.myLooper(); mImageAcquireLooper = Looper.myLooper();
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { setImageListener(new Handler());
@Override
public void onImageAvailable(ImageReader reader) {
if (mLatestImage != null) {
mLatestImage.close();
}
mLatestImage = reader.acquireNextImage();
if (mLatestImage != null) {
synchronized (mImageWaitingLock) {
mImageWaitingLock.notify();
}
}
}
}, null);
Looper.loop(); Looper.loop();
} }
}).start(); }).start();
} }
public Image capture() { private void setImageListener(Handler handler) {
if (mLatestImage == null) { mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
if (mImage != null) { @Override
return mImage; 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(); waitForImageAvailable();
return mLatestImage;
} }
if (mImage != null) { if (mLatestImage != null) {
mImage.close(); 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() { 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) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void release() { public void release() {

View File

@@ -4,6 +4,7 @@ import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription; import android.accessibilityservice.GestureDescription;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.util.Log; import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo; 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.AccessibilityBridge;
import com.stardust.autojs.runtime.ScriptInterface; import com.stardust.autojs.runtime.ScriptInterface;
import com.stardust.autojs.runtime.api.AutomatorConfig; import com.stardust.autojs.runtime.api.AutomatorConfig;
import com.stardust.autojs.runtime.api.Loopers;
import com.stardust.automator.GlobalActionAutomator; import com.stardust.automator.GlobalActionAutomator;
import com.stardust.automator.UiObject; import com.stardust.automator.UiObject;
import com.stardust.automator.simple_action.ActionFactory; import com.stardust.automator.simple_action.ActionFactory;
@@ -30,7 +32,8 @@ public class SimpleActionAutomator {
private AccessibilityBridge mAccessibilityBridge; private AccessibilityBridge mAccessibilityBridge;
private AbstractScriptRuntime mScriptRuntime; private AbstractScriptRuntime mScriptRuntime;
private GlobalActionAutomator mGlobalActionAutomator = new GlobalActionAutomator(); private GlobalActionAutomator mGlobalActionAutomator;
private ScreenMetrics mScreenMetrics;
public SimpleActionAutomator(AccessibilityBridge accessibilityBridge, AbstractScriptRuntime scriptRuntime) { public SimpleActionAutomator(AccessibilityBridge accessibilityBridge, AbstractScriptRuntime scriptRuntime) {
mAccessibilityBridge = accessibilityBridge; mAccessibilityBridge = accessibilityBridge;
@@ -190,6 +193,10 @@ public class SimpleActionAutomator {
private void prepareForGesture() { private void prepareForGesture() {
ensureAccessibilityServiceEnabled(); ensureAccessibilityServiceEnabled();
mScriptRuntime.requiresApi(24); mScriptRuntime.requiresApi(24);
if (mGlobalActionAutomator != null)
return;
mGlobalActionAutomator = new GlobalActionAutomator(new Handler(mScriptRuntime.loopers.getServantLooper()));
mGlobalActionAutomator.setScreenMetrics(mScreenMetrics);
mGlobalActionAutomator.setService(mAccessibilityBridge.getService()); mGlobalActionAutomator.setService(mAccessibilityBridge.getService());
} }
@@ -229,8 +236,10 @@ public class SimpleActionAutomator {
return service.performGlobalAction(action); return service.performGlobalAction(action);
} }
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
@ScriptInterface @ScriptInterface
public boolean paste(ActionTarget target) { public boolean paste(ActionTarget target) {
mScriptRuntime.requiresApi(18);
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE)); return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
} }
@@ -260,7 +269,9 @@ public class SimpleActionAutomator {
} }
public void setScreenMetrics(ScreenMetrics metrics) { public void setScreenMetrics(ScreenMetrics metrics) {
mGlobalActionAutomator.setScreenMetrics(metrics); mScreenMetrics = metrics;
if (mGlobalActionAutomator != null)
mGlobalActionAutomator.setScreenMetrics(metrics);
} }
} }

View File

@@ -6,6 +6,7 @@ import android.graphics.Path;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
@@ -20,6 +21,11 @@ public class GlobalActionAutomator {
private AccessibilityService mService; private AccessibilityService mService;
private ScreenMetrics mScreenMetrics; private ScreenMetrics mScreenMetrics;
private Handler mHandler;
public GlobalActionAutomator(@Nullable Handler handler) {
mHandler = handler;
}
public void setService(AccessibilityService service) { public void setService(AccessibilityService service) {
mService = service; mService = service;
@@ -95,10 +101,36 @@ public class GlobalActionAutomator {
for (GestureDescription.StrokeDescription stroke : strokes) { for (GestureDescription.StrokeDescription stroke : strokes) {
builder.addStroke(stroke); 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(); prepareLooperIfNeeded();
final VolatileBox<Boolean> result = new VolatileBox<>(false); final VolatileBox<Boolean> result = new VolatileBox<>(false);
Handler handler = new Handler(Looper.myLooper()); Handler handler = new Handler(Looper.myLooper());
mService.dispatchGesture(builder.build(), new AccessibilityService.GestureResultCallback() { mService.dispatchGesture(description, new AccessibilityService.GestureResultCallback() {
@Override @Override
public void onCompleted(GestureDescription gestureDescription) { public void onCompleted(GestureDescription gestureDescription) {
result.set(true); result.set(true);
@@ -129,7 +161,7 @@ public class GlobalActionAutomator {
private void quitLoop() { private void quitLoop() {
Looper looper = Looper.myLooper(); Looper looper = Looper.myLooper();
if (looper != null) { if (looper != null) {
looper.quitSafely(); looper.quit();
} }
} }