diff --git a/app/build.gradle b/app/build.gradle
index 518067f9..1da94332 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,8 +9,8 @@ android {
applicationId "com.stardust.scriptdroid"
minSdkVersion 19
targetSdkVersion 23
- versionCode 132
- versionName "2.0.11 Alpha3"
+ versionCode 134
+ versionName "2.0.12 Alpha"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6da2e2a1..c6a7458e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -84,6 +84,9 @@
+
可选参数
\ No newline at end of file
diff --git a/app/src/main/assets/js/autojs_init.js b/app/src/main/assets/js/autojs_init.js
index 709faded..7e7a23c2 100644
--- a/app/src/main/assets/js/autojs_init.js
+++ b/app/src/main/assets/js/autojs_init.js
@@ -5,7 +5,8 @@ var dialogs = {};
dialogs.rawInput = function(title, prefill){
prefill = prefill || "";
- return String(__runtime__.dialogs.rawInput(title, prefill));
+ var s = __runtime__.dialogs.rawInput(title, prefill);
+ return s ? String(s) : null;
};
dialogs.input = function(title, prefill){
diff --git a/app/src/main/assets/sample/图片与图色处理/区域找色1.js b/app/src/main/assets/sample/图片与图色处理/区域找色1.js
new file mode 100644
index 00000000..6a041722
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/区域找色1.js
@@ -0,0 +1,17 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+//0xffffff为白色
+toastLog("开始找色");
+//指定在位置(90, 220)宽高为900*1000的区域找色。
+//0xff00cc是编辑器的深粉红色字体(字符串)颜色
+var point = findColorInRegion(img, 0xff00cc, 90, 220, 900, 1000);
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/assets/sample/图片与图色处理/区域找色2.js b/app/src/main/assets/sample/图片与图色处理/区域找色2.js
new file mode 100644
index 00000000..7a8bc33a
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/区域找色2.js
@@ -0,0 +1,18 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+//0xffffff为白色
+toastLog("开始找色");
+//指定在位置(90, 220)宽高为900*1000的区域找色。
+//0xff00cc是编辑器的深粉红色字体(字符串)颜色
+var point = findColor(img, 0xff00cc, {
+ region: [90, 220, 900, 1000],
+ threads: 8
+});
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
diff --git a/app/src/main/assets/sample/图片与图色处理/多线程找色.js b/app/src/main/assets/sample/图片与图色处理/多线程找色.js
new file mode 100644
index 00000000..d0ecacac
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/多线程找色.js
@@ -0,0 +1,19 @@
+//减少截图分辨率以提高速度
+if(!requestScreenCapture(640, 960)){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+toastLog("开始找色");
+//0x02b902为输入法绿色字体的颜色
+var point = findColor(img, 0x02b902, {
+ //指定用8个线程找色
+ threads: 8
+});
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/assets/sample/图片与图色处理/截图并保存1.js b/app/src/main/assets/sample/图片与图色处理/截图并保存1.js
new file mode 100644
index 00000000..6a72a9bf
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/截图并保存1.js
@@ -0,0 +1,6 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+images.saveImage(img, "/sdcard/1.png");
\ No newline at end of file
diff --git a/app/src/main/assets/sample/图片与图色处理/截图并保存2.js b/app/src/main/assets/sample/图片与图色处理/截图并保存2.js
new file mode 100644
index 00000000..286adaa1
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/截图并保存2.js
@@ -0,0 +1,6 @@
+//指定截图分辨率为 640×960
+if(!requestScreenCapture(640, 960)){
+ toast("请求截图失败");
+ stop();
+}
+captureScreen("/sdcard/1.png");
diff --git a/app/src/main/assets/sample/图片与图色处理/找到QQ红点位置.js b/app/src/main/assets/sample/图片与图色处理/找到QQ红点位置.js
new file mode 100644
index 00000000..04cd0945
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/找到QQ红点位置.js
@@ -0,0 +1,16 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+launchApp("QQ");
+sleep(2000);
+var img = captureScreen();
+toastLog("开始找色");
+var point = findColor(img, 0xf64d30);
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/assets/sample/图片与图色处理/找色与找色算法.js b/app/src/main/assets/sample/图片与图色处理/找色与找色算法.js
new file mode 100644
index 00000000..e8c2232d
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/找色与找色算法.js
@@ -0,0 +1,22 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+//0xffffff为白色
+toastLog("开始找色");
+var point = findColor(img, 0xffffff, {
+ //指定算法为rgb+,更默认算法rgb更准确,但时间更久
+ algorithm: "rgb+",
+ //指定颜色临界值为16
+ threshold: 16,
+ //指定用8个线程找色
+ threads: 8
+});
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/assets/sample/图片与图色处理/模糊找色.js b/app/src/main/assets/sample/图片与图色处理/模糊找色.js
new file mode 100644
index 00000000..7cbca253
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/模糊找色.js
@@ -0,0 +1,15 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+//0x9966ff为编辑器紫色字体的颜色
+toastLog("开始找色");
+var point = findColor(img, 0x9966ff);
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/assets/sample/图片与图色处理/精确找色.js b/app/src/main/assets/sample/图片与图色处理/精确找色.js
new file mode 100644
index 00000000..b37e7eb5
--- /dev/null
+++ b/app/src/main/assets/sample/图片与图色处理/精确找色.js
@@ -0,0 +1,16 @@
+if(!requestScreenCapture()){
+ toast("请求截图失败");
+ stop();
+}
+var img = captureScreen();
+toastLog("开始找色");
+//0x006699为编辑器蓝色字体(var)的颜色
+//找到颜色与0x006699完全相等的颜色
+var point = findColorEquals(img, 0x006699);
+if(point){
+ toastLog("x = " + point.x + ", y = " + point.y);
+}else{
+ toastLog("没有找到");
+}
+
+
diff --git a/app/src/main/java/com/stardust/scriptdroid/App.java b/app/src/main/java/com/stardust/scriptdroid/App.java
index dbfc9c33..b18636f9 100644
--- a/app/src/main/java/com/stardust/scriptdroid/App.java
+++ b/app/src/main/java/com/stardust/scriptdroid/App.java
@@ -1,6 +1,7 @@
package com.stardust.scriptdroid;
import android.app.Activity;
+import android.app.Application;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.multidex.MultiDexApplication;
@@ -79,26 +80,7 @@ public class App extends MultiDexApplication {
}
private void registerActivityLifecycleCallback() {
- registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
- @Override
- public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- super.onActivityCreated(activity, savedInstanceState);
- AutoJs.getInstance().getAppUtils().setCurrentActivity(activity);
- }
-
- @Override
- public void onActivityPaused(Activity activity) {
- AutoJs.getInstance().getAppUtils().setCurrentActivity(null);
- }
-
- @Override
- public void onActivityResumed(Activity activity) {
- ScreenMetrics.initIfNeeded(activity);
- AutoJs.getInstance().getAppUtils().setCurrentActivity(activity);
- }
-
- });
}
public static String getResString(int id) {
diff --git a/app/src/main/java/com/stardust/scriptdroid/autojs/AutoJs.java b/app/src/main/java/com/stardust/scriptdroid/autojs/AutoJs.java
index 39cc0865..41d85602 100644
--- a/app/src/main/java/com/stardust/scriptdroid/autojs/AutoJs.java
+++ b/app/src/main/java/com/stardust/scriptdroid/autojs/AutoJs.java
@@ -1,9 +1,15 @@
package com.stardust.scriptdroid.autojs;
import android.accessibilityservice.AccessibilityService;
+import android.app.Activity;
import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import com.stardust.app.OnActivityResultDelegate;
+import com.stardust.app.SimpleActivityLifecycleCallbacks;
import com.stardust.autojs.ScriptEngineService;
import com.stardust.autojs.ScriptEngineServiceBuilder;
import com.stardust.autojs.engine.RhinoJavaScriptEngineManager;
@@ -12,6 +18,7 @@ import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.ScriptStopException;
import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
+import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.automator.AccessibilityEventCommandHost;
import com.stardust.automator.simple_action.SimpleActionPerformHost;
import com.stardust.pio.PFile;
@@ -19,8 +26,10 @@ import com.stardust.pio.UncheckedIOException;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
+import com.stardust.scriptdroid.autojs.api.ScreenCaptureRequestActivity;
import com.stardust.scriptdroid.autojs.api.Shell;
import com.stardust.scriptdroid.ui.console.StardustConsole;
+import com.stardust.util.ScreenMetrics;
import com.stardust.util.Supplier;
import com.stardust.util.UiHandler;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
@@ -59,6 +68,23 @@ public class AutoJs implements AccessibilityBridge {
private final AccessibilityInfoProvider mAccessibilityInfoProvider;
private final UiHandler mUiHandler;
private final AppUtils mAppUtils;
+ private final ScreenCaptureRequester mScreenCaptureRequester = new ScreenCaptureRequester.AbstractScreenCaptureRequester() {
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void request() {
+ Activity activity = mAppUtils.getCurrentActivity();
+ if (activity instanceof OnActivityResultDelegate.DelegateHost) {
+ ScreenCaptureRequester requester = new ActivityScreenCaptureRequester(
+ ((OnActivityResultDelegate.DelegateHost) activity).getOnActivityResultDelegateMediator(), activity);
+ requester.setOnActivityResultCallback(mCallback);
+ requester.request();
+ } else {
+ ScreenCaptureRequestActivity.request(mUiHandler.getContext(), mCallback);
+ }
+ }
+
+ };
private AutoJs(final Context context) {
@@ -75,8 +101,8 @@ public class AutoJs implements AccessibilityBridge {
@Override
public com.stardust.autojs.runtime.ScriptRuntime get() {
return new ScriptRuntime.Builder()
- .setAppUtils(mAppUtils)
.setConsole(new StardustConsole(mUiHandler))
+ .setScreenCaptureRequester(mScreenCaptureRequester)
.setAccessibilityBridge(AutoJs.this)
.setUiHandler(mUiHandler)
.setShellSupplier(new Supplier() {
@@ -90,6 +116,28 @@ public class AutoJs implements AccessibilityBridge {
.build();
addAccessibilityServiceDelegates();
mScriptEngineService.registerGlobalScriptExecutionListener(new ScriptExecutionGlobalListener());
+ registerActivityLifecycleCallbacks();
+ }
+
+ private void registerActivityLifecycleCallbacks() {
+ App.getApp().registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() {
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ ScreenMetrics.initIfNeeded(activity);
+ mAppUtils.setCurrentActivity(activity);
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ mAppUtils.setCurrentActivity(null);
+ }
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ mAppUtils.setCurrentActivity(activity);
+ }
+ });
}
private ScriptEngineManager createScriptEngineManager(Context context) {
diff --git a/app/src/main/java/com/stardust/scriptdroid/autojs/api/Dialogs.java b/app/src/main/java/com/stardust/scriptdroid/autojs/api/Dialogs.java
index 725bcd58..c42befe7 100644
--- a/app/src/main/java/com/stardust/scriptdroid/autojs/api/Dialogs.java
+++ b/app/src/main/java/com/stardust/scriptdroid/autojs/api/Dialogs.java
@@ -7,6 +7,7 @@ import android.view.ContextThemeWrapper;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.runtime.ScriptInterface;
+import com.stardust.autojs.runtime.ScriptInterruptedException;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.concurrent.VolatileBox;
import com.stardust.scriptdroid.R;
@@ -35,7 +36,7 @@ public class Dialogs {
.input(null, prefill, true, result)
.title(title)
.show();
- return result.blockedGet();
+ return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -49,7 +50,7 @@ public class Dialogs {
builder.content(content);
}
builder.show();
- lock.blockedGet();
+ lock.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -65,7 +66,7 @@ public class Dialogs {
builder.content(content);
}
builder.show();
- return result.blockedGet();
+ return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
private Context getContext() {
@@ -83,7 +84,7 @@ public class Dialogs {
.title(title)
.items((CharSequence[]) items)
.show();
- return result.blockedGet();
+ return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -95,7 +96,7 @@ public class Dialogs {
.positiveText(R.string.ok)
.items((CharSequence[]) items)
.show();
- return result.blockedGet();
+ return result.blockedGetOrThrow(ScriptInterruptedException.class);
}
@ScriptInterface
@@ -107,7 +108,7 @@ public class Dialogs {
.positiveText(R.string.ok)
.items((CharSequence[]) items)
.show();
- return ArrayUtils.unbox(result.blockedGet());
+ return ArrayUtils.unbox(result.blockedGetOrThrow(ScriptInterruptedException.class));
}
diff --git a/app/src/main/java/com/stardust/scriptdroid/autojs/api/ScreenCaptureRequestActivity.java b/app/src/main/java/com/stardust/scriptdroid/autojs/api/ScreenCaptureRequestActivity.java
new file mode 100644
index 00000000..d70d6865
--- /dev/null
+++ b/app/src/main/java/com/stardust/scriptdroid/autojs/api/ScreenCaptureRequestActivity.java
@@ -0,0 +1,50 @@
+package com.stardust.scriptdroid.autojs.api;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+
+import com.stardust.app.OnActivityResultDelegate;
+import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
+import com.stardust.scriptdroid.ui.BaseActivity;
+
+/**
+ * Created by Stardust on 2017/5/22.
+ */
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class ScreenCaptureRequestActivity extends BaseActivity {
+
+
+ private static ScreenCaptureRequester.Callback sCallback;
+
+ public static void request(Context context, ScreenCaptureRequester.Callback callback) {
+ if (sCallback != null) {
+ return;
+ }
+ sCallback = callback;
+ context.startActivity(new Intent(context, ScreenCaptureRequestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ private OnActivityResultDelegate.Mediator mOnActivityResultDelegateMediator = new OnActivityResultDelegate.Mediator();
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ScreenCaptureRequester requester = new ScreenCaptureRequester.ActivityScreenCaptureRequester(mOnActivityResultDelegateMediator, this);
+ requester.setOnActivityResultCallback(sCallback);
+ requester.request();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mOnActivityResultDelegateMediator.onActivityResult(requestCode, resultCode, data);
+ finish();
+ sCallback = null;
+ }
+
+}
diff --git a/app/src/main/java/com/stardust/scriptdroid/service/MediaProjectionScreenCaptureService.java b/app/src/main/java/com/stardust/scriptdroid/service/MediaProjectionScreenCaptureService.java
new file mode 100644
index 00000000..97963d24
--- /dev/null
+++ b/app/src/main/java/com/stardust/scriptdroid/service/MediaProjectionScreenCaptureService.java
@@ -0,0 +1,83 @@
+package com.stardust.scriptdroid.service;
+
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.annotation.RequiresApi;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/**
+ * Created by Stardust on 2017/1/19.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+public class MediaProjectionScreenCaptureService extends Service {
+ private ImageReader mImageReader;
+ private int mScreenWidth;
+ private int mScreenHeight;
+ private int mScreenDensity;
+ private MediaProjection mMediaProjection;
+ private VirtualDisplay mVirtualDisplay;
+ public static Intent mResultData;
+
+
+ @Override
+ public void onCreate() {
+ if (mResultData == null) {
+
+ }
+ WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(metrics);
+ mScreenDensity = metrics.densityDpi;
+ mScreenWidth = metrics.widthPixels;
+ mScreenHeight = metrics.heightPixels;
+
+ createImageReader();
+ startVirtualDisplay();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void createImageReader() {
+ mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 1);
+ }
+
+ public void startVirtualDisplay() {
+ mMediaProjection = getMediaProjectionManager().getMediaProjection(Activity.RESULT_OK, mResultData);
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
+ mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ mImageReader.getSurface(), null, null);
+ }
+
+ private MediaProjectionManager getMediaProjectionManager() {
+ return (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ mVirtualDisplay = null;
+ }
+ if (mMediaProjection != null) {
+ mMediaProjection.stop();
+ mMediaProjection = null;
+ }
+ super.onDestroy();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stardust/scriptdroid/statics/SQLiteStaticsStorage.java b/app/src/main/java/com/stardust/scriptdroid/statics/SQLiteStaticsStorage.java
index 414d56d6..f8b37cf8 100644
--- a/app/src/main/java/com/stardust/scriptdroid/statics/SQLiteStaticsStorage.java
+++ b/app/src/main/java/com/stardust/scriptdroid/statics/SQLiteStaticsStorage.java
@@ -117,7 +117,6 @@ public class SQLiteStaticsStorage implements ScriptStaticsStorage {
private static class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
-
SQLiteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java
index dc1ccba6..2462ac6b 100644
--- a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java
+++ b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java
@@ -25,7 +25,7 @@ public class ImageSelector implements OnActivityResultDelegate {
private Activity mActivity;
private ImageSelectorCallback mCallback;
- public ImageSelector(Activity activity, Mediator mediator, ImageSelectorCallback callback) {
+ public ImageSelector(Activity activity, OnActivityResultDelegate.Mediator mediator, ImageSelectorCallback callback) {
mediator.addDelegate(REQUEST_CODE, this);
mActivity = activity;
mCallback = callback;
diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java
index 4124916b..bde63587 100644
--- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java
+++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java
@@ -21,10 +21,10 @@ import com.jecelyin.editor.v2.core.widget.TextView;
import com.jecelyin.editor.v2.ui.EditorDelegate;
import com.jecelyin.editor.v2.view.EditorView;
import com.jecelyin.editor.v2.view.menu.MenuDef;
+import com.stardust.app.OnActivityResultDelegate;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.FileScriptSource;
import com.stardust.autojs.script.JsBeautifier;
-import com.stardust.autojs.script.StringScriptSource;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.scriptdroid.script.ScriptFile;
@@ -49,7 +49,7 @@ import java.io.File;
* Created by Stardust on 2017/1/29.
*/
-public class EditActivity extends Editor920Activity {
+public class EditActivity extends Editor920Activity implements OnActivityResultDelegate.DelegateHost {
public static class InputMethodEnhanceBarBridge implements InputMethodEnhanceBar.EditTextBridge {
@@ -76,6 +76,8 @@ public class EditActivity extends Editor920Activity {
public TextView getEditText() {
return mTextView;
}
+
+
}
@@ -102,6 +104,7 @@ public class EditActivity extends Editor920Activity {
private EditorDelegate mEditorDelegate;
private SparseArray mMenuMap;
private boolean mReadOnly = false;
+ private OnActivityResultDelegate.Mediator mActivityResultMediator = new OnActivityResultDelegate.Mediator();
private BroadcastReceiver mOnRunFinishedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -359,6 +362,17 @@ public class EditActivity extends Editor920Activity {
.show();
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mActivityResultMediator.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
+ return mActivityResultMediator;
+ }
+
@Override
public void doCommand(Command command) {
mEditorDelegate.doCommand(command);
diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/ViewSampleActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/ViewSampleActivity.java
index c456ce6e..b77f1897 100644
--- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/ViewSampleActivity.java
+++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/ViewSampleActivity.java
@@ -15,6 +15,7 @@ import com.jecelyin.editor.v2.common.Command;
import com.jecelyin.editor.v2.ui.EditorDelegate;
import com.jecelyin.editor.v2.view.EditorView;
import com.jecelyin.editor.v2.view.menu.MenuDef;
+import com.stardust.app.OnActivityResultDelegate;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.StringScriptSource;
import com.stardust.scriptdroid.R;
@@ -42,7 +43,7 @@ import static com.stardust.scriptdroid.script.Scripts.EXTRA_EXCEPTION_MESSAGE;
* Created by Stardust on 2017/4/29.
*/
-public class ViewSampleActivity extends Editor920Activity {
+public class ViewSampleActivity extends Editor920Activity implements OnActivityResultDelegate.DelegateHost {
public static void view(Context context, Sample sample) {
@@ -56,6 +57,7 @@ public class ViewSampleActivity extends Editor920Activity {
private ScriptExecution mScriptExecution;
private EditorDelegate mEditorDelegate;
private SparseArray mMenuMap;
+ private OnActivityResultDelegate.Mediator mMediator = new OnActivityResultDelegate.Mediator();
private BroadcastReceiver mOnRunFinishedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -185,4 +187,13 @@ public class ViewSampleActivity extends Editor920Activity {
}
}
+ @Override
+ public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
+ return mMediator;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mMediator.onActivityResult(requestCode, resultCode, data);
+ }
}
diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java
index e3b20dec..de8e5799 100644
--- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java
+++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java
@@ -62,10 +62,11 @@ import org.greenrobot.eventbus.Subscribe;
import java.io.IOException;
-public class MainActivity extends BaseActivity {
+public class MainActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost {
public static final String MESSAGE_CLEAR_BACKGROUND_SETTINGS = "MESSAGE_CLEAR_BACKGROUND_SETTINGS";
+ private static final String LOG_TAG = "MainActivity";
private static final String EXTRA_ACTION = "EXTRA_ACTION";
private static final String ACTION_ON_ACTION_RECORD_STOPPED = "ACTION_ON_ACTION_RECORD_STOPPED";
@@ -99,6 +100,11 @@ public class MainActivity extends BaseActivity {
showAnnunciationIfNeeded();
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
private void showAnnunciationIfNeeded() {
if (!Pref.shouldShowAnnunciation()) {
return;
@@ -406,4 +412,8 @@ public class MainActivity extends BaseActivity {
context.startActivity(intent);
}
+ @Override
+ public OnActivityResultDelegate.Mediator getOnActivityResultDelegateMediator() {
+ return mActivityResultMediator;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/xml/accessibility_service_config.xml b/app/src/main/res/xml/accessibility_service_config.xml
index a26caa28..ea60b9a1 100644
--- a/app/src/main/res/xml/accessibility_service_config.xml
+++ b/app/src/main/res/xml/accessibility_service_config.xml
@@ -5,7 +5,6 @@
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
android:canPerformGestures="true"
android:canRequestEnhancedWebAccessibility="true"
- android:canRequestTouchExplorationMode="true"
android:canRetrieveWindowContent="true"
android:description="@string/text_accessibility_service_description"
android:notificationTimeout="100"/>
\ No newline at end of file
diff --git a/autojs/src/androidTest/java/com/stardust/autojs/ExampleInstrumentedTest.java b/autojs/src/androidTest/java/com/stardust/autojs/ExampleInstrumentedTest.java
index ca86f2f5..0a71cea4 100644
--- a/autojs/src/androidTest/java/com/stardust/autojs/ExampleInstrumentedTest.java
+++ b/autojs/src/androidTest/java/com/stardust/autojs/ExampleInstrumentedTest.java
@@ -1,18 +1,13 @@
package com.stardust.autojs;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiDevice;
-import android.view.InputEvent;
-import android.test.ActivityInstrumentationTestCase2;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
/**
* Instrumentation test, which will execute on an Android device.
diff --git a/autojs/src/main/assets/javascript_engine_init.js b/autojs/src/main/assets/javascript_engine_init.js
index 4b49e21a..dd1886b9 100644
--- a/autojs/src/main/assets/javascript_engine_init.js
+++ b/autojs/src/main/assets/javascript_engine_init.js
@@ -27,7 +27,7 @@ require("__general__")(__runtime__, this);
(function(scope){
- var modules = ['app', 'automator', 'console', 'io', 'selector', 'shell', 'web', 'ui'];
+ var modules = ['app', 'automator', 'console', 'io', 'selector', 'shell', 'web', 'ui', "images"];
var len = modules.length;
for(var i = 0; i < len; i++) {
var m = modules[i];
diff --git a/autojs/src/main/assets/modules/__automator__.js b/autojs/src/main/assets/modules/__automator__.js
index eca86d99..3163e61c 100644
--- a/autojs/src/main/assets/modules/__automator__.js
+++ b/autojs/src/main/assets/modules/__automator__.js
@@ -13,7 +13,7 @@ module.exports = function(__runtime__, scope){
}
automator.click = function(){
- if(arguments.length >= 2 && typeof(arguments[0]) == 'number' && typeof(arguments[1]) == 'number'){
+ if(arguments.length == 2 && typeof(arguments[0]) == 'number' && typeof(arguments[1]) == 'number'){
return __runtime__.automator.click(arguments[0], arguments[1]);
}
return performAction(function(target){
@@ -34,7 +34,7 @@ module.exports = function(__runtime__, scope){
automator.gesture = __runtime__.automator.gesture.bind(__runtime__.automator, 0);
automator.gestureAsync = __runtime__.automator.gestureAsync.bind(__runtime__.automator, 0);
automator.swipe = __runtime__.automator.swipe.bind(__runtime__.automator);
- automator.gestures = function(){
+ automator.gestures = function(){
return __runtime__.automator.gestures(toStrokes(arguments));
}
diff --git a/autojs/src/main/assets/modules/__general__.js b/autojs/src/main/assets/modules/__general__.js
index 8e46b15f..f06e0ba1 100644
--- a/autojs/src/main/assets/modules/__general__.js
+++ b/autojs/src/main/assets/modules/__general__.js
@@ -4,6 +4,11 @@ module.exports = function(__runtime__, scope){
__runtime__.toast(text);
}
+ scope.toastLog = function(text){
+ __runtime__.toast(text);
+ scope.log(text);
+ }
+
scope.sleep = function(millis){
__runtime__.sleep(millis);
}
diff --git a/autojs/src/main/assets/modules/__images__.js b/autojs/src/main/assets/modules/__images__.js
new file mode 100644
index 00000000..abffa120
--- /dev/null
+++ b/autojs/src/main/assets/modules/__images__.js
@@ -0,0 +1,64 @@
+
+module.exports = function(__runtime__, scope){
+ var images = {};
+
+ var colorFinder = __runtime__.images.colorFinder;
+
+ images.requestScreenCapture = __runtime__.images.requestScreenCapture.bind(__runtime__.images);
+
+ images.captureScreen = __runtime__.images.captureScreen.bind(__runtime__.images);
+
+ images.saveImage = __runtime__.images.saveImage.bind(__runtime__.images);
+
+ images.findColor = function(img, color, options){
+ options = options || {};
+ var region = options.region || [];
+ x = region[0] || 0;
+ y = region[1] || 0;
+ width = region[2] || (img.getWidth() - x);
+ height = region[3] || (img.getHeight() - y);
+ threads = options.threads || 4;
+ if(options.threshold !== 0){
+ threshold = options.threshold || 8;
+ }
+ algorithm = options.algorithm || "rgb";
+ var rect = new android.graphics.Rect(x, y, width + x, height + y);
+ var colorDetector = getColorDetector(color, algorithm, threshold);
+ return colorFinder.findColorConcurrently(img, colorDetector, rect, threads);
+ }
+
+ images.findColorInRegion = function(img, color, x, y, width, height, threads, algorithm, threshold){
+ return findColor(img, color, {
+ region: [x, y, width, height],
+ algorithm: algorithm,
+ threshold: threshold,
+ threads: threads
+ });
+ }
+
+ images.findColorEquals = function(img, color, x, y, width, height, threads){
+ return findColor(img, color, {
+ region: [x, y, width, height],
+ algorithm: "equal",
+ threads: threads
+ });
+ }
+
+ function getColorDetector(color, algorithm, threshold){
+ switch(algorithm){
+ case "rgb":
+ return new com.stardust.autojs.runtime.api.image.ColorDetector.RGBDistanceDetector(color, threshold);
+ case "equal":
+ return new com.stardust.autojs.runtime.api.image.ColorDetector.EqualityDetector(color);
+ case "rgb+":
+ return new com.stardust.autojs.runtime.api.image.ColorDetector.WeightedRGBDistanceDetector(color, threshold);
+ case "hs":
+ return new com.stardust.autojs.runtime.api.image.ColorDetector.HSDistanceDetector(color, threshold);
+ }
+ throw new Error("Unknown algorithm: " + algorithm);
+ }
+
+ scope.__asGlobal__(images, ['requestScreenCapture', 'captureScreen', 'findColor', 'findColorInRegion', 'findColorEquals']);
+
+ return images;
+}
\ No newline at end of file
diff --git a/autojs/src/main/assets/modules/__selector__.js b/autojs/src/main/assets/modules/__selector__.js
index e66bc685..de5af861 100644
--- a/autojs/src/main/assets/modules/__selector__.js
+++ b/autojs/src/main/assets/modules/__selector__.js
@@ -9,20 +9,7 @@ module.exports = function(__runtime__, scope){
scope[method] = (function(method) {
return function(){
var s = selector();
- //这里不知道怎么写。尴尬。只能写成这样。
- if(arguments.length == 0){
- return s[method]();
- }else if(arguments.length == 1){
- return s[method](arguments[0]);
- }else if(arguments.length == 2){
- return s[method](arguments[0], arguments[1]);
- }else if(arguments.length == 3){
- return s[method](arguments[0], arguments[1], arguments[2]);
- }else if(arguments.length == 4){
- return s[method](arguments[0], arguments[1], arguments[2], arguments[3]);
- }else{
- return s[method].call(s, Array.prototype.slice.call(arguments));
- }
+ return s[method].apply(s, Array.prototype.slice.call(arguments));
};
})(method);
}
@@ -33,4 +20,3 @@ module.exports = function(__runtime__, scope){
};
}
-
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/AbstractScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/AbstractScriptRuntime.java
index a91fdbc9..278ca5f8 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/AbstractScriptRuntime.java
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/AbstractScriptRuntime.java
@@ -1,10 +1,15 @@
package com.stardust.autojs.runtime;
+import android.content.Context;
+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.Console;
import com.stardust.autojs.runtime.api.UiSelector;
+import com.stardust.autojs.runtime.api.image.Images;
+import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.autojs.runtime.api.ui.UI;
import com.stardust.autojs.runtime.simpleaction.SimpleActionAutomator;
import com.stardust.util.ScreenMetrics;
@@ -31,15 +36,16 @@ public abstract class AbstractScriptRuntime {
@ScriptVariable
public UI ui;
- public AbstractScriptRuntime(AppUtils app, Console console, AccessibilityBridge bridge, UI ui) {
- this.app = app;
+ @ScriptVariable
+ public Images images;
+
+ public AbstractScriptRuntime(Context context, Console console, AccessibilityBridge bridge, ScreenCaptureRequester screenCaptureRequester) {
+ this.app = new AppUtils(context);
this.console = console;
this.automator = new SimpleActionAutomator(bridge, this);
this.info = bridge.getInfoProvider();
- this.ui = ui;
- }
-
- public AbstractScriptRuntime() {
+ this.ui = new UI(context);
+ images = new Images(context, this, screenCaptureRequester);
}
@ScriptInterface
@@ -80,5 +86,8 @@ public abstract class AbstractScriptRuntime {
public abstract void ensureAccessibilityServiceEnabled();
- public abstract void onStop();
+ @CallSuper
+ public void onStop() {
+ images.releaseScreenCapturer();
+ }
}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java
index 0008b016..e59d7761 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/ScriptRuntime.java
@@ -10,6 +10,7 @@ import com.stardust.autojs.runtime.api.AbstractShell;
import com.stardust.autojs.runtime.api.AppUtils;
import com.stardust.autojs.runtime.api.Console;
import com.stardust.autojs.runtime.api.UiSelector;
+import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
import com.stardust.concurrent.VolatileBox;
import com.stardust.autojs.runtime.api.ui.UI;
import com.stardust.pio.UncheckedIOException;
@@ -35,23 +36,16 @@ public class ScriptRuntime extends AbstractScriptRuntime {
private static final String TAG = "ScriptRuntime";
public static class Builder {
- private AppUtils mAppUtils;
private UiHandler mUiHandler;
private Console mConsole;
private AccessibilityBridge mAccessibilityBridge;
private Supplier mShellSupplier;
- public UI mUi;
-
+ private ScreenCaptureRequester mScreenCaptureRequester;
public Builder() {
}
- public Builder setAppUtils(AppUtils appUtils) {
- mAppUtils = appUtils;
- return this;
- }
-
public Builder setUiHandler(UiHandler uiHandler) {
mUiHandler = uiHandler;
return this;
@@ -72,8 +66,8 @@ public class ScriptRuntime extends AbstractScriptRuntime {
return this;
}
- public Builder setUI(UI ui) {
- mUi = ui;
+ public Builder setScreenCaptureRequester(ScreenCaptureRequester requester) {
+ mScreenCaptureRequester = requester;
return this;
}
@@ -92,13 +86,11 @@ public class ScriptRuntime extends AbstractScriptRuntime {
protected ScriptRuntime(Builder builder) {
- super(builder.mAppUtils, builder.mConsole, builder.mAccessibilityBridge, builder.mUi);
+ super(builder.mUiHandler.getContext(), builder.mConsole, builder.mAccessibilityBridge, builder.mScreenCaptureRequester);
mAccessibilityBridge = builder.mAccessibilityBridge;
mUiHandler = builder.mUiHandler;
mShellSupplier = builder.mShellSupplier;
- if (ui == null) {
- ui = new UI(mUiHandler.getContext());
- }
+ ui = new UI(mUiHandler.getContext());
automator.setScreenMetrics(mScreenMetrics);
}
@@ -142,7 +134,7 @@ public class ScriptRuntime extends AbstractScriptRuntime {
clip.setAndNotify(ClipboardUtil.getClipOrEmpty(mUiHandler.getContext()).toString());
}
});
- return clip.blockedGet();
+ return clip.blockedGetOrThrow(ScriptInterruptedException.class);
}
@Override
@@ -193,7 +185,6 @@ public class ScriptRuntime extends AbstractScriptRuntime {
throw new ScriptInterruptedException();
}
-
@Override
public void setScreenMetrics(int width, int height) {
mScreenMetrics.setScreenMetrics(width, height);
@@ -210,6 +201,7 @@ public class ScriptRuntime extends AbstractScriptRuntime {
@Override
public void onStop() {
+ super.onStop();
if (mRootShell != null) {
mRootShell.exit();
mRootShell = null;
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java
index 9419b7e1..ba902d37 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/AbstractShell.java
@@ -69,6 +69,9 @@ public abstract class AbstractShell {
}
public void SetScreenMetrics(int width, int height) {
+ if (mScreenMetrics == null) {
+ mScreenMetrics = new ScreenMetrics();
+ }
mScreenMetrics.setScreenMetrics(width, height);
}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/AppUtils.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/AppUtils.java
index c45193b1..88ab7520 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/api/AppUtils.java
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/AppUtils.java
@@ -31,7 +31,8 @@ public class AppUtils {
public boolean launchPackage(String packageName) {
try {
PackageManager packageManager = mContext.getPackageManager();
- mContext.startActivity(packageManager.getLaunchIntentForPackage(packageName));
+ mContext.startActivity(packageManager.getLaunchIntentForPackage(packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return true;
} catch (Exception e) {
return false;
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java
new file mode 100644
index 00000000..539511b1
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorDetector.java
@@ -0,0 +1,203 @@
+package com.stardust.autojs.runtime.api.image;
+
+/**
+ * Created by Stardust on 2017/5/20.
+ */
+
+public interface ColorDetector {
+
+ boolean detectsColor(int color);
+
+ abstract class AbstractColorDetector implements ColorDetector {
+
+ protected final int mColor;
+
+ public AbstractColorDetector(int color) {
+ mColor = color;
+ }
+ }
+
+ class EqualityDetector implements ColorDetector {
+
+ private final int mColor;
+
+ public EqualityDetector(int color) {
+ mColor = color & 0xffffff;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ return mColor == (color & 0xffffff);
+ }
+ }
+
+ class DifferenceDetector extends AbstractColorDetector {
+
+ private final int mThreshold;
+
+ public DifferenceDetector(int color, int threshold) {
+ super(color);
+ mThreshold = threshold;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ return Math.abs(mColor - color) <= mThreshold;
+ }
+ }
+
+ class RDistanceDetector extends AbstractColorDetector {
+
+ private final int mR;
+ private final int mThreshold;
+
+ public RDistanceDetector(int color, int threshold) {
+ super(color);
+ mThreshold = threshold;
+ mR = (color & 0xff0000) >> 16;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ int R = (color & 0xff0000) >> 16;
+ return Math.abs(mR - R) <= mThreshold;
+ }
+ }
+
+ class RGBDistanceDetector extends AbstractColorDetector {
+
+ private final int mThreshold;
+ private final int mR, mG, mB;
+
+ public RGBDistanceDetector(int color, int threshold) {
+ super(color);
+ mR = (color & 0xff0000) >> 16;
+ mG = (color & 0x00ff00) >> 8;
+ mB = color & 0xff;
+ mThreshold = threshold * threshold;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ int dR = ((color & 0xff0000) >> 16) - mR;
+ int dG = ((color & 0x00ff00) >> 8) - mG;
+ int dB = (color & 0xff) - mB;
+ int d = dR * dR + dG * dG + dB * dB;
+ return d <= mThreshold;
+ }
+ }
+
+ class WeightedRGBDistanceDetector extends AbstractColorDetector {
+
+ private final int mThreshold;
+ private final int mR, mG, mB;
+
+ public WeightedRGBDistanceDetector(int color, int threshold) {
+ super(color);
+ mR = (color & 0xff0000) >> 16;
+ mG = (color & 0x00ff00) >> 8;
+ mB = color & 0xff;
+ mThreshold = threshold * threshold;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ int R = (color & 0xff0000) >> 16;
+ int dR = R - mR;
+ int dG = ((color & 0x00ff00) >> 8) - mG;
+ int dB = (color & 0xff) - mB;
+ double meanR = (mR + R) / 2;
+ double weightR = 2 + meanR / 256;
+ double weightG = 4.0;
+ double weightB = 2 + (255 - meanR) / 256;
+ return weightR * dR * dR + weightG * dG * dG + weightB * dB * dB <= mThreshold;
+ }
+ }
+
+ class HDistanceDetector extends AbstractColorDetector {
+
+ private final int mH;
+ private final int mThreshold;
+
+ public HDistanceDetector(int color, int threshold) {
+ super(color);
+ mH = getH(color);
+ mThreshold = threshold;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ return Math.abs(mH - getH(color)) <= mThreshold;
+ }
+
+ private static int getH(int color) {
+ int R = (color & 0xff0000) >> 16;
+ int G = (color & 0x00ff00) >> 8;
+ int B = color & 0xff;
+ int max, min, H;
+ if (R > G) {
+ min = Math.min(G, B);
+ max = Math.max(R, B);
+ } else {
+ min = Math.min(R, B);
+ max = Math.max(G, B);
+ }
+ if (R == max) {
+ H = (G - B) / (max - min) * 60;
+ } else if (G == max) {
+ H = 120 + (B - R) / (max - min) * 60;
+ } else {
+ H = 240 + (R - G) / (max - min) * 60;
+ }
+ if (H < 0) H = H + 360;
+ return H;
+ }
+ }
+
+ class HSDistanceDetector extends AbstractColorDetector {
+
+ private final int mH, mS;
+ private final int mThreshold;
+
+ public HSDistanceDetector(int color, int threshold) {
+ super(color);
+ long HS = getHS(color);
+ mH = (int) (HS & 0xffffffffL);
+ mS = (int) ((HS >> 32) & 0xffffffffL);
+ mThreshold = threshold * threshold;
+ }
+
+ @Override
+ public boolean detectsColor(int color) {
+ long hs = getHS(color);
+ int dH = (int) (hs & 0xffffffffL) - mH;
+ int dS = (int) ((hs >> 32) & 0xffffffffL) - mS;
+ return dH * dH + dS * dS <= mThreshold;
+ }
+
+ private static long getHS(int color) {
+ int R = (color & 0xff0000) >> 16;
+ int G = (color & 0x00ff00) >> 8;
+ int B = color & 0xff;
+ int max, min, H;
+ if (R > G) {
+ min = Math.min(G, B);
+ max = Math.max(R, B);
+ } else {
+ min = Math.min(R, B);
+ max = Math.max(G, B);
+ }
+ if (R == max) {
+ H = (G - B) / (max - min) * 60;
+ } else if (G == max) {
+ H = 120 + (B - R) / (max - min) * 60;
+ } else {
+ H = 240 + (R - G) / (max - min) * 60;
+ }
+ if (H < 0) H = H + 360;
+ int S = (max - min) / max;
+ return H & ((long) S << 32);
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java
new file mode 100644
index 00000000..a285ac8c
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorFinder.java
@@ -0,0 +1,249 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.media.Image;
+
+import com.stardust.autojs.runtime.ScriptInterruptedException;
+import com.stardust.concurrent.VolatileBox;
+import com.stardust.util.ScreenMetrics;
+
+import java.util.List;
+import java.util.Vector;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Created by Stardust on 2017/5/18.
+ */
+
+public class ColorFinder {
+
+ private static ThreadPoolExecutor sThreadPoolExecutor = new ThreadPoolExecutor(4, 16, 5, TimeUnit.MINUTES, new SynchronousQueue());
+
+ static {
+ sThreadPoolExecutor.allowCoreThreadTimeOut(true);
+ }
+
+ private ThreadPoolExecutor mThreadPoolExecutor;
+
+ public ColorFinder(ThreadPoolExecutor threadPoolExecutor) {
+ mThreadPoolExecutor = threadPoolExecutor;
+ }
+
+ public ColorFinder() {
+ this(sThreadPoolExecutor);
+ }
+
+ public void prestartThreads() {
+ mThreadPoolExecutor.prestartAllCoreThreads();
+ }
+
+ public Point[] findAllColors(Image image, ColorDetector detector, Rect rect, int threadCount) {
+ List result = new Vector<>();
+ ColorIterator[] iterators = divide(image, rect, threadCount);
+ for (int i = 1; i < threadCount; i++) {
+ mThreadPoolExecutor.execute(new FindAllColorsRunnable(result, iterators[i], detector));
+ }
+ new FindAllColorsRunnable(result, iterators[0], detector).run();
+ Point[] points = new Point[result.size()];
+ for (int i = 0; i < points.length; i++) {
+ points[i] = scalePoint(result.get(i), image.getWidth(), image.getHeight());
+ }
+ return points;
+ }
+
+ public Point findColorConcurrently(Image image, ColorDetector detector, Rect rect, int threadCount) {
+ if (threadCount <= 1) {
+ return findColor(image, detector, rect);
+ }
+ VolatileBox result = new VolatileBox<>();
+ ColorIterator[] iterators = divide(image, rect, threadCount);
+ for (int i = 1; i < threadCount; i++) {
+ mThreadPoolExecutor.execute(new FindColorRunnable(result, iterators[i], detector));
+ }
+ new FindColorRunnable(result, iterators[0], detector).run();
+ return scalePoint(result.get(), image.getWidth(), image.getHeight());
+ }
+
+
+ private Point scalePoint(Point point, int width, int height) {
+ if (point == null)
+ return null;
+ if (ScreenMetrics.getDeviceScreenHeight() == height && ScreenMetrics.getDeviceScreenWidth() == height) {
+ return point;
+ }
+ point.set(ScreenMetrics.scaleX(point.x, width), ScreenMetrics.scaleY(point.y, height));
+ return point;
+ }
+
+ protected ColorIterator[] divide(Image image, Rect rect, int count) {
+ Rect[] subAreas = divideIntoSubAreas(rect, count);
+ int centerY = rect.centerY();
+ ColorIterator[] iterators = new ColorIterator[count];
+ for (int i = 1; i < subAreas.length; i++) {
+ Rect subArea = subAreas[i];
+ if (subArea.top > centerY) {
+ iterators[i] = new ColorIterator.SequentialIterator(image, subArea, true);
+ } else {
+ iterators[i] = new ColorIterator.SequentialIterator(image, subArea, true);
+ }
+ }
+ iterators[0] = new ColorIterator.SequentialIterator(image, subAreas[0], false);
+ return iterators;
+ }
+
+ protected Rect[] divideIntoSubAreas(Rect rect, int count) {
+ int row, column;
+ switch (count) {
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ case 14:
+ row = count / 2;
+ column = 2;
+ break;
+ case 9:
+ case 12:
+ case 15:
+ row = count / 3;
+ column = 3;
+ break;
+ case 16:
+ row = 4;
+ column = 4;
+ break;
+ default:
+ row = count;
+ column = 1;
+ }
+ Rect[] cells = new Rect[count];
+ int cellWidth = rect.width() / column;
+ int cellHeight = rect.height() / row;
+ for (int i = 0; i < row; i++) {
+ for (int j = 0; j < column; j++) {
+ int x = rect.left + j * cellWidth;
+ int y = rect.top + i * cellHeight;
+ cells[i * column + j] = new Rect(x, y, x + cellWidth, y + cellHeight);
+ }
+ }
+ return cells;
+ }
+
+ public Point findColorConcurrently(Image image, int color, Rect rect, int threadCount, int threshold) {
+ return findColorConcurrently(image, defaultColorDetector(color, threshold), rect, threadCount);
+ }
+
+ public Point findColorConcurrently(Image image, int color, Rect rect, int threadCount) {
+ return findColorConcurrently(image, color, rect, threadCount, 8);
+ }
+
+ public Point findColorConcurrently(Image image, int color, int threadCount) {
+ Rect rect = new Rect(0, 0, image.getWidth(), image.getHeight());
+ return findColorConcurrently(image, defaultColorDetector(color), rect, threadCount);
+ }
+
+ public Point findColorEqualsConcurrently(Image image, int color, Rect rect, int threadCount) {
+ return findColorConcurrently(image, new ColorDetector.EqualityDetector(color), rect, threadCount);
+ }
+
+ public static Point findColor(ColorIterator iterator, ColorDetector detector) {
+ Thread thread = Thread.currentThread();
+ while (!thread.isInterrupted() && iterator.hasNext()) {
+ int c = iterator.nextColor();
+ if (detector.detectsColor(c)) {
+ return new Point(iterator.getX(), iterator.getY());
+ }
+ }
+ if (thread.isInterrupted()) {
+ throw new ScriptInterruptedException();
+ }
+ return null;
+ }
+
+ public ColorDetector defaultColorDetector(int color) {
+ return new ColorDetector.RGBDistanceDetector(color, 16);
+ }
+
+ public ColorDetector defaultColorDetector(int color, int threshold) {
+ return new ColorDetector.RGBDistanceDetector(color, threshold);
+ }
+
+ public ColorIterator defaultColorIterator(Image image, Rect rect) {
+ return new ColorIterator.SequentialIterator(image, rect);
+ }
+
+ public Point findColor(Image image, ColorDetector detector, Rect rect) {
+ return scalePoint(findColor(defaultColorIterator(image, rect), detector), image.getWidth(), image.getHeight());
+ }
+
+ public Point findColor(Image image, int color, Rect rect) {
+ return scalePoint(findColor(defaultColorIterator(image, rect), defaultColorDetector(color)), image.getWidth(), image.getHeight());
+ }
+
+ public Point findColor(Image image, int color) {
+ return findColor(image, color, new Rect(0, 0, image.getWidth(), image.getHeight()));
+ }
+
+ public Point findColorEquals(Image image, int color, Rect rect) {
+ return scalePoint(findColor(defaultColorIterator(image, rect), new ColorDetector.EqualityDetector(color)), image.getWidth(), image.getHeight());
+ }
+
+ private static class FindColorRunnable implements Runnable {
+
+ private final VolatileBox mResultBox;
+ private final ColorIterator mColorIterator;
+ private final ColorDetector mColorDetector;
+
+ private FindColorRunnable(VolatileBox resultBox, ColorIterator colorIterator, ColorDetector colorDetector) {
+ mResultBox = resultBox;
+ mColorIterator = colorIterator;
+ mColorDetector = colorDetector;
+ }
+
+ @Override
+ public void run() {
+ Thread thread = Thread.currentThread();
+ while (mResultBox.isNull() && mColorIterator.hasNext() && !thread.isInterrupted()) {
+ int c = mColorIterator.nextColor();
+ if (mColorDetector.detectsColor(c)) {
+ mResultBox.set(new Point(mColorIterator.getX(), mColorIterator.getY()));
+ break;
+ }
+ }
+ if (thread.isInterrupted()) {
+ throw new ScriptInterruptedException();
+ }
+ }
+ }
+
+ private static class FindAllColorsRunnable implements Runnable {
+
+ private final List mResult;
+ private final ColorIterator mColorIterator;
+ private final ColorDetector mColorDetector;
+
+ private FindAllColorsRunnable(List result, ColorIterator colorIterator, ColorDetector colorDetector) {
+ mResult = result;
+ mColorIterator = colorIterator;
+ mColorDetector = colorDetector;
+ }
+
+ @Override
+ public void run() {
+ Thread thread = Thread.currentThread();
+ while (mColorIterator.hasNext() && !thread.isInterrupted()) {
+ int c = mColorIterator.nextColor();
+ if (mColorDetector.detectsColor(c)) {
+ mResult.add(new Point(mColorIterator.getX(), mColorIterator.getY()));
+ }
+ }
+ if (thread.isInterrupted()) {
+ throw new ScriptInterruptedException();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorIterator.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorIterator.java
new file mode 100644
index 00000000..a61c4d9e
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ColorIterator.java
@@ -0,0 +1,186 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.graphics.Rect;
+import android.media.Image;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Created by Stardust on 2017/5/20.
+ */
+
+public interface ColorIterator {
+
+ boolean hasNext();
+
+ int nextColor();
+
+ int getX();
+
+ int getY();
+
+ abstract class ImageColorIterator implements ColorIterator {
+
+ protected final ByteBuffer mByteBuffer;
+ protected final Rect mIterateArea;
+
+ public ImageColorIterator(Image image, Rect area, boolean duplicateBuffer) {
+ Image.Plane plane = image.getPlanes()[0];
+ if (duplicateBuffer) {
+ mByteBuffer = plane.getBuffer().duplicate();
+ } else {
+ mByteBuffer = plane.getBuffer();
+ }
+ mIterateArea = area;
+ }
+
+ public ImageColorIterator(Image image, Rect area) {
+ this(image, area, false);
+ }
+
+
+ protected void skip(int i) {
+ mByteBuffer.position(mByteBuffer.position() + i);
+ }
+ }
+
+ class SequentialIterator extends ImageColorIterator {
+
+ private static final String LOG_TAG = "SequentialIterator";
+
+ private final int mRowStride;
+ private final int mSkipPerRow;
+ private final int mWidth;
+ private final int mHeight;
+ private int mX = -1;
+ private int mY = 0;
+
+ public SequentialIterator(Image image, Rect area, boolean duplicateBuffer) {
+ super(image, area, duplicateBuffer);
+ Image.Plane plane = image.getPlanes()[0];
+ int pixelStride = plane.getPixelStride();
+ mRowStride = plane.getRowStride();
+ mWidth = area.width();
+ mHeight = area.height();
+ int rowPadding = mRowStride - pixelStride * image.getWidth();
+ mSkipPerRow = rowPadding + (image.getWidth() - mWidth) * pixelStride;
+ int offset = mIterateArea.top * mRowStride + mIterateArea.left * pixelStride;
+ mByteBuffer.position(offset);
+ }
+
+ public SequentialIterator(Image image, Rect area) {
+ this(image, area, false);
+ }
+
+ public SequentialIterator(Image image) {
+ this(image, new Rect(0, 0, image.getWidth(), image.getHeight()));
+ }
+
+
+ @Override
+ public boolean hasNext() {
+ return mX < mWidth - 1 || mY < mHeight - 1;
+ }
+
+ @Override
+ public int getX() {
+ return mIterateArea.left + mX;
+ }
+
+ @Override
+ public int getY() {
+ return mIterateArea.top + mY;
+ }
+
+ @Override
+ public int nextColor() {
+ if (mX == mWidth - 1) {
+ skip(mSkipPerRow);
+ mX = 0;
+ mY++;
+ } else {
+ mX++;
+ }
+ int c = (mByteBuffer.get() & 0xff) << 16;
+ c |= (mByteBuffer.get() & 0xff) << 8;
+ c |= (mByteBuffer.get() & 0xff);
+ c |= (mByteBuffer.get() & 0xff) << 24;
+ return c;
+ }
+ }
+
+
+ /**
+ * 中心螺旋。未完成。
+ */
+ class CentralSpiralIterator extends ImageColorIterator {
+
+ private static final int DIRECTION_RIGHT = 0;
+ private static final int DIRECTION_TOP = 1;
+ private static final int DIRECTION_LEFT = 2;
+ private static final int DIRECTION_BOTTOM = 3;
+
+ private final int mPixelStride, mRowStride;
+ private int mNextStepSkip;
+ private int mStepCount;
+ private int mMaxStep = 1;
+ private int mDirection = DIRECTION_RIGHT;
+
+ public CentralSpiralIterator(Image image, Rect area, boolean duplicateBuffer) {
+ super(image, area, duplicateBuffer);
+ Image.Plane plane = image.getPlanes()[0];
+ mPixelStride = mNextStepSkip = plane.getPixelStride();
+ mRowStride = plane.getRowStride();
+ mByteBuffer.position(area.centerX() * mPixelStride + area.centerY() * mRowStride);
+ }
+
+ public CentralSpiralIterator(Image image, Rect area) {
+ this(image, area, false);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mByteBuffer.position() < mByteBuffer.limit();
+ }
+
+ @Override
+ public int nextColor() {
+ int c = mByteBuffer.getInt();
+ skip(mNextStepSkip);
+ mStepCount++;
+ if (mStepCount == mMaxStep) {
+ mStepCount = 0;
+ mMaxStep++;
+ mDirection = (mDirection + 1) & 4;
+ switch (mDirection) {
+ case DIRECTION_RIGHT:
+ mNextStepSkip = mPixelStride;
+ break;
+ case DIRECTION_TOP:
+ mNextStepSkip = -mRowStride;
+ break;
+ case DIRECTION_LEFT:
+ mNextStepSkip = -mPixelStride;
+ break;
+ case DIRECTION_BOTTOM:
+ mNextStepSkip = mRowStride;
+ break;
+ }
+ }
+ return c;
+ }
+
+ @Override
+ public int getX() {
+ return 0;
+ }
+
+ @Override
+ public int getY() {
+ return 0;
+ }
+ }
+
+
+}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ConcurrentColorIterator.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ConcurrentColorIterator.java
new file mode 100644
index 00000000..e4b0fb95
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ConcurrentColorIterator.java
@@ -0,0 +1,88 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.graphics.Rect;
+import android.media.Image;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Created by Stardust on 2017/5/21.
+ */
+
+public interface ConcurrentColorIterator {
+
+ void nextColor(Pixel pixel);
+
+ class Pixel {
+
+ public int x;
+ public int y;
+ public int color;
+ public boolean valid = true;
+
+ }
+
+ abstract class ConcurrentImageColorIterator implements ConcurrentColorIterator {
+
+ protected final ByteBuffer mByteBuffer;
+ protected final int mImageWidth, mImageHeight;
+ protected final Rect mIterateArea;
+ protected final int mAreaWidth, mAreaHeight;
+ protected volatile int mX = -1, mY;
+
+ public ConcurrentImageColorIterator(Image image, Rect area) {
+ Image.Plane plane = image.getPlanes()[0];
+ mByteBuffer = plane.getBuffer();
+ mImageWidth = image.getWidth();
+ mImageHeight = image.getHeight();
+ mIterateArea = area;
+ mAreaWidth = area.width();
+ mAreaHeight = area.height();
+ }
+
+
+ protected void skip(int i) {
+ mByteBuffer.position(mByteBuffer.position() + i);
+ }
+ }
+
+ class ConcurrentSequentialIterator extends ConcurrentImageColorIterator {
+
+ private final int mSkipPerRow;
+
+ public ConcurrentSequentialIterator(Image image, Rect area) {
+ super(image, area);
+ Image.Plane plane = image.getPlanes()[0];
+ int pixelStride = plane.getPixelStride();
+ int rowStride = plane.getRowStride();
+ int rowPadding = rowStride - pixelStride * mImageWidth;
+ mSkipPerRow = (mImageWidth - mAreaWidth) * pixelStride + rowPadding;
+ int offset = mIterateArea.top * rowStride + mIterateArea.left * pixelStride;
+ mByteBuffer.position(offset);
+ }
+
+ // TODO: 2017/5/21 对锁的竞争造成并发速度极慢。能否做到无锁?
+ @Override
+ public synchronized void nextColor(Pixel pixel) {
+ if (!(mY < mAreaHeight - 1 || mX < mAreaWidth - 1)) {
+ pixel.valid = false;
+ return;
+ }
+ int c = mByteBuffer.getInt();
+ c = ((c & 0xff) << 16) | (c & 0xff00) | ((c & 0xff0000) >> 16) | 0xff000000;
+ mX++;
+ if (mX == mAreaWidth) {
+ mX = 0;
+ mY++;
+ if (mSkipPerRow > 0) {
+ skip(mSkipPerRow);
+ }
+ }
+ pixel.x = mX + mIterateArea.left;
+ pixel.y = mY + mIterateArea.top;
+ pixel.color = c;
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java
new file mode 100644
index 00000000..8e375d57
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/Images.java
@@ -0,0 +1,140 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.media.Image;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import com.stardust.autojs.runtime.AbstractScriptRuntime;
+import com.stardust.autojs.runtime.ScriptInterruptedException;
+import com.stardust.autojs.runtime.ScriptVariable;
+import com.stardust.concurrent.VolatileBox;
+import com.stardust.pio.UncheckedIOException;
+import com.stardust.util.ScreenMetrics;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Created by Stardust on 2017/5/20.
+ */
+
+public class Images {
+
+ private AbstractScriptRuntime mScriptRuntime;
+ private ScreenCaptureRequester mScreenCaptureRequester;
+ private ScreenCapturer mScreenCapturer;
+ private Context mContext;
+
+ @ScriptVariable
+ public ColorFinder colorFinder = new ColorFinder();
+
+ public Images(Context context, AbstractScriptRuntime scriptRuntime, ScreenCaptureRequester screenCaptureRequester) {
+ mScriptRuntime = scriptRuntime;
+ mScreenCaptureRequester = screenCaptureRequester;
+ mContext = context;
+ }
+
+ public boolean requestScreenCapture(final int width, final int height) {
+ mScriptRuntime.requiresApi(21);
+ colorFinder.prestartThreads();
+ final VolatileBox requestResult = new VolatileBox<>();
+ mScreenCaptureRequester.setOnActivityResultCallback(new ScreenCaptureRequester.Callback() {
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void onRequestResult(int result, Intent data) {
+ if (result == Activity.RESULT_OK) {
+ mScreenCapturer = new ScreenCapturer(mContext, data, width, height);
+ requestResult.setAndNotify(true);
+ } else {
+ requestResult.setAndNotify(false);
+ }
+ }
+ });
+ mScreenCaptureRequester.request();
+ return requestResult.blockedGetOrThrow(ScriptInterruptedException.class);
+ }
+
+ public boolean requestScreenCapture() {
+ return requestScreenCapture(ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public Image captureScreen() {
+ mScriptRuntime.requiresApi(21);
+ colorFinder.prestartThreads();
+ return mScreenCapturer.capture();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public boolean captureScreen(String path) {
+ mScriptRuntime.requiresApi(21);
+ Image image = mScreenCapturer.capture();
+ if (image != null) {
+ saveImage(image, path);
+ image.close();
+ return true;
+ }
+ return false;
+ }
+
+ public void saveImage(Image image, String path) {
+ Bitmap bitmap = toBitmap(image);
+ saveBitmap(bitmap, path);
+ bitmap.recycle();
+ }
+
+ public static Bitmap toBitmap(Image image) {
+ Image.Plane plane = image.getPlanes()[0];
+ ByteBuffer buffer = plane.getBuffer();
+ buffer.position(0);
+ int pixelStride = plane.getPixelStride();
+ int rowPadding = plane.getRowStride() - pixelStride * image.getWidth();
+ Bitmap bitmap = Bitmap.createBitmap(image.getWidth() + rowPadding / pixelStride, image.getHeight(), Bitmap.Config.ARGB_8888);
+ bitmap.copyPixelsFromBuffer(buffer);
+ return bitmap;
+ }
+
+
+ public static void saveBitmap(Bitmap bitmap, String path) {
+ try {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(path));
+ } catch (FileNotFoundException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public void saveImage(Image image, String path, int width, int height) {
+ Bitmap bitmap = toBitmap(image);
+ if (width != bitmap.getWidth() || height != bitmap.getHeight()) {
+ bitmap = scaleBitmap(bitmap, width, height);
+ }
+ saveBitmap(bitmap, path);
+ bitmap.recycle();
+ }
+
+ public static Bitmap scaleBitmap(Bitmap origin, int newWidth, int newHeight) {
+ if (origin == null) {
+ return null;
+ }
+ int height = origin.getHeight();
+ int width = origin.getWidth();
+ float scaleWidth = ((float) newWidth) / width;
+ float scaleHeight = ((float) newHeight) / height;
+ Matrix matrix = new Matrix();
+ matrix.postScale(scaleWidth, scaleHeight);
+ return Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
+ }
+
+ public void releaseScreenCapturer() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mScreenCapturer != null) {
+ mScreenCapturer.release();
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCaptureRequester.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCaptureRequester.java
new file mode 100644
index 00000000..f3b23731
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCaptureRequester.java
@@ -0,0 +1,64 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.projection.MediaProjectionManager;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.support.annotation.RequiresApi;
+
+import com.stardust.app.OnActivityResultDelegate;
+
+/**
+ * Created by Stardust on 2017/5/17.
+ */
+
+public interface ScreenCaptureRequester {
+
+ interface Callback {
+
+ void onRequestResult(int result, Intent data);
+
+ }
+
+ void request();
+
+ void setOnActivityResultCallback(Callback callback);
+
+ abstract class AbstractScreenCaptureRequester implements ScreenCaptureRequester {
+
+ protected Callback mCallback;
+
+ @Override
+ public void setOnActivityResultCallback(Callback callback) {
+ mCallback = callback;
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ class ActivityScreenCaptureRequester extends AbstractScreenCaptureRequester implements ScreenCaptureRequester, OnActivityResultDelegate {
+
+ private static final int REQUEST_CODE_MEDIA_PROJECTION = "Eating...Today is 17.5.20 yet...The 90 days、、、".hashCode() >> 16;
+ private OnActivityResultDelegate.Mediator mMediator;
+ private Activity mActivity;
+
+ public ActivityScreenCaptureRequester(Mediator mediator, Activity activity) {
+ mMediator = mediator;
+ mActivity = activity;
+ mMediator.addDelegate(REQUEST_CODE_MEDIA_PROJECTION, this);
+ }
+
+ @Override
+ public void request() {
+ mActivity.startActivityForResult(((MediaProjectionManager) mActivity.getSystemService(Context.MEDIA_PROJECTION_SERVICE)).createScreenCaptureIntent(), REQUEST_CODE_MEDIA_PROJECTION);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mMediator.removeDelegate(this);
+ mCallback.onRequestResult(resultCode, data);
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCapturer.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCapturer.java
new file mode 100644
index 00000000..595c58ce
--- /dev/null
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/image/ScreenCapturer.java
@@ -0,0 +1,77 @@
+package com.stardust.autojs.runtime.api.image;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Build;
+import android.os.Looper;
+import android.support.annotation.RequiresApi;
+
+import com.stardust.util.ScreenMetrics;
+
+/**
+ * Created by Stardust on 2017/5/17.
+ */
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+public class ScreenCapturer {
+
+ private ImageReader mImageReader;
+ private MediaProjection mMediaProjection;
+ private VirtualDisplay mVirtualDisplay;
+ private final Object mImageLock = new Object();
+
+ public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight, int screenDensity) {
+ MediaProjectionManager manager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+ initVirtualDisplay(manager, data, screenWidth, screenHeight, screenDensity);
+ }
+
+ public ScreenCapturer(Context context, Intent data, int screenWidth, int screenHeight) {
+ this(context, data, screenWidth, screenHeight, ScreenMetrics.getDeviceScreenDensity());
+ }
+
+ public ScreenCapturer(Context context, Intent data) {
+ this(context, data, ScreenMetrics.getDeviceScreenWidth(), ScreenMetrics.getDeviceScreenHeight());
+ }
+
+ private void initVirtualDisplay(MediaProjectionManager manager, Intent data, int screenWidth, int screenHeight, int screenDensity) {
+ mImageReader = ImageReader.newInstance(screenWidth, screenHeight, PixelFormat.RGBA_8888, 1);
+ mMediaProjection = manager.getMediaProjection(Activity.RESULT_OK, data);
+ mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
+ screenWidth, screenHeight, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ mImageReader.getSurface(), null, null);
+ }
+
+ public Image capture() {
+ Image image = mImageReader.acquireLatestImage();
+ if (image == null) {
+ Looper.prepare();
+ mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Looper.myLooper().quit();
+ }
+ }, null);
+ Looper.loop();
+ image = mImageReader.acquireLatestImage();
+ }
+ return image;
+ }
+
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public void release() {
+ if (mMediaProjection != null) {
+ mMediaProjection.stop();
+ }
+ if (mVirtualDisplay != null) {
+ mVirtualDisplay.release();
+ }
+ }
+}
diff --git a/autojs/src/test/java/com/stardust/autojs/ExampleUnitTest.java b/autojs/src/test/java/com/stardust/autojs/ExampleUnitTest.java
index 9a930c82..f7c84114 100644
--- a/autojs/src/test/java/com/stardust/autojs/ExampleUnitTest.java
+++ b/autojs/src/test/java/com/stardust/autojs/ExampleUnitTest.java
@@ -1,5 +1,11 @@
package com.stardust.autojs;
+import com.stardust.autojs.runtime.api.image.ColorDetector;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -8,4 +14,12 @@ package com.stardust.autojs;
public class ExampleUnitTest {
+ @Test
+ public void test() {
+
+ }
+
+ public boolean equals(int i, int j) {
+ return i == j;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java b/common/src/main/java/com/stardust/app/OnActivityResultDelegate.java
similarity index 93%
rename from app/src/main/java/com/stardust/app/OnActivityResultDelegate.java
rename to common/src/main/java/com/stardust/app/OnActivityResultDelegate.java
index de273984..f9db8b2d 100644
--- a/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java
+++ b/common/src/main/java/com/stardust/app/OnActivityResultDelegate.java
@@ -1,6 +1,7 @@
package com.stardust.app;
import android.content.Intent;
+import android.support.annotation.NonNull;
import android.util.SparseArray;
import java.util.ArrayList;
@@ -12,11 +13,11 @@ import java.util.List;
public interface OnActivityResultDelegate {
-
void onActivityResult(int requestCode, int resultCode, Intent data);
interface DelegateHost {
- Mediator getDelegateManger();
+ @NonNull
+ Mediator getOnActivityResultDelegateMediator();
}
class Mediator implements OnActivityResultDelegate {
diff --git a/common/src/main/java/com/stardust/concurrent/VolatileBox.java b/common/src/main/java/com/stardust/concurrent/VolatileBox.java
index 5bcf0803..5648896d 100644
--- a/common/src/main/java/com/stardust/concurrent/VolatileBox.java
+++ b/common/src/main/java/com/stardust/concurrent/VolatileBox.java
@@ -1,6 +1,8 @@
package com.stardust.concurrent;
+import java.lang.reflect.Constructor;
+
/**
* Created by Stardust on 2017/5/8.
*/
@@ -25,6 +27,14 @@ public class VolatileBox {
mValue = value;
}
+ public boolean isNull() {
+ return mValue == null;
+ }
+
+ public boolean notNull() {
+ return mValue != null;
+ }
+
public void setAndNotify(T value) {
mValue = value;
synchronized (this) {
@@ -43,4 +53,18 @@ public class VolatileBox {
return mValue;
}
+ public T blockedGetOrThrow(Class extends RuntimeException> exception) {
+ synchronized (this) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ try {
+ throw exception.newInstance();
+ } catch (InstantiationException | IllegalAccessException e1) {
+ throw new RuntimeException(e1);
+ }
+ }
+ }
+ return mValue;
+ }
}
diff --git a/common/src/main/java/com/stardust/util/ScreenMetrics.java b/common/src/main/java/com/stardust/util/ScreenMetrics.java
index a148b041..0f12791a 100644
--- a/common/src/main/java/com/stardust/util/ScreenMetrics.java
+++ b/common/src/main/java/com/stardust/util/ScreenMetrics.java
@@ -12,6 +12,7 @@ public class ScreenMetrics {
private static int deviceScreenHeight;
private static int deviceScreenWidth;
private static boolean initialized = false;
+ private static int deviceScreenDensity;
public static void initIfNeeded(Activity activity) {
if (!initialized) {
@@ -19,6 +20,7 @@ public class ScreenMetrics {
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
deviceScreenHeight = metrics.heightPixels;
deviceScreenWidth = metrics.widthPixels;
+ deviceScreenDensity = metrics.densityDpi;
initialized = true;
}
}
@@ -31,10 +33,26 @@ public class ScreenMetrics {
return deviceScreenWidth;
}
+ public static int getDeviceScreenDensity() {
+ return deviceScreenDensity;
+ }
+
+ public static int scaleX(int x, int width) {
+ if (width == 0 || !initialized)
+ return x;
+ return x * deviceScreenWidth / width;
+ }
+
+ public static int scaleY(int y, int height) {
+ if (height == 0 || !initialized)
+ return y;
+ return y * deviceScreenHeight / height;
+ }
private int mScreenWidth;
private int mScreenHeight;
+
public void setScreenWidth(int screenWidth) {
mScreenWidth = screenWidth;
}
@@ -44,17 +62,14 @@ public class ScreenMetrics {
}
public int scaleX(int x) {
- if (mScreenWidth == 0 || !initialized)
- return x;
- return x * deviceScreenWidth / mScreenWidth;
+ return scaleX(x, mScreenWidth);
}
public int scaleY(int y) {
- if (mScreenHeight == 0 || !initialized)
- return y;
- return y * deviceScreenHeight / mScreenHeight;
+ return scaleY(y, mScreenHeight);
}
+
public void setScreenMetrics(int width, int height) {
mScreenWidth = width;
mScreenHeight = height;