>}cYHr|dh=3FMH;X~
zaJu{K6k2`)k=16Qrk>FIv4=SC+p9|jg+Mdnl+>E@7vA|`d3pC6EPlOp)%=O6ul#!p
zvdW?n0!)^{2kAf+>U#TkTs{RTB>P*&=e8s8-KOIRK0*^>0~dTA!C9!Fz(ac>yiH7v
zXrgt}B=Eqh$~mo3bv{A|>KtK?8kYH76B5K7INzc0R|yl)#hdRECY&MEYPL|s%wSo^
zzK3n7w51zs3eP`as>0M1FWbnzDM^@`>rDAof9vYuJ5AV-l@$@4DQ<&S_P3~B2w8lrxy#wIebbYG{%a^lKBRdduwk8HJNapE
zTC4s^Y2bPLoNdd;Vl4B(9dc!&??WfOLhsX@F`xYS9za_7cQ3%Xj`x<&C|JEVdc6>s
zqTdN4guT8spwh4oYhO0D3xhe=n6eK_G1#!_?p=f*TNbFTp%}1d5SR*6)(r$gDb(A_
zz=isKJJ4iVii&NNJ`x9!+ETH@7W2)DT3`+Cg4|-MDeIDf*|^#tKOV{SdYxUDjr(_H
zBN9m;s5B{g;f}Khz1447SPx`dUdVMiGan@T^)nAjZD)X`_!yin24fAA%e%Y~h4$JF
zLIud`k&gX_@}R_D(X@@ywZ+sTU#3Zx*ti8*;-~w+ROX41x?~kwCp?C+QQFqYjS@d}
z?GM;WpajU5SZ$EsIyWDW)y4=YW$@mhlgwVES2tw6a*Xmsg)*=qaR*w0`5Rgy-<-0D
zqr`h!QQ?vu*o(xaO5|((H$91LPU?}l8LJ2RC#?R(=1*t-zrs+kdxdt!QHlD18q
z4RR_kI$U+R)=awmI3_CSp5&Tt$YKP;^L95H2&P|E4n$KshVop^`NlbI>{A*ILTlWG
zUg1sC;Z>9Bk^&CZEAux0N%Ha7-049fnekxYy7v8;gGW|o>A|1aH@_2iTznqn#oRD)
zJw#xN|8UFOrY5*D@Ch-ni~!4F0AsZW#Se?0W*3`}Ym)=y*(pe6U
zDWlmCRI{p*9;el%+$WI*oh}(7=*ZFuv{j&^0f<*mUU)aQex;FJGx~&2#Bb@DU4a?4
zkGxnG(xZ&(1FE0Dq)8qV-)}EXJLk8aQj+-`JF%z7{6R4%PN**`*Pj#v8nXFe?i=Y4
zo#P!Rf{W>EpYA+)vot$3?9SHsahS963Y?Z1n>E4>JV1mG2rxm`LA^hZ!RGQ$kHe4=
zv-OYwwOC+!yRo^fN797Fu_F=(2XH*(Vq0hJoA?hz0G+1*
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable/ic_new_directory.png b/app/src/main/res/drawable/ic_new_directory.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c10f4354c718fa8a8119d610a5d117dc8e4e27e
GIT binary patch
literal 4034
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
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_about_items.xml b/app/src/main/res/layout/activity_about_items.xml
index b53e94b0..00b319d3 100644
--- a/app/src/main/res/layout/activity_about_items.xml
+++ b/app/src/main/res/layout/activity_about_items.xml
@@ -52,7 +52,6 @@
android:layout_height="wrap_content"
android:text="@string/github"
android:textColor="@android:color/secondary_text_light"
- android:textIsSelectable="true"
android:textSize="16sp"/>
@@ -74,7 +73,7 @@
android:src="@drawable/ic_email_black_48dp"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_directory_operations.xml b/app/src/main/res/layout/dialog_directory_operations.xml
new file mode 100644
index 00000000..c861160f
--- /dev/null
+++ b/app/src/main/res/layout/dialog_directory_operations.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_script_file_operations.xml b/app/src/main/res/layout/dialog_script_file_operations.xml
new file mode 100644
index 00000000..96224fb7
--- /dev/null
+++ b/app/src/main/res/layout/dialog_script_file_operations.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_my_script_list.xml b/app/src/main/res/layout/fragment_my_script_list.xml
index 7f741c54..cb2e981c 100644
--- a/app/src/main/res/layout/fragment_my_script_list.xml
+++ b/app/src/main/res/layout/fragment_my_script_list.xml
@@ -1,11 +1,13 @@
-
@@ -17,5 +19,22 @@
android:gravity="center"
android:text="@string/hint_no_script"
android:textColor="#adadad"
- android:textSize="16sp"/>
+ android:textSize="16sp"
+ android:visibility="gone"/>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/script_list_recycler_view_folder.xml b/app/src/main/res/layout/script_list_recycler_view_folder.xml
new file mode 100644
index 00000000..4817e678
--- /dev/null
+++ b/app/src/main/res/layout/script_list_recycler_view_folder.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/script_list_recycler_view_item.xml b/app/src/main/res/layout/script_list_recycler_view_item.xml
index 19c59455..fa97fb2b 100644
--- a/app/src/main/res/layout/script_list_recycler_view_item.xml
+++ b/app/src/main/res/layout/script_list_recycler_view_item.xml
@@ -5,11 +5,11 @@
android:layout_height="wrap_content"
android:foreground="?android:selectableItemBackground"
android:orientation="vertical"
- android:paddingBottom="12dp"
- android:paddingLeft="16dp"
- android:paddingTop="12dp">
+ android:paddingBottom="8dp"
+ android:paddingLeft="12dp"
+ android:paddingTop="8dp">
-
+ android:src="@drawable/ic_node_js_black"/>
+
+
+
+
+
+
-
-
-
-
-
-
-
+ android:src="@drawable/ic_ali_edit"/>
+
diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml
index 994f9a26..13a978b2 100644
--- a/app/src/main/res/values-en/strings.xml
+++ b/app/src/main/res/values-en/strings.xml
@@ -8,7 +8,7 @@
Exit
Service disabled.
No file explorer T.T
- File creation failed
+ Creation failed
Input name
Name
Go to setting
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ddd5712c..575b1886 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,7 +8,7 @@
退出
服务已关闭
找不到文件管理器T.T
- 文件创建失败
+ 创建失败
请输入名称
名称
去设置
@@ -180,6 +180,11 @@
脚本
管理
任务管理
+ 名称不能为空
+ 新文件夹
+ 导入失败
+ 确定要删除吗
+ 没有浏览器耶o(╯□╰)o快去安装一个吧
- 无
diff --git a/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java b/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java
index 84d00c0f..1fd3dd15 100644
--- a/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java
+++ b/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java
@@ -4,6 +4,7 @@ import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
import com.stardust.scriptdroid.droid.script.RhinoJavaScriptEngine;
import com.stardust.scriptdroid.droid.script.file.ScriptFile;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsConverter;
+import com.stardust.util.LimitedHashMap;
import org.junit.Test;
import org.mozilla.javascript.Context;
@@ -27,6 +28,8 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* Example local unit test, which will execute on the development machine (host).
@@ -45,6 +48,9 @@ public class ExampleUnitTest {
// TODO: 2017/3/24 ui。E4x
// TODO: 2017/3/24 编辑界面文档和自动补全
// TODO: 2017/3/24 驻留模式
+ //// TODO: 2017/3/26 NODEJS
+ // TODO: 2017/3/26 加密
+ // TODO: 2017/3/31 自定义快捷方式图标
// FIXME: 2017/3/23 死机重启问题
@@ -53,44 +59,30 @@ public class ExampleUnitTest {
@Test
public void test() {
-
- List paths = Collections.singletonList(new File("D:/js/").toURI());
- Context ctx = Context.enter();
- ctx.setOptimizationLevel(-1);
- ctx.setLanguageVersion(Context.VERSION_1_7);
- ctx.setInstructionObserverThreshold(10000);
- ImporterTopLevel scope = new ImporterTopLevel();
- scope.initStandardObjects(ctx, false);
-
- new RequireBuilder()
- .setModuleScriptProvider(new SoftCachingModuleScriptProvider(
- new UrlModuleSourceProvider(paths, null)))
- .setSandboxed(true)
- .createRequire(ctx, scope)
- .install(scope);
-
-
-
- Context.exit();
+ LimitedHashMap hashMap = new LimitedHashMap<>(5);
+ hashMap.put("a", 1);
+ hashMap.put("b", 1);
+ hashMap.put("c", 1);
+ hashMap.put("d", 1);
+ hashMap.put("e", 1);
+ hashMap.get("a");
+ hashMap.put("f", 1);
+ assertFalse(hashMap.containsKey("a"));
}
@Test
- public void test2(){
- Context context = Context.enter();
- context.setOptimizationLevel(-1);
- context.setLanguageVersion(Context.VERSION_1_7);
- context.setInstructionObserverThreshold(10000);
- ImporterTopLevel scope = new ImporterTopLevel();
- scope.initStandardObjects(context, false);
- List paths = Collections.singletonList(new File("D:/js/").toURI());
- new RequireBuilder()
- .setModuleScriptProvider(new SoftCachingModuleScriptProvider(
- new UrlModuleSourceProvider(paths, null)))
- .setSandboxed(true)
- .createRequire(context, scope)
- .install(scope);
- context.evaluateString(scope, "require('test.js')()", "", 1, null);
-
- Context.exit();
+ public void testAutoReorder() {
+ LimitedHashMap hashMap = new LimitedHashMap<>(5);
+ hashMap.put("a", 1);
+ hashMap.put("b", 2);
+ hashMap.put("c", 3);
+ hashMap.put("d", 4);
+ hashMap.put("e", 5);
+ hashMap.get("a");
+ hashMap.put("f", 6);
+ assertTrue(hashMap.containsKey("a"));
+ assertFalse(hashMap.containsKey("b"));
}
+
+
}
\ No newline at end of file
diff --git a/autojs/.gitignore b/autojs/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/autojs/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/autojs/build.gradle b/autojs/build.gradle
new file mode 100644
index 00000000..eb51a094
--- /dev/null
+++ b/autojs/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 25
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ compile 'com.android.support:appcompat-v7:25.3.0'
+ testCompile 'junit:junit:4.12'
+}
diff --git a/autojs/proguard-rules.pro b/autojs/proguard-rules.pro
new file mode 100644
index 00000000..27b6816c
--- /dev/null
+++ b/autojs/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in E:\YiBin\eclipse\Android_SDK_windows/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/autojs/src/androidTest/java/com/example/autojs/ExampleInstrumentedTest.java b/autojs/src/androidTest/java/com/example/autojs/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..7ed030c8
--- /dev/null
+++ b/autojs/src/androidTest/java/com/example/autojs/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.autojs;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.example.autojs.test", appContext.getPackageName());
+ }
+}
diff --git a/autojs/src/main/AndroidManifest.xml b/autojs/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..4687a903
--- /dev/null
+++ b/autojs/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/autojs/src/main/java/autojs/Droid.java b/autojs/src/main/java/autojs/Droid.java
new file mode 100644
index 00000000..4df27a47
--- /dev/null
+++ b/autojs/src/main/java/autojs/Droid.java
@@ -0,0 +1,172 @@
+package autojs;
+
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+import com.stardust.scriptdroid.App;
+import com.stardust.scriptdroid.Pref;
+import com.stardust.scriptdroid.R;
+import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
+import com.stardust.scriptdroid.droid.script.JavaScriptEngine;
+import com.stardust.scriptdroid.droid.script.ScriptExecuteActivity;
+import com.stardust.scriptdroid.service.VolumeChangeObverseService;
+import com.stardust.scriptdroid.tool.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.util.Date;
+
+import timber.log.Timber;
+
+/**
+ * Created by Stardust on 2017/1/23.
+ */
+
+public class Droid {
+
+ public static final String STAY = "\"stay\";";
+
+ public static final String UI = "\"ui\";";
+
+ public static final String AUTO = "\"auto\";";
+
+ public interface OnRunFinishedListener extends Serializable {
+ void onRunFinished(Object result);
+
+ void onException(@NonNull Exception e);
+ }
+
+ public static class SimpleOnRunFinishedListener implements OnRunFinishedListener {
+
+ @Override
+ public void onRunFinished(Object result) {
+
+ }
+
+ @Override
+ public void onException(@NonNull Exception e) {
+
+ }
+ }
+
+ private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime();
+ public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = JavaScriptEngine.getDefault();
+ private static final OnRunFinishedListener DEFAULT_LISTENER = new SimpleOnRunFinishedListener() {
+ @Override
+ public void onException(@NonNull Exception e) {
+ if (!causedByInterruptedException(e)) {
+ RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage());
+ Timber.e(e.getMessage());
+ }
+ }
+ };
+ private static Droid instance = new Droid();
+ private VolumeChangeObverseService.OnVolumeChangeListener mOnVolumeChangeListener = new VolumeChangeObverseService.OnVolumeChangeListener() {
+ @Override
+ public void onVolumeChange() {
+ if (Pref.isRunningVolumeControlEnabled()) {
+ stopAllAndToast();
+ }
+ }
+ };
+
+
+ private Droid() {
+ VolumeChangeObverseService.addOnVolumeChangeListener(mOnVolumeChangeListener);
+ }
+
+ public static Droid getInstance() {
+ return instance;
+ }
+
+ public static boolean causedByInterruptedException(Throwable e) {
+ while (e != null) {
+ if (e instanceof InterruptedException) {
+ return true;
+ }
+ e = e.getCause();
+ }
+ return false;
+ }
+
+ public void runScriptFile(File file) {
+ runScriptFile(file, null, new RunningConfig());
+ }
+
+ public void runScriptFile(File file, OnRunFinishedListener listener, RunningConfig config) {
+ Timber.v(DateFormat.getTimeInstance().format(new Date()) + " " + App.getResString(R.string.text_start_running) + " " + file);
+ listener = listener == null ? DEFAULT_LISTENER : listener;
+ int errorMsgId = PathChecker.check(file.getPath());
+ if(errorMsgId != PathChecker.CHECK_RESULT_OK){
+ listener.onException(new IOException(App.getResString(errorMsgId)));
+ }
+ ScriptEngine
+ runScript(FileUtils.readString(file), listener, config.path(file.getPath()));
+ }
+
+
+ public void runScriptFile(String path) {
+ runScriptFile(new File(path));
+ }
+
+ public void runScript(String script) {
+ runScript(script, null, RunningConfig.getDefault());
+ }
+
+ public void runScript(String script, OnRunFinishedListener listener, RunningConfig config) {
+ App.getApp().startService(new Intent(App.getApp(), VolumeChangeObverseService.class));
+ listener = listener == null ? DEFAULT_LISTENER : listener;
+ if (!TextUtils.isEmpty(config.prepareScript))
+ script = config.prepareScript + "\n" + script;
+ if (config.runInNewThread) {
+ if (script.startsWith(UI)) {
+ ScriptExecuteActivity.runScript(script, listener, config);
+ } else {
+ new Thread(new RunScriptRunnable(script, listener, config)).start();
+ }
+ } else {
+ new RunScriptRunnable(script, listener, config).run();
+ }
+
+ }
+
+
+ public int stopAll() {
+ return JAVA_SCRIPT_ENGINE.stopAll();
+ }
+
+
+ public void stopAllAndToast() {
+ int n = stopAll();
+ if (n > 0)
+ RUNTIME.toast(String.format(App.getResString(R.string.text_already_stop_n_scripts), n));
+ else
+ RUNTIME.toast(App.getResString(R.string.text_no_running_script));
+ }
+
+ private static class RunScriptRunnable implements Runnable {
+
+ private final String mScript;
+ private OnRunFinishedListener mOnRunFinishedListener;
+
+ RunScriptRunnable(String script, OnRunFinishedListener listener, RunningConfig config) {
+ mOnRunFinishedListener = listener;
+ mScript = script;
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (mScript.startsWith(AUTO))
+ DroidRuntime.getRuntime().ensureAccessibilityServiceEnabled();
+ mOnRunFinishedListener.onRunFinished(JAVA_SCRIPT_ENGINE.execute(mScript));
+ } catch (Exception e) {
+ mOnRunFinishedListener.onException(e);
+ }
+ }
+
+ }
+}
diff --git a/autojs/src/main/java/autojs/PathChecker.java b/autojs/src/main/java/autojs/PathChecker.java
new file mode 100644
index 00000000..ada794d8
--- /dev/null
+++ b/autojs/src/main/java/autojs/PathChecker.java
@@ -0,0 +1,62 @@
+package autojs;
+
+import android.app.Activity;
+import android.os.Build;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import com.stardust.scriptdroid.R;
+
+import java.io.File;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+/**
+ * Created by Stardust on 2017/4/1.
+ */
+
+public class PathChecker {
+ public static final int CHECK_RESULT_OK = 0;
+
+ private Activity mActivity;
+
+ public PathChecker(Activity activity) {
+ mActivity = activity;
+ }
+
+
+ public static int check(final String path) {
+ if (TextUtils.isEmpty(path))
+ return R.string.text_path_is_empty;
+ if (!new File(path).exists())
+ return R.string.text_file_not_exists;
+ return CHECK_RESULT_OK;
+ }
+
+ public boolean checkAndToastError(String path) {
+ int result = checkWithStoragePermission(path);
+ if (result != CHECK_RESULT_OK) {
+ Toast.makeText(mActivity, result, Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ return true;
+ }
+
+ private int checkWithStoragePermission(String path) {
+ if (!hasStorageReadPermission(mActivity)) {
+ return R.string.text_no_file_rw_permission;
+ }
+ return check(path);
+ }
+
+ private static boolean hasStorageReadPermission(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
+ activity.checkSelfPermission(READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED;
+ }
+ return true;
+ }
+
+
+}
diff --git a/autojs/src/main/java/autojs/RunningConfig.java b/autojs/src/main/java/autojs/RunningConfig.java
new file mode 100644
index 00000000..227074a3
--- /dev/null
+++ b/autojs/src/main/java/autojs/RunningConfig.java
@@ -0,0 +1,32 @@
+package autojs;
+
+/**
+ * Created by Stardust on 2017/2/1.
+ */
+public class RunningConfig {
+
+ private static final RunningConfig RUNNING_CONFIG = new RunningConfig();
+ public String path;
+ public String prepareScript = "";
+
+ public static RunningConfig getDefault() {
+ return RUNNING_CONFIG;
+ }
+
+ public boolean runInNewThread = true;
+
+ public RunningConfig runInNewThread(boolean runInNewThread) {
+ this.runInNewThread = runInNewThread;
+ return this;
+ }
+
+ public RunningConfig path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public RunningConfig prepareScript(String script) {
+ this.prepareScript = script;
+ return this;
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/DroidRuntime.java b/autojs/src/main/java/autojs/runtime/DroidRuntime.java
new file mode 100644
index 00000000..3094e096
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/DroidRuntime.java
@@ -0,0 +1,442 @@
+package autojs.runtime;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Toast;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.jraska.console.Console;
+import com.jraska.console.timber.ConsoleTree;
+import com.stardust.automator.AccessibilityEventCommandHost;
+import com.stardust.scriptdroid.App;
+import com.stardust.scriptdroid.Pref;
+import com.stardust.scriptdroid.R;
+import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
+import com.stardust.scriptdroid.droid.runtime.action.Action;
+import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
+import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
+import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
+import com.stardust.scriptdroid.droid.runtime.api.UiSelector;
+import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
+import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
+import com.stardust.scriptdroid.tool.FileUtils;
+import com.stardust.scriptdroid.tool.IntentTool;
+import com.stardust.scriptdroid.tool.Shell;
+import com.stardust.scriptdroid.ui.console.ConsoleActivity;
+import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
+import com.stardust.view.accessibility.AccessibilityServiceUtils;
+
+import java.io.File;
+import java.util.List;
+
+import timber.log.Timber;
+
+;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public class DroidRuntime {
+
+ static {
+ Timber.plant(new ConsoleTree.Builder()
+ .minPriority(Log.VERBOSE)
+ .verboseColor(0xff909090)
+ .debugColor(0xffc88b48)
+ .infoColor(0xffc9c9c9)
+ .warnColor(0xffa97db6)
+ .errorColor(0xffff534e)
+ .assertColor(0xffff5540)
+ .build());
+ }
+
+ private static class PerformGlobalActionCommand implements AccessibilityEventCommandHost.Command {
+
+ boolean result;
+ private int mGlobalAction;
+
+ PerformGlobalActionCommand(int globalAction) {
+ mGlobalAction = globalAction;
+ }
+
+ @Override
+ public void execute(AccessibilityService service, AccessibilityEvent event) {
+ result = service.performGlobalAction(mGlobalAction);
+ }
+ }
+
+ private static final String TAG = "DroidRuntime";
+ private static DroidRuntime runtime;
+
+ private final Object mActionPerformLock = new Object();
+ private Handler mUIHandler;
+ private Context mContext;
+
+ public static void initRuntime(Context context){
+ runtime = new DroidRuntime(context);
+ }
+
+ public static DroidRuntime getRuntime() {
+ return runtime;
+ }
+
+ private DroidRuntime(Context context) {
+ mContext = context;
+ mUIHandler = new Handler(mContext.getMainLooper());
+ }
+
+ public boolean launchPackage(String packageName) {
+ try {
+ PackageManager packageManager = App.getApp().getPackageManager();
+ App.getApp().startActivity(packageManager.getLaunchIntentForPackage(packageName));
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+
+ }
+
+ public boolean launchApp(String appName) {
+ return launchPackage(getPackageName(appName));
+ }
+
+ public String getPackageName(String appName) {
+ PackageManager packageManager = App.getApp().getPackageManager();
+ List installedApplications = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
+ for (ApplicationInfo applicationInfo : installedApplications) {
+ if (packageManager.getApplicationLabel(applicationInfo).toString().equals(appName)) {
+ return applicationInfo.processName;
+ }
+ }
+ return "";
+ }
+
+ public boolean openAppSetting(String packageName) {
+ return IntentTool.goToAppSetting(App.getApp(), packageName);
+ }
+
+ public ActionTarget text(String text, int i) {
+ return new ActionTarget.TextActionTarget(text, i);
+ }
+
+ public ActionTarget bounds(int left, int top, int right, int bottom) {
+ return new ActionTarget.BoundsActionTarget(new Rect(left, top, right, bottom));
+ }
+
+ public ActionTarget editable(int i) {
+ return new ActionTarget.EditableActionTarget(i);
+ }
+
+ public ActionTarget id(String id) {
+ return new ActionTarget.IdActionTarget(id);
+ }
+
+ public boolean click(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
+ }
+
+ public boolean longClick(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_LONG_CLICK));
+ }
+
+ public boolean scrollUp(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
+ }
+
+ public boolean scrollDown(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
+ }
+
+ public boolean scrollUp(int i) {
+ return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, i));
+ }
+
+ public boolean scrollDown(int i) {
+ return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, i));
+ }
+
+ public boolean scrollAllUp() {
+ return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
+ }
+
+ public boolean scrollAllDown() {
+ return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
+ }
+
+ public boolean focus(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_FOCUS));
+ }
+
+ public boolean select(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SELECT));
+ }
+
+ public boolean setText(ActionTarget target, String text) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SET_TEXT, text));
+ }
+
+ public void setClip(final String text) {
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ((ClipboardManager) App.getApp().getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText(TAG, text));
+ }
+ });
+ }
+
+ public boolean back() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
+ }
+
+ public boolean home() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
+ }
+
+ public boolean powerDialog() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
+ }
+
+ public boolean notifications() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
+ }
+
+ public boolean quickSettings() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
+ }
+
+ public boolean recents() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
+ }
+
+ public boolean splitScreen() {
+ return performGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);
+ }
+
+ public boolean swipeDown() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN);
+ }
+
+ public boolean swipeDownLeft() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT);
+ }
+
+ public boolean swipeDownRight() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT);
+ }
+
+ public boolean swipeDownUp() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP);
+ }
+
+ public boolean swipeUp() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP);
+ }
+
+ public boolean swipeUpLeft() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT);
+ }
+
+ public boolean swipeUpRight() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT);
+ }
+
+ public boolean swipeUpDown() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN);
+ }
+
+ public boolean swipeLeft() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT);
+ }
+
+ public boolean swipeLeftRight() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT);
+ }
+
+ public boolean swipeLeftUp() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP);
+ }
+
+ public boolean swipeLeftDown() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN);
+ }
+
+ public boolean swipeRight() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT);
+ }
+
+ public boolean swipeRightLeft() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT);
+ }
+
+ public boolean swipeRightUp() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP);
+ }
+
+ public boolean swipeRightDown() {
+ return performGlobalAction(AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN);
+ }
+
+ private boolean performGlobalAction(final int action) {
+ ensureAccessibilityServiceEnabled();
+ PerformGlobalActionCommand command = new PerformGlobalActionCommand(action);
+ AccessibilityEventCommandHost.getInstance().executeAndWaitForEvent(command);
+ return command.result;
+ }
+
+ public boolean paste(ActionTarget target) {
+ return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
+ }
+
+ public void log(@Nullable Object o) {
+ String str = o + "";
+ SpannableString spannableString = new SpannableString(str);
+ spannableString.setSpan(new ForegroundColorSpan(Color.BLACK), 0, str.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Console.writeLine(spannableString);
+ }
+
+ public void err(@Nullable Object o) {
+ Timber.e("" + o);
+ }
+
+ public void console() {
+ App.getApp().startActivity(new Intent(App.getApp(), ConsoleActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ public void clearConsole() {
+ Console.clear();
+ }
+
+ private T performAction(Action action) {
+ ensureAccessibilityServiceEnabled();
+ ActionPerformAccessibilityDelegate.setAction(action);
+ synchronized (mActionPerformLock) {
+ try {
+ mActionPerformLock.wait();
+ } catch (InterruptedException e) {
+ ActionPerformAccessibilityDelegate.setAction(ActionPerformAccessibilityDelegate.NO_ACTION);
+ throw new ScriptStopException(App.getApp().getString(R.string.text_script_stopped), e);
+ }
+ }
+ return (T) action.getResult();
+ }
+
+ public void ensureAccessibilityServiceEnabled() {
+ if (AccessibilityWatchDogService.getInstance() == null) {
+ String errorMessage = null;
+ if (AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class)) {
+ errorMessage = App.getApp().getString(R.string.text_auto_operate_service_enabled_but_not_running);
+ } else {
+ if (Pref.def().getBoolean(App.getApp().getString(R.string.key_enable_accessibility_service_by_root), false)) {
+ if (!AccessibilityServiceTool.enableAccessibilityServiceByRootAndWaitFor(AccessibilityWatchDogService.class, 3000)) {
+ errorMessage = App.getApp().getString(R.string.text_enable_accessibility_service_by_root_timeout);
+ }
+ } else {
+ errorMessage = App.getApp().getString(R.string.text_no_accessibility_permission);
+ }
+ }
+ if (errorMessage != null) {
+ toast(errorMessage);
+ throw new ScriptStopException(errorMessage);
+ }
+ }
+ }
+
+ public void toast(final String text) {
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(App.getApp(), text, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ throw new ScriptStopException(e);
+ }
+ }
+
+ public void addAccessibilityEventListener(final Object delegate) {
+ if (delegate == null)
+ return;
+
+ }
+
+ public Shell.CommandResult shell(String cmd, int root) {
+ return Shell.execCommand(cmd, root != 0);
+ }
+
+ public String currentPackage() {
+ ensureAccessibilityServiceEnabled();
+ return AccessibilityInfoProvider.getInstance().getLatestPackage();
+ }
+
+ public String currentActivity() {
+ ensureAccessibilityServiceEnabled();
+ return AccessibilityInfoProvider.getInstance().getLatestActivity();
+ }
+
+ public UiSelector selector() {
+ return new UiSelector();
+ }
+
+ public boolean isStopped() {
+ return Thread.currentThread().isInterrupted();
+ }
+
+ public void stop() {
+ Thread.interrupted();
+ }
+
+ public String readFile(String path) {
+ return FileUtils.readString(new File(Environment.getExternalStorageDirectory() + "/" + path));
+ }
+
+ public ThemeColorMaterialDialogBuilder dialog() {
+ if (App.currentActivity() == null) {
+ throw new ScriptStopException(App.getApp().getString(R.string.text_cannot_create_dialog_when_app_invisible));
+ }
+ return new ThemeColorMaterialDialogBuilder(App.currentActivity()) {
+
+ public MaterialDialog show() {
+ mUIHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ superShow();
+ }
+ });
+ return null;
+ }
+
+ private void superShow() {
+ super.show();
+ }
+ };
+ }
+
+ public void notifyActionPerformed() {
+ synchronized (mActionPerformLock) {
+ mActionPerformLock.notify();
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/ScriptStopException.java b/autojs/src/main/java/autojs/runtime/ScriptStopException.java
new file mode 100644
index 00000000..daa691c6
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/ScriptStopException.java
@@ -0,0 +1,24 @@
+package autojs.runtime;
+
+/**
+ * Created by Stardust on 2017/1/29.
+ */
+public class ScriptStopException extends RuntimeException {
+
+
+ public ScriptStopException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ScriptStopException(String message) {
+ super(message);
+ }
+
+ public ScriptStopException() {
+
+ }
+
+ public ScriptStopException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/Able.java b/autojs/src/main/java/autojs/runtime/action/Able.java
new file mode 100644
index 00000000..4ed8d249
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/Able.java
@@ -0,0 +1,49 @@
+package autojs.runtime.action;
+
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.stardust.util.SparseArrayEntries;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public interface Able {
+
+ SparseArray ABLE_MAP = new SparseArrayEntries()
+ .entry(AccessibilityNodeInfo.ACTION_CLICK, new Able() {
+ @Override
+ public boolean isAble(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.isClickable();
+ }
+ })
+ .entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, new Able() {
+ @Override
+ public boolean isAble(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.isLongClickable();
+ }
+ })
+ .entry(AccessibilityNodeInfo.ACTION_FOCUS, new Able() {
+ @Override
+ public boolean isAble(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.isFocusable();
+ }
+ })
+ .entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, new Able() {
+ @Override
+ public boolean isAble(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.isScrollable();
+ }
+ })
+ .entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, new Able() {
+ @Override
+ public boolean isAble(AccessibilityNodeInfo nodeInfo) {
+ return nodeInfo.isScrollable();
+ }
+ })
+ .sparseArray();
+
+ boolean isAble(AccessibilityNodeInfo node);
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/Action.java b/autojs/src/main/java/autojs/runtime/action/Action.java
new file mode 100644
index 00000000..3a7d66cf
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/Action.java
@@ -0,0 +1,40 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public abstract class Action {
+
+ private boolean mPerformUtilSucceed = false;
+ private volatile Object mResult = false;
+
+ public Action(boolean performUtilSucceed) {
+ mPerformUtilSucceed = performUtilSucceed;
+ }
+
+ public Action() {
+ this(false);
+ }
+
+ public abstract boolean perform(AccessibilityNodeInfo root);
+
+ public boolean performUtilSucceed() {
+ return mPerformUtilSucceed;
+ }
+
+ public Action setPerformUtilSucceed(boolean performUtilSucceed) {
+ mPerformUtilSucceed = performUtilSucceed;
+ return this;
+ }
+
+ public Object getResult() {
+ return mResult;
+ }
+
+ public void setResult(Object result) {
+ mResult = result;
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/ActionFactory.java b/autojs/src/main/java/autojs/runtime/action/ActionFactory.java
new file mode 100644
index 00000000..6f49d263
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/ActionFactory.java
@@ -0,0 +1,64 @@
+package autojs.runtime.action;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.stardust.util.MapEntries;
+
+import java.util.Map;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public class ActionFactory {
+
+ private static Map searchUpAction = new MapEntries()
+ .entry(AccessibilityNodeInfo.ACTION_CLICK, null)
+ .entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
+ .entry(AccessibilityNodeInfo.ACTION_SELECT, null)
+ .entry(AccessibilityNodeInfo.ACTION_FOCUS, null)
+ .entry(AccessibilityNodeInfo.ACTION_SELECT, null)
+ .entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
+ .entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
+ .map();
+
+ public static Action createActionWithTextFilter(int action, String text, int index) {
+ if (searchUpAction.containsKey(action))
+ return new SearchUpTargetAction(action, new FilterAction.TextFilter(text, index));
+ else
+ return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text, index));
+ }
+
+ public static Action createActionWithBoundsFilter(int action, Rect rect) {
+ if (searchUpAction.containsKey(action))
+ return new SearchUpTargetAction(action, new FilterAction.BoundsFilter(rect));
+ else
+ return new DepthFirstSearchTargetAction(action, new FilterAction.BoundsFilter(rect));
+ }
+
+ public static Action createActionWithEditableFilter(int action, int index, final String text) {
+ return new SearchTargetAction(action, new FilterAction.EditableFilter(index)) {
+
+ @Override
+ protected void performAction(AccessibilityNodeInfo node) {
+ Bundle args = new Bundle();
+ args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
+ node.performAction(getAction(), args);
+ }
+ };
+ }
+
+ public static Action createScrollMaxAction(int action) {
+ return new ScrollMaxAction(action);
+ }
+
+ public static Action createScrollAction(int action, int i) {
+ return new ScrollAction(action, i);
+ }
+
+ public static Action createActionWithIdFilter(int action, String id) {
+ return new FilterAction.SimpleFilterAction(action, new FilterAction.IdFilter(id));
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/ActionPerformAccessibilityDelegate.java b/autojs/src/main/java/autojs/runtime/action/ActionPerformAccessibilityDelegate.java
new file mode 100644
index 00000000..e61497e7
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/ActionPerformAccessibilityDelegate.java
@@ -0,0 +1,75 @@
+package autojs.runtime.action;
+
+import android.accessibilityservice.AccessibilityService;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
+import com.stardust.view.accessibility.AccessibilityDelegate;
+
+
+/**
+ * Created by Stardust on 2017/1/21.
+ */
+
+public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate {
+
+ private static final String TAG = "ActionPerformDelegate";
+
+ public static final Action NO_ACTION = null;
+
+ private static final Object ACTION_LOCK = new Object();
+
+ private static Action action;
+
+ //private Executor mExecutor = Executors.newFixedThreadPool(5);
+
+ public static void setAction(Action action) {
+ synchronized (ACTION_LOCK) {
+ ActionPerformAccessibilityDelegate.action = action;
+ }
+ }
+
+ @Override
+ public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
+ synchronized (ACTION_LOCK) {
+ if (action == NO_ACTION)
+ return false;
+ AccessibilityNodeInfo root = service.getRootInActiveWindow();
+ if (root == null) {
+ Log.v(TAG, "root = null");
+ return false;
+ }
+ performAction(root, action);
+ return false;
+ }
+ }
+
+ private void performAction(final AccessibilityNodeInfo root, final Action action) {
+ new Runnable() {
+ @Override
+ public void run() {
+ Log.i(TAG, "perform action:" + action);
+ if (action.perform(root)) {
+ action.setResult(true);
+ onActionPerformed();
+ } else if (!action.performUtilSucceed()) {
+ action.setResult(false);
+ onActionPerformed();
+ }
+ }
+ }.run();
+ }
+
+
+ private void onActionPerformed() {
+ synchronized (ACTION_LOCK) {
+ action = NO_ACTION;
+ DroidRuntime.getRuntime().notifyActionPerformed();
+ }
+ }
+
+
+}
+
diff --git a/autojs/src/main/java/autojs/runtime/action/ActionTarget.java b/autojs/src/main/java/autojs/runtime/action/ActionTarget.java
new file mode 100644
index 00000000..233e5eb2
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/ActionTarget.java
@@ -0,0 +1,68 @@
+package autojs.runtime.action;
+
+import android.graphics.Rect;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public interface ActionTarget {
+
+ Action createAction(int action, Object... params);
+
+ class TextActionTarget implements ActionTarget {
+
+ String mText;
+ int mIndex;
+
+ public TextActionTarget(String text, int i) {
+ mText = text;
+ mIndex = i;
+ }
+
+ @Override
+ public Action createAction(int action, Object... params) {
+ return ActionFactory.createActionWithTextFilter(action, mText, mIndex);
+ }
+ }
+
+ class BoundsActionTarget implements ActionTarget {
+
+ Rect mBoundsInRect;
+
+ public BoundsActionTarget(Rect rect) {
+ mBoundsInRect = rect;
+ }
+
+ @Override
+ public Action createAction(int action, Object... params) {
+ return ActionFactory.createActionWithBoundsFilter(action, mBoundsInRect);
+ }
+ }
+
+ class EditableActionTarget implements ActionTarget {
+ private int mIndex;
+
+ public EditableActionTarget(int index) {
+ mIndex = index;
+ }
+
+ @Override
+ public Action createAction(int action, Object... params) {
+ return ActionFactory.createActionWithEditableFilter(action, mIndex, params[0].toString());
+ }
+ }
+
+ class IdActionTarget implements ActionTarget {
+ private String mId;
+
+ public IdActionTarget(String id) {
+ mId = id;
+ }
+
+ @Override
+ public Action createAction(int action, Object... params) {
+ return ActionFactory.createActionWithIdFilter(action, mId);
+ }
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/DepthFirstSearchTargetAction.java b/autojs/src/main/java/autojs/runtime/action/DepthFirstSearchTargetAction.java
new file mode 100644
index 00000000..eea6ff68
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/DepthFirstSearchTargetAction.java
@@ -0,0 +1,40 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public class DepthFirstSearchTargetAction extends SearchTargetAction {
+
+
+ private Able mAble;
+
+ public DepthFirstSearchTargetAction(int action, Filter filter) {
+ super(action, filter);
+ mAble = Able.ABLE_MAP.get(action);
+ }
+
+
+ @Override
+ public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo n) {
+ if (n == null)
+ return null;
+ if (mAble.isAble(n))
+ return n;
+ for (int i = 0; i < n.getChildCount(); i++) {
+ AccessibilityNodeInfo child = n.getChild(i);
+ if (child == null)
+ continue;
+ AccessibilityNodeInfo node = searchTarget(child);
+ if (node != null)
+ return node;
+ else
+ child.recycle();
+ }
+ return null;
+ }
+
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/FilterAction.java b/autojs/src/main/java/autojs/runtime/action/FilterAction.java
new file mode 100644
index 00000000..3d3a8b6a
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/FilterAction.java
@@ -0,0 +1,165 @@
+package autojs.runtime.action;
+
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public abstract class FilterAction extends Action {
+
+
+ public interface Filter {
+
+ List filter(AccessibilityNodeInfo root);
+ }
+
+ public static class TextFilter implements Filter {
+
+ String mText;
+ int mIndex;
+
+ public TextFilter(String text, int index) {
+ mText = text;
+ mIndex = index;
+ }
+
+ @Override
+ public List filter(AccessibilityNodeInfo root) {
+
+ List list = root.findAccessibilityNodeInfosByText(mText);
+ if (mIndex == -1)
+ return list;
+ if (mIndex >= list.size())
+ return Collections.EMPTY_LIST;
+ return Collections.singletonList(list.get(mIndex));
+ }
+ }
+
+ public static class BoundsFilter implements Filter {
+
+ Rect mBoundsInScreen;
+
+ public BoundsFilter(Rect boundsInScreen) {
+ mBoundsInScreen = boundsInScreen;
+ }
+
+ @Override
+ public List filter(AccessibilityNodeInfo root) {
+ return Collections.singletonList(findAccessibilityNodeInfosByBounds(root));
+ }
+
+ private AccessibilityNodeInfo findAccessibilityNodeInfosByBounds(AccessibilityNodeInfo root) {
+ if (root == null)
+ return null;
+ Rect rect = new Rect();
+ root.getBoundsInScreen(rect);
+ if (rect.equals(mBoundsInScreen)) {
+ return root;
+ }
+ for (int i = 0; i < root.getChildCount(); i++) {
+ AccessibilityNodeInfo child = root.getChild(i);
+ if (child == null)
+ continue;
+ AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(child);
+ if (nodeInfo != null)
+ return nodeInfo;
+ else
+ child.recycle();
+ }
+ return null;
+ }
+ }
+
+ public static class EditableFilter implements Filter {
+
+ private int mIndex;
+
+ public EditableFilter(int index) {
+ mIndex = index;
+ }
+
+ @Override
+ public List filter(AccessibilityNodeInfo root) {
+ List editableList = findEditable(root);
+ if (mIndex == -1)
+ return editableList;
+ if (mIndex >= editableList.size())
+ return Collections.EMPTY_LIST;
+ return Collections.singletonList(editableList.get(mIndex));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List findEditable(AccessibilityNodeInfo root) {
+ if (root == null) {
+ return Collections.EMPTY_LIST;
+ }
+ if (root.isEditable()) {
+ return Collections.singletonList(root);
+ }
+ List list = new LinkedList<>();
+ for (int i = 0; i < root.getChildCount(); i++) {
+ list.addAll(findEditable(root.getChild(i)));
+ }
+ return list;
+ }
+ }
+
+ public static class IdFilter implements Filter {
+
+ private final String mId;
+
+ public IdFilter(String id) {
+ this.mId = id;
+ }
+
+ @Override
+ public List filter(AccessibilityNodeInfo root) {
+ return root.findAccessibilityNodeInfosByViewId(mId);
+ }
+ }
+
+
+ private Filter mFilter;
+
+ public FilterAction(Filter filter) {
+ mFilter = filter;
+ }
+
+ public boolean perform(AccessibilityNodeInfo root) {
+ if (root == null)
+ return false;
+ return perform(mFilter.filter(root));
+ }
+
+ public abstract boolean perform(List nodes);
+
+ public static class SimpleFilterAction extends FilterAction {
+
+ private int mAction;
+
+ public SimpleFilterAction(int action, Filter filter) {
+ super(filter);
+ mAction = action;
+ }
+
+ @Override
+ public boolean perform(List nodes) {
+ if(nodes == null || nodes.isEmpty())
+ return false;
+ boolean succeed = true;
+ for(AccessibilityNodeInfo nodeInfo : nodes){
+ if(!nodeInfo.performAction(mAction)){
+ succeed = false;
+ }
+ }
+ return succeed;
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/GetTextAction.java b/autojs/src/main/java/autojs/runtime/action/GetTextAction.java
new file mode 100644
index 00000000..993b9eca
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/GetTextAction.java
@@ -0,0 +1,42 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/2/14.
+ */
+
+public class GetTextAction extends Action {
+
+
+ @Override
+ public boolean perform(AccessibilityNodeInfo root) {
+ List texts = new ArrayList<>();
+ getText(root, texts);
+ super.setResult(texts);
+ return true;
+ }
+
+ private void getText(AccessibilityNodeInfo nodeInfo, List texts) {
+ CharSequence text = nodeInfo.getText();
+ if (text != null && text.length() != 0) {
+ texts.add(text.toString());
+ }
+ for (int i = 0; i < nodeInfo.getChildCount(); i++) {
+ AccessibilityNodeInfo child = nodeInfo.getChild(i);
+ if (child != null) {
+ getText(child, texts);
+ child.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setResult(Object result) {
+ if (result instanceof List)
+ super.setResult(result);
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/ScrollAction.java b/autojs/src/main/java/autojs/runtime/action/ScrollAction.java
new file mode 100644
index 00000000..6cab8c62
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/ScrollAction.java
@@ -0,0 +1,57 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/2/12.
+ */
+public class ScrollAction extends Action {
+
+ private int mIndex, mAction;
+
+ public ScrollAction(int action, int i) {
+ mAction = action;
+ mIndex = i;
+ }
+
+ @Override
+ public boolean perform(AccessibilityNodeInfo root) {
+ List scrollableNodes = findScrollableNodes(root);
+ boolean result = mIndex < scrollableNodes.size() && scrollableNodes.get(mIndex).performAction(mAction);
+ recycle(scrollableNodes, root);
+ return result;
+ }
+
+ private void recycle(List list, AccessibilityNodeInfo root) {
+ for (AccessibilityNodeInfo nodeInfo : list) {
+ if (nodeInfo != root)
+ nodeInfo.recycle();
+ }
+ }
+
+ private List findScrollableNodes(AccessibilityNodeInfo root) {
+ List list = new ArrayList<>();
+ findScrollableNodes(root, list);
+ return list;
+ }
+
+ private static boolean findScrollableNodes(AccessibilityNodeInfo node, List list) {
+ if (node == null) {
+ return false;
+ }
+ if (node.isScrollable()) {
+ list.add(node);
+ }
+ for (int i = 0; i < node.getChildCount(); i++) {
+ AccessibilityNodeInfo child = node.getChild(i);
+ if (child == null)
+ continue;
+ if (!findScrollableNodes(child, list))
+ child.recycle();
+ }
+ return node.isScrollable();
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/ScrollMaxAction.java b/autojs/src/main/java/autojs/runtime/action/ScrollMaxAction.java
new file mode 100644
index 00000000..b0c9d28b
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/ScrollMaxAction.java
@@ -0,0 +1,70 @@
+package autojs.runtime.action;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public class ScrollMaxAction extends Action {
+
+ private static final String TAG = ScrollMaxAction.class.getSimpleName();
+ private int mScrollAction;
+ private AccessibilityNodeInfo mMaxScrollableNode;
+ private AccessibilityNodeInfo mRootNode;
+
+ public ScrollMaxAction(int scrollAction) {
+ mScrollAction = scrollAction;
+ }
+
+ @Override
+ public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
+ reset();
+ mRootNode = rootNodeInfo;
+ findMaxScrollableNodeInfo(rootNodeInfo);
+ boolean result = mMaxScrollableNode != null && mMaxScrollableNode.performAction(mScrollAction);
+ reset();
+ return result;
+ }
+
+ private void reset() {
+ if (mMaxScrollableNode != null && mMaxScrollableNode != mRootNode) {
+ mMaxScrollableNode.recycle();
+ }
+ mMaxScrollableNode = mRootNode = null;
+ }
+
+ private void findMaxScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
+ if (nodeInfo == null)
+ return;
+ if (nodeInfo.isScrollable()) {
+ if (mMaxScrollableNode == null) {
+ mMaxScrollableNode = nodeInfo;
+ } else if (getAreaInScreen(mMaxScrollableNode) < getAreaInScreen(nodeInfo)) {
+ if (mMaxScrollableNode != mRootNode)
+ mMaxScrollableNode.recycle();
+ mMaxScrollableNode = nodeInfo;
+ }
+ }
+ for (int i = 0; i < nodeInfo.getChildCount(); i++) {
+ AccessibilityNodeInfo child = nodeInfo.getChild(i);
+ if (child != null) {
+ findMaxScrollableNodeInfo(child);
+ if (mMaxScrollableNode != child) {
+ child.recycle();
+ }
+ }
+ }
+ }
+
+ private long getAreaInScreen(AccessibilityNodeInfo nodeInfo) {
+ Rect rect = new Rect();
+ nodeInfo.getBoundsInScreen(rect);
+ long area = ((long) rect.width()) * rect.height();
+ Log.v(TAG, "area=" + area);
+ return area;
+ }
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/SearchTargetAction.java b/autojs/src/main/java/autojs/runtime/action/SearchTargetAction.java
new file mode 100644
index 00000000..0f55ba06
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/SearchTargetAction.java
@@ -0,0 +1,47 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.List;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public abstract class SearchTargetAction extends FilterAction {
+
+ private int mAction;
+
+ public SearchTargetAction(int action, Filter filter) {
+ super(filter);
+ mAction = action;
+ }
+
+
+ @Override
+ public boolean perform(List nodes) {
+ boolean performed = false;
+ for (AccessibilityNodeInfo node : nodes) {
+ node = searchTarget(node);
+ if (node != null) {
+ performAction(node);
+ performed = true;
+ }
+ }
+ return performed;
+ }
+
+ protected void performAction(AccessibilityNodeInfo node) {
+ node.performAction(mAction);
+ }
+
+ public int getAction(){
+ return mAction;
+ }
+
+ public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo node) {
+ return node;
+ }
+
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/action/SearchUpTargetAction.java b/autojs/src/main/java/autojs/runtime/action/SearchUpTargetAction.java
new file mode 100644
index 00000000..e7712b74
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/action/SearchUpTargetAction.java
@@ -0,0 +1,30 @@
+package autojs.runtime.action;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public class SearchUpTargetAction extends SearchTargetAction {
+
+ private Able mAble;
+
+ public SearchUpTargetAction(int action, Filter filter) {
+ super(action, filter);
+ mAble = Able.ABLE_MAP.get(action);
+ }
+
+ @Override
+ public AccessibilityNodeInfo searchTarget(AccessibilityNodeInfo n) {
+ AccessibilityNodeInfo node = n;
+ while (node != null && !mAble.isAble(node)) {
+ AccessibilityNodeInfo parent = node.getParent();
+ if (node != n) {
+ node.recycle();
+ }
+ node = parent;
+ }
+ return node;
+ }
+}
diff --git a/autojs/src/main/java/autojs/runtime/api/InjectableWebClient.java b/autojs/src/main/java/autojs/runtime/api/InjectableWebClient.java
new file mode 100644
index 00000000..0782befb
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/api/InjectableWebClient.java
@@ -0,0 +1,140 @@
+package autojs.runtime.api;
+
+import android.annotation.SuppressLint;
+import android.util.Log;
+import android.util.Pair;
+import android.webkit.JavascriptInterface;
+import android.webkit.ValueCallback;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Created by Stardust on 2017/4/1.
+ */
+
+public class InjectableWebClient extends WebViewClient {
+
+ private static final String TAG = "InjectableWebClient";
+
+ private Queue>> mToInjectJavaScripts = new LinkedList<>();
+ private final ValueCallback defaultCallback = new ValueCallback() {
+ @Override
+ public void onReceiveValue(String value) {
+ Log.i(TAG, "onReceiveValue: " + value);
+ }
+ };
+ private WebView mWebView;
+ private Context mContext;
+ private Scriptable mScriptable;
+ private ScriptBridge mScriptBridge = new ScriptBridge();
+
+ public InjectableWebClient(Context context, Scriptable scriptable) {
+ mContext = context;
+ mScriptable = scriptable;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ mWebView = view;
+ setUpWebView(view);
+ while (!mToInjectJavaScripts.isEmpty()) {
+ Pair> pair = mToInjectJavaScripts.poll();
+ inject(view, pair.first, pair.second);
+ }
+ super.onPageFinished(view, url);
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private void setUpWebView(WebView view) {
+ view.addJavascriptInterface(mScriptBridge, "rhino");
+ WebSettings webSettings = view.getSettings();
+ webSettings.setJavaScriptEnabled(true);
+ webSettings.setAllowUniversalAccessFromFileURLs(true);
+ }
+
+ private void inject(WebView view, String script, ValueCallback callback) {
+ view.evaluateJavascript(script, callback);
+ }
+
+ public void inject(String script, ValueCallback callback) {
+ if (mWebView != null) {
+ inject(mWebView, script, callback);
+ return;
+ }
+ mToInjectJavaScripts.offer(new Pair<>(script, callback));
+ }
+
+ public void inject(String script) {
+ inject(script, defaultCallback);
+ }
+
+ public String injectAndWait(String script) {
+ InjectReturnCallback callback = new InjectReturnCallback();
+ inject(script, callback);
+ return callback.waitResult();
+ }
+
+
+ private class ScriptBridge {
+
+ private Object result;
+
+ @JavascriptInterface
+ public String eval(final String script) {
+ result = null;
+ mWebView.post(new Runnable() {
+ @Override
+ public void run() {
+ Log.v(TAG, "ScriptBridge.eval: " + script);
+ result = mContext.evaluateString(mScriptable, script, "", 1, null);
+ Log.v(TAG, "ScriptBridge.eval = " + result);
+ synchronized (ScriptBridge.this) {
+ ScriptBridge.this.notify();
+ }
+ }
+ });
+ synchronized (ScriptBridge.this) {
+ try {
+ ScriptBridge.this.wait();
+ } catch (InterruptedException e) {
+ throw new ScriptStopException(e);
+ }
+ }
+ return result.toString();
+ }
+ }
+
+ private static class InjectReturnCallback implements ValueCallback {
+
+ private String result;
+
+ @Override
+ public void onReceiveValue(String value) {
+ result = value;
+ synchronized (this) {
+ this.notify();
+ }
+ }
+
+ String waitResult() {
+ synchronized (this) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ throw new ScriptStopException(e);
+ }
+ }
+ return result;
+ }
+ }
+
+}
diff --git a/autojs/src/main/java/autojs/runtime/api/InjectableWebView.java b/autojs/src/main/java/autojs/runtime/api/InjectableWebView.java
new file mode 100644
index 00000000..929635a7
--- /dev/null
+++ b/autojs/src/main/java/autojs/runtime/api/InjectableWebView.java
@@ -0,0 +1,34 @@
+package autojs.runtime.api;
+
+import android.content.Context;
+import android.webkit.ValueCallback;
+import android.webkit.WebView;
+
+import org.mozilla.javascript.Scriptable;
+
+/**
+ * Created by Stardust on 2017/4/1.
+ */
+
+public class InjectableWebView extends WebView {
+
+ private InjectableWebClient mInjectableWebClient;
+
+ public InjectableWebView(Context context, org.mozilla.javascript.Context jsCtx, Scriptable scriptable) {
+ super(context);
+ init(jsCtx, scriptable);
+ }
+
+ private void init(org.mozilla.javascript.Context jsCtx, Scriptable scriptable) {
+ mInjectableWebClient = new InjectableWebClient(jsCtx, scriptable);
+ setWebViewClient(mInjectableWebClient);
+ }
+
+ public void inject(String script, ValueCallback callback) {
+ mInjectableWebClient.inject(script, callback);
+ }
+
+ public void inject(String script) {
+ mInjectableWebClient.inject(script);
+ }
+}
diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/Shell.java b/autojs/src/main/java/autojs/runtime/api/Shell.java
similarity index 96%
rename from app/src/main/java/com/stardust/scriptdroid/droid/runtime/Shell.java
rename to autojs/src/main/java/autojs/runtime/api/Shell.java
index 3e7bf331..63df7008 100644
--- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/Shell.java
+++ b/autojs/src/main/java/autojs/runtime/api/Shell.java
@@ -1,4 +1,4 @@
-package com.stardust.scriptdroid.droid.runtime;
+package autojs.runtime.api;
/**
* Created by Stardust on 2017/3/7.
diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java b/autojs/src/main/java/autojs/runtime/api/UiSelector.java
similarity index 99%
rename from app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java
rename to autojs/src/main/java/autojs/runtime/api/UiSelector.java
index 1eddfef6..8eed33cb 100644
--- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java
+++ b/autojs/src/main/java/autojs/runtime/api/UiSelector.java
@@ -1,4 +1,4 @@
-package com.stardust.scriptdroid.droid.runtime;
+package autojs.runtime.api;
import android.accessibilityservice.AccessibilityService;
import android.support.annotation.NonNull;
@@ -12,6 +12,7 @@ import com.stardust.automator.UiObject;
import com.stardust.automator.UiObjectCollection;
import com.stardust.automator.filter.DfsFilter;
import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider;
+import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ARGUMENT_COLUMN_INT;
diff --git a/autojs/src/main/java/autojs/script/JavaScriptEngine.java b/autojs/src/main/java/autojs/script/JavaScriptEngine.java
new file mode 100644
index 00000000..314acb9d
--- /dev/null
+++ b/autojs/src/main/java/autojs/script/JavaScriptEngine.java
@@ -0,0 +1,61 @@
+package autojs.script;
+
+import com.stardust.scriptdroid.App;
+import com.stardust.scriptdroid.BuildConfig;
+import com.stardust.scriptdroid.tool.FileUtils;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by Stardust on 2017/1/27.
+ */
+
+public abstract class JavaScriptEngine {
+
+ private static JavaScriptEngine defaultEngine;
+
+ public static JavaScriptEngine getDefault() {
+ return defaultEngine;
+ }
+
+ public static void setDefault(JavaScriptEngine engine) {
+ defaultEngine = engine;
+ }
+
+ Map mVariableMap = new HashMap<>();
+
+ public abstract Object execute(String script);
+
+ public void set(String varName, Object value) {
+ mVariableMap.put(varName, value);
+ }
+
+ public abstract void removeAndDestroy();
+
+ public abstract int stopAll();
+
+ public abstract String[] getGlobalFunctions();
+
+ static class Init {
+ private static final String INIT_SCRIPT = readInitScript();
+
+ private static String readInitScript() {
+ try {
+ return FileUtils.readString(App.getApp().getAssets().open("javasccript_engine_init.js"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static String getInitScript() {
+ if (BuildConfig.DEBUG) {
+ // 调试时不缓存INIT_SCRIPT否则修改javasccript_engine_init.js后不会更新
+ return readInitScript();
+ } else {
+ return INIT_SCRIPT;
+ }
+ }
+ }
+}
diff --git a/autojs/src/main/java/autojs/script/NodeJsJavaScriptEngine.java b/autojs/src/main/java/autojs/script/NodeJsJavaScriptEngine.java
new file mode 100644
index 00000000..5f635304
--- /dev/null
+++ b/autojs/src/main/java/autojs/script/NodeJsJavaScriptEngine.java
@@ -0,0 +1,59 @@
+package autojs.script;
+
+import com.iwebpp.node.NodeContext;
+import com.stardust.scriptdroid.App;
+import com.stardust.scriptdroid.droid.Droid;
+import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
+import com.stardust.scriptdroid.tool.FileUtils;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+
+import java.io.IOException;
+
+/**
+ * Created by Stardust on 2017/3/25.
+ */
+
+public class NodeJsJavaScriptEngine extends RhinoJavaScriptEngine {
+
+ public NodeJsJavaScriptEngine(DroidRuntime runtime) {
+ super(runtime);
+ }
+
+
+ @Override
+ public Object execute(String script) {
+ NodeContext nodectx = new NodeContext();
+ Context context = createContext();
+ Scriptable scope = createScope(context);
+ ScriptableObject.putProperty(scope, "NodeCurrentContext", Context.javaToJS(nodectx, scope));
+ init(context, scope);
+ Object result = context.evaluateString(scope, script, "