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内置的触摸、按键事件必须调用loop()函数才能正常工作。例如:
```
events.observeKey();
events.onKeyDown("volume_up", function(event){
@ -18,7 +17,6 @@ events.onKeyDown("menu", function(event){
toast("菜单键被按下了");
});
loop();
//后面的语句不会再执行了
```

View File

@ -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 对象。

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

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() {
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);

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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() {

View File

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

View File

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