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