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"));
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/stardust/view/ViewBinder.java b/app/src/main/java/com/stardust/view/ViewBinder.java
index 2ac04a9d..e8887809 100644
--- a/app/src/main/java/com/stardust/view/ViewBinder.java
+++ b/app/src/main/java/com/stardust/view/ViewBinder.java
@@ -9,83 +9,114 @@ import java.lang.reflect.Method;
/**
* Created by Stardust on 2017/1/30.
- *
+ *
* 哈?你说我为什么不用AA框架?之前还不知道嘛所以现在用了。
*/
public class ViewBinder {
- public static void bind(Object o) {
- Method findViewById;
+ public interface ViewSupplier {
+ View findViewById(int id);
+ }
+
+
+ public static void bind(final Object o) {
+ final Method findViewById;
try {
findViewById = o.getClass().getMethod("findViewById", int.class);
findViewById.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException("You must implement findViewById to use view binding", e);
}
+ bind(o, new ViewSupplier() {
+ @Override
+ public View findViewById(int id) {
+ try {
+ return (View) findViewById.invoke(o, id);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ public static void bind(Object o, final View view) {
+ bind(o, new ViewSupplier() {
+ @Override
+ public View findViewById(int id) {
+ return view.findViewById(id);
+ }
+ });
+ }
+
+
+ public static void bind(Object o, ViewSupplier viewSupplier) {
Method[] methods = o.getClass().getDeclaredMethods();
- bindId(o, findViewById);
+ bindId(o, viewSupplier);
for (Method method : methods) {
method.setAccessible(true);
- bindClick(o, method, findViewById);
- bindCheck(o, method, findViewById);
+ bindClick(o, method, viewSupplier);
+ bindCheck(o, method, viewSupplier);
}
}
- private static void bindId(Object o, Method findViewById) {
+ public static void bindId(Object o, final View v) {
+ bindId(o, new ViewSupplier() {
+ @Override
+ public View findViewById(int id) {
+ return v.findViewById(id);
+ }
+ });
+ }
+
+ private static void bindId(Object o, ViewSupplier viewSupplier) {
for (Field field : o.getClass().getDeclaredFields()) {
field.setAccessible(true);
ViewBinding.Id id = field.getAnnotation(ViewBinding.Id.class);
if (id == null || id.value() == 0)
continue;
try {
- field.set(o, findViewById.invoke(o, id.value()));
- } catch (IllegalAccessException | InvocationTargetException e) {
+ field.set(o, viewSupplier.findViewById(id.value()));
+ } catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
- private static void bindCheck(final Object o, final Method method, Method findViewById) {
+ private static void bindCheck(final Object o, final Method method, ViewSupplier viewSupplier) {
ViewBinding.Check annotation = method.getAnnotation(ViewBinding.Check.class);
if (annotation == null || annotation.value() == 0)
return;
int id = annotation.value();
- try {
- CompoundButton button = (CompoundButton) findViewById.invoke(o, id);
- button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- try {
- method.invoke(o, isChecked);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
+ CompoundButton button = (CompoundButton) viewSupplier.findViewById(id);
+ button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ try {
+ method.invoke(o, isChecked);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
}
- });
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
+ }
+ });
}
- private static void bindClick(final Object o, final Method method, Method findViewById) {
+ private static void bindClick(final Object o, final Method method, ViewSupplier viewSupplier) {
ViewBinding.Click annotation = method.getAnnotation(ViewBinding.Click.class);
if (annotation == null || annotation.value() == 0)
return;
int id = annotation.value();
- try {
- View view = (View) findViewById.invoke(o, id);
- view.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- invokeMethod(o, method);
- }
- });
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
+ View view = viewSupplier.findViewById(id);
+ if (view == null)
+ return;
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ invokeMethod(o, method);
+ }
+ });
}
private static void invokeMethod(Object o, Method method) {
diff --git a/app/src/main/java/com/stardust/widget/LevelBeamView.java b/app/src/main/java/com/stardust/widget/LevelBeamView.java
index a18e739b..5f0f1bbb 100644
--- a/app/src/main/java/com/stardust/widget/LevelBeamView.java
+++ b/app/src/main/java/com/stardust/widget/LevelBeamView.java
@@ -78,7 +78,6 @@ public class LevelBeamView extends View {
@Override
protected void onDraw(Canvas canvas) {
- Log.i(TAG, "onDraw");
super.onDraw(canvas);
for (int lvl = 0; lvl <= mLevel; lvl++) {
float LINE_X = mPaddingLeft + lvl * mLinesWidth;
diff --git a/app/src/main/java/com/stardust/widget/OnItemClickListener.java b/app/src/main/java/com/stardust/widget/OnItemClickListener.java
new file mode 100644
index 00000000..15a3abdf
--- /dev/null
+++ b/app/src/main/java/com/stardust/widget/OnItemClickListener.java
@@ -0,0 +1,13 @@
+package com.stardust.widget;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * Created by Stardust on 2017/3/27.
+ */
+
+public interface OnItemClickListener {
+
+ void onItemClick(RecyclerView parent, View item, int position);
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_folder_black_24dp.png
new file mode 100644
index 00000000..1d25a2d1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 00000000..e9c288c9
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_folder_black_24dp.png
new file mode 100644
index 00000000..6e6b8705
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 00000000..d78c57ba
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_folder_black_24dp.png
new file mode 100644
index 00000000..49272b0f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 00000000..f208795f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_folder_black_24dp.png
new file mode 100644
index 00000000..5c4360af
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 00000000..5345ee3c
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_folder_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_folder_black_24dp.png
new file mode 100644
index 00000000..073d8533
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_folder_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png
new file mode 100644
index 00000000..d12d4956
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_ali_delete.png b/app/src/main/res/drawable/ic_ali_delete.png
new file mode 100644
index 00000000..e7ac00ae
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_delete.png differ
diff --git a/app/src/main/res/drawable/ic_ali_edit.png b/app/src/main/res/drawable/ic_ali_edit.png
new file mode 100644
index 00000000..13c4a9bd
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_edit.png differ
diff --git a/app/src/main/res/drawable/ic_ali_open.png b/app/src/main/res/drawable/ic_ali_open.png
new file mode 100644
index 00000000..8e700664
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_open.png differ
diff --git a/app/src/main/res/drawable/ic_ali_rename.png b/app/src/main/res/drawable/ic_ali_rename.png
new file mode 100644
index 00000000..8777c5c5
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_rename.png differ
diff --git a/app/src/main/res/drawable/ic_ali_run.png b/app/src/main/res/drawable/ic_ali_run.png
new file mode 100644
index 00000000..600819cd
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_run.png differ
diff --git a/app/src/main/res/drawable/ic_ali_shortcut.png b/app/src/main/res/drawable/ic_ali_shortcut.png
new file mode 100644
index 00000000..be72dd6e
Binary files /dev/null and b/app/src/main/res/drawable/ic_ali_shortcut.png differ
diff --git a/app/src/main/res/drawable/ic_folder_yellow_200px.png b/app/src/main/res/drawable/ic_folder_yellow_200px.png
new file mode 100644
index 00000000..81e0930e
Binary files /dev/null and b/app/src/main/res/drawable/ic_folder_yellow_200px.png differ
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 00000000..5c10f435
Binary files /dev/null and b/app/src/main/res/drawable/ic_new_directory.png differ
diff --git a/app/src/main/res/drawable/ic_node_js_black.xml b/app/src/main/res/drawable/ic_node_js_black.xml
new file mode 100644
index 00000000..e0e26d90
--- /dev/null
+++ b/app/src/main/res/drawable/ic_node_js_black.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
\ 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, "