diff --git a/.idea/statistic.xml b/.idea/statistic.xml new file mode 100644 index 00000000..67cee7cb --- /dev/null +++ b/.idea/statistic.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/Readme.md b/Readme.md index 9002bc85..5536a13f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,16 @@ # 免Root脚本机器人 -#简介 -一个**不需要**root权限的类似脚本精灵、按键精灵的自动操作的软件。可以实现自动点击、滑动、输入文字、打开应用等,例如QQ自动抢红包。当然了你也可以把它当做js的IDE来写JavaScript(作为IDE而言目前还比较弱,没有自动补全和包列表等)。另外由于不使用root权限,要点击屏幕上的精确位置是不能实现的,像游戏刷副本自动签到什么的基本上不能做到,这些还是用root权限的脚本精灵吧~~ +## 简介 +一个**不需要Root权限**的类似按键精灵的自动操作软件,可以实现自动点击、滑动、输入文字、打开应用等。 +> 注意:这里的不需要Root权限指的是一般软件,**游戏的自动点击等是需要Root权限的**。 + +特性: +* 简单易用的自动操作函数 +* 悬浮窗录制和运行 +* 丰富的文档、教程与示例 +* 更专业&强大的选择器API,提供对屏幕上的控件的寻找、遍历、获取信息、操作等。类似于Google的UI测试框架UiAutomator,您也可以把他当做移动版UI测试框架使用 +* 采用JavaScript为脚本语言,支持简单的代码补全。您也可以把他当作简便的JavaScript IDE使用 +* 带有界面分析工具,类似Android Studio的LayoutInspector,可以分析界面层次和范围、获取界面上的控件信息 +* 支持使用Root权限以提供更强大的屏幕点击、滑动、录制功能和运行shell命令 ![screen-capture1](https://raw.githubusercontent.com/hyb1996/NoRootScriptDroid/master/screen-captures/ss1.png) @@ -10,68 +20,9 @@ ![screen-capture4](https://raw.githubusercontent.com/hyb1996/NoRootScriptDroid/master/screen-captures/ss4.png) -#基本语法与API -目前支持的自动操作函数还比较少,未来会逐渐增加。 -###一、语法 -本软件使用JavaScript语言([ECMAscript E5/E5.1](http://www.ecma-international.org/ecma-262/5.1/)),基于[Duktape](http://www.duktape.org/)引擎拓展一些自动操作(点击、长按、滑动等)函数。因而语法参见JavaScript(例如[w3cschool教程](http://www.w3school.com.cn/js/))。 +![screen-capture5](https://raw.githubusercontent.com/hyb1996/NoRootScriptDroid/master/screen-captures/ss5.png) -###二、自动操作函数 -**自动操作的函数都需要开启"自动操作服务"才能执行,否则执行到相应函数时脚本会停止运行。** -* `click(text)` 点击文本text所在的区域,并返回是否点击成功。当前界面没有出现该文本或者该文本所在区域不可点击时返回false。例如`click("发现")`,是点击"发现"。如果要点击"发现"直至点击成功可以用`while(!click("发现"))`。 - -> 文本所在区域指的是,从文本处向其父视图寻找,直至发现一个可点击的部件为止。 - -* `click(left, top, bottom, right)` - -> 有些按钮或者部件是图标而不是文字(例如发送朋友圈的照相机图标以及QQ下方的消息、联系人、动态图标),这时不能通过`click(text)`来点击,只能通过描述图标所在的区域来点击。left, bottom, top, right描述的就是点击的区域。至于要定位点击的区域,可以在侧拉菜单开启"点击区域辅助"(或者安卓7.0以上在通知栏点击"修改"添加点击区域辅助快捷设定图标),之后每次点击或长按都会提示这次点击或长按的区域并自动保存,可以在编辑器的右侧拉菜单中插入。 - - 点击与长方形范围严格匹配的区域,并返回是否点击成功。其中left为长方形左边与屏幕左边的像素距离,top为上边与屏幕上边的像素距离,right为右边与**屏幕左边**的像素距离, bottom为下边与**屏幕上边**的距离。区域严格匹配。 - -> 以下的longClick、select、scrollUp、scrollDown的参数均与click类似,不再赘述。 - -* `longClick` 长按 -* `select` 选择 -* `scrollUp` 上滑。不加参数时会寻找"最大"的可滑动的控件上滑,例如微信消息列表等。 -* `scrollDown` 下滑。不加参数时与scrollUp类似。 -* `input(text)` 把所有输入框的文本都置为text。例如input("测试")。 -* `input(i, text)` 把第i个输入框的文本设为text。i从0开始。 - -###三、其他函数 -* `sleep(n)` 暂停执行n**毫秒**时间。 -* `toast(text)` 显示提示文本。 -* `launch(packageName)` 运行包名为packageName的应用。例如launch("com.tencent.mm")是运行微信,应用包名可以通过一些工具获取;获取通过函数launchApp代替 -* `launch(packageName, className)` 运行包名为packageName,类名(Activity)为className的应用。 -* `launchApp(appName)` 运存应用名称为appName的应用。例如launchApp("QQ")。不同应用名称可能相同,这时只运行其中某一个应用。 -* `notStopped` 若当前脚本处于运行状态时返回`true`, 否则返回`false`。对于某些循环, 例如`while(true)`,请用`while(notStopped())`代替,以免死循环造成的脚本无法正常停止。 -* `isStoppd` 若当前脚本处于停止状态时返回`true`, 否则返回`false`。 - -###四、全局变量 -* `context` ApplicationContext,参见安卓[android.content.Context](https://developer.android.com/reference/android/content/Context.html) - -> 这里的context由于是ApplicationContext,是不可见的,不能用于dialog和其他UI相关。如果要显示弹窗或者视图,请启动UI模式(代码的第一行为`"ui";`既可)并使用activity代替。 - -* `activity` UI模式下会启动一个Activity来运行脚本,并在UI线程下运行。可通过该变量来获取该activity。 - -###五、在脚本中调用Java -使用importClass来引入要使用的库,例如: -```javascript -importClass("android.view.View.OnClickListener") -view.setOnClickListener(new OnClickListener(function(){ - Toast.makeText(activity, "Button1 Clicked", Toast.LENGTH_SHORT).show(); - var intent = new Intent(activity, "com.furture.react.activity.DetailActivity"); - activity.startActivity(intent); -})); - -view2.setOnClickListener(new OnClickListener({ - onClick: function(){ - Toast.makeText(activity, "Button2 Clicked", Toast.LENGTH_SHORT).show(); - } -})); -``` - -详细文档参见[DuktapeJava Java Docs](http://gubaojian.github.io/DuktapeJava/javadoc/)。 - -#License +## License [CC-BY-NC-SA](https://github.com/hyb1996/NoRootScriptDroid/blob/master/LICENSE.md) * 署名 — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. * **非商业性使用** — 您不得将本作品用于商业与盈利目的。 diff --git a/app-release-2.0.0 Beta.apk b/app-release-2.0.0 Beta.apk new file mode 100644 index 00000000..07132001 Binary files /dev/null and b/app-release-2.0.0 Beta.apk differ diff --git a/app-release-2.0.0内测.apk b/app-release-2.0.0内测.apk new file mode 100644 index 00000000..7881edff Binary files /dev/null and b/app-release-2.0.0内测.apk differ diff --git a/app-release-2.0.1 Beta.apk b/app-release-2.0.1 Beta.apk new file mode 100644 index 00000000..b485f679 Binary files /dev/null and b/app-release-2.0.1 Beta.apk differ diff --git a/app/build.gradle b/app/build.gradle index 7cefafed..8f3322ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +def AAVersion = '4.2.0' android { compileSdkVersion 25 @@ -7,8 +8,8 @@ android { applicationId "com.stardust.scriptdroid" minSdkVersion 19 targetSdkVersion 23 - versionCode 41 - versionName "1.17.0314内测" + versionCode 103 + versionName "2.0.1 Beta" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true @@ -51,6 +52,8 @@ dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + annotationProcessor "org.androidannotations:androidannotations:$AAVersion" + compile "org.androidannotations:androidannotations-api:$AAVersion" compile 'com.android.support:appcompat-v7:25.1.0' compile 'com.android.support:design:25.1.0' compile 'com.github.hyb1996:MutableTheme:0.2.2' @@ -67,7 +70,6 @@ dependencies { compile 'io.mattcarroll.hover:hover:0.9.7' compile 'org.greenrobot:eventbus:3.0.0' compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' - compile(name: 'libtermexec-release', ext: 'aar') compile(name: 'emulatorview-release', ext: 'aar') compile(name: 'term-debug', ext: 'aar') @@ -80,8 +82,4 @@ dependencies { compile(name: '920-file_explorer-release', ext: 'aar') compile(name: '920-app-debug', ext: 'aar') compile project(':automator') -} - -configurations { - all*.exclude group: 'org.mozilla', module: 'rhino' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7c5b804..f7586b98 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,8 +78,7 @@ android:theme="@style/IssueReportTheme"/> - - + diff --git a/app/src/main/assets/help/catalogue.json b/app/src/main/assets/help/catalogue.json index bbe379db..55a9d639 100644 --- a/app/src/main/assets/help/catalogue.json +++ b/app/src/main/assets/help/catalogue.json @@ -53,6 +53,11 @@ "title": "需要Root权限的自动操作函数", "type": "markdown", "path":"documentation" + }, + { + "title": "调用Java API", + "type": "markdown", + "path":"documentation" } ], "EMCAScript教程": [ diff --git a/app/src/main/assets/help/documentation/shell命令.md b/app/src/main/assets/help/documentation/shell命令.md index 1e4de15b..49bcc133 100644 --- a/app/src/main/assets/help/documentation/shell命令.md +++ b/app/src/main/assets/help/documentation/shell命令.md @@ -27,6 +27,7 @@ if(result.code == 1){ * [应用包名](#应用包名) * [pm命令](#pm命令) * [其他命令](#其他命令) +* [Shell类](#Shell类) ## am命令 @@ -356,4 +357,41 @@ ls filepath 例如: ``` log(shell("ls /system/bin")); -``` \ No newline at end of file +``` + +## Shell类 + +当需要连续执行很多命令时,每条命令都需要申请Root权限,执行时间会大大增加。这时可以使用Shell类。 +### new Shell(root = false) +* root \ 是否以root权限执行。默认为false。 + +Shell类的构造函数。 + +### Shell.execute(cmd) +* cmd \ 要执行的命令 + +执行命令cmd,例如: +``` +var sh = Shell(true); +sh.execute("rm /sdcard/1.txt"); //删除SD卡上的1.txt文件 +``` + +### Shell.exitAndWaitFor() +退出Shell,并等待所有命令执行完成。 +一个完整的例子如下: +``` +var sh = Shell(true); +sh.execute("rm /sdcard/1.txt"); +sh.execute("input keyevent 26"); +sh.exitAndWaitFor(); +``` + +### Shell.waitFor() +等待当前所有命令执行完成。返回一个整数作为返回码,1为执行成功,其他数字为错误。 + +### Shell.exit() +退出Shell。调用该函数后再执行的命令无效。 + + + +除以上命令之外, Shell也包含自动操作的命令,例如Shell.Tap,参见《需要Root权限的自动操作函数》。 \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/一般常用函数.md b/app/src/main/assets/help/documentation/一般常用函数.md index c8502969..05827e69 100644 --- a/app/src/main/assets/help/documentation/一般常用函数.md +++ b/app/src/main/assets/help/documentation/一般常用函数.md @@ -20,10 +20,10 @@ launchPackage("com.tencent.mm"); launchApp("微信"); ``` -### getPackageName() +### currentPackage() 返回最近一次监测到的正在运行的应用的包名,一般可以认为就是当前正在运行的应用的包名。 -### getActivityName() +### currentActivity() 返回最近一次监测到的正在运行的Activity的名称,一般可以认为就是当前正在运行的Activity的名称。 ### setClip(text) diff --git a/app/src/main/assets/help/documentation/常见问题.md b/app/src/main/assets/help/documentation/常见问题.md index fcb6d8cf..1612c1bb 100644 --- a/app/src/main/assets/help/documentation/常见问题.md +++ b/app/src/main/assets/help/documentation/常见问题.md @@ -1,16 +1,21 @@ 目录 * [没有root权限是否支持游戏](#没有root权限是否支持游戏) * [如何重复做某些操作n次](#如何重复做某些操作n次) +* [如何循环做某些操作](#如何循环做某些操作) * [某些有文字的按键点击无效怎么办](#某些有文字的按键点击无效怎么办) * [如何点击图标按钮](#如何点击图标按钮) +* [如何支持Tasker调用](#如何支持Tasker调用) ### 没有root权限是否支持游戏 + +**游戏需要root权限!!!** +**游戏需要root权限!!!** +**游戏需要root权限!!!** 100%的游戏在没有root权限的情况下都不支持。 -**游戏需要root权限!!!** -**游戏需要root权限!!!** -**游戏需要root权限!!!** +不管是录制脚本还是运行脚本,在没有root权限的情况下都无法做到,在游戏界面不使用root权限录制脚本录制出来的是空白的。 ### 如何重复做某些操作n次 + 使用循环语句实现实现。例如每个1秒执行一个操作,总共执行20次的代码如下: ``` for(var i = 1; i <= 20; i++){ @@ -19,10 +24,27 @@ for(var i = 1; i <= 20; i++){ } ``` +### 如何循环做某些操作 + +使用while(true)语句实现。例如QQ无限发送消息的脚本如下: +``` +while(true){ + input("测试"); + click("发送"); +} +``` + ### 某些有文字的按键点击无效怎么办 + 例如QQ下方的"联系人"按钮,用click("联系人")是无效的。这是因为这些按钮上的文字并不是真正的文字,而是一个图标。参见[如何点击图标按钮](#如何点击图标按钮)。 ### 如何点击图标按钮 + 有以下几种方法: * 录制脚本 * 通过图标在屏幕上的位置来点击。要获取图标在屏幕上的位置,可以开启悬浮窗,选择布局范围查看,之后点击要点击的图标,在弹出的窗口中的"bounds"一栏即图标在屏幕上的区域。例如: +* 通过id, desc等选择器来定位图片 + +### 如何支持Tasker调用 + +新建任务时选择文件,点击浏览找到要运行的脚本文件,在MimeType处填入application/x-javascript。 \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/控制台与输出.md b/app/src/main/assets/help/documentation/控制台与输出.md index 70597e8c..b437681c 100644 --- a/app/src/main/assets/help/documentation/控制台与输出.md +++ b/app/src/main/assets/help/documentation/控制台与输出.md @@ -8,6 +8,9 @@ ### log(text) * text \ | \ 要打印到控制台的信息 +### print(text) +等同于log。 + 在控制台中输出日志,例如`log("Hello world");`。当text是一个对象时则会转换String以后再输出。 ### err(error) diff --git a/app/src/main/assets/help/documentation/调用Java API.md b/app/src/main/assets/help/documentation/调用Java API.md new file mode 100644 index 00000000..9b73780d --- /dev/null +++ b/app/src/main/assets/help/documentation/调用Java API.md @@ -0,0 +1,162 @@ +以下内容来自[Rhino:Scripting Java](https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino/Scripting_Java)。 + +这个章节描述了如何在rhino中使用java。使用脚本调用Java有很多用途,它使得我们可以利用Java中现有的库,来帮助我们构建强大的脚本。我们可以通过编写脚本,来对Java程序进行测试。可以通过脚本来进行探索式编程,辅助Java的开发,所谓探索式编程,就是通过快速地编程调用库或API来探索这些库或API可以做什么,显而易见,脚本语言很适合探索式编程。 + +这里注意,ECMA标准并没有包含和Java(或者其他任何对象系统)交互的标准。本文所描述的所有内容,应该被认为是一个扩展。 + +目录: +* [访问Java的包和类](#访问Java的包和类) +* [使用Java的类](#使用Java的类) +* [实现Java接口](#实现Java接口) +* [创建Java数组](#创建Java数组) +* [Java字符串与JavaScript字符串](#Java字符串与JavaScript字符串) +* [Java异常](#Java异常) +* [JavaImporter](#JavaImporter) + +## 访问Java的包和类 + +Java的每段代码都是类的一部分,每一个JAVA类都是包的一部分。在Javascript中,脚本不属于任何package。我们可以访问Java包中的类么? + +Rhino定义了一个顶层的变量Packages。Packages的所有属性都是Java中顶层的包,比如java和com。比如我们可以访问java包: +``` +Packages.java.util.Vector +``` +还有一种更方便的方式,Rhino定义了一个顶层的变量java,等价于Packages.java。所以上面的例子可以更简介地写成: +``` +java.util.Vector +``` +如果你的脚本需要访问很多的Java类,每次都附带完整的包名会使得编程很麻烦。Rhino提供了一个顶层的方法importPackage,它的功能和Java的import一样。比如,我们可以导入java.io包中的所有类,然后直接通过类名File来访问java.io.File: +``` +importPackage(java.io) +``` +这里importPackage(java.io)使得java.io包中的所有类(例如File)可以在顶层被访问。这和Java中的java.io.*;等价。 + +要注意Java会默认导入java.lang.*,但是Rhino不会。因为JavaScript的顶层对象Boolean、Math、Number、Object和String和java.lang包中同名的类并不相同。因为这种冲突,建议不要用importPackage来导入java.lang包。 + +有一点要注意的,就是Rhino对于指定包名或类名时是如何处理错误的。如果java.Myclass是可访问的,Rhino会试图加载名为java.MyClass的类,如果加载失败,它会假设java.MyClass是一个包名,不会报错。只有在你试图将这个对象当作类使用时,才会报错。 + +## 使用Java的类 + +现在我们可以访问Java类,下一步就是要创建一个对象。方法就和在Java中一样, 用new来创建对象: +``` +new java.util.Date(); +``` +如果我们将创建的对象存放在JavaScript变量中,我们可以调用它的方法: +``` +var f = new java.io.File("test.text"); +log(f.exists()); +log(f.getName()); +``` +静态方法和属性可以直接通过类对象来访问: +``` +log(java.lang.Math.PI); +``` +不像Java,在JavaScript里,方法就是一个对象。它可以被评估,也可以被调用。如果我们去查看这个方法,我们可以看到这个方法所有重载的形式: +``` +log(f.listFiles); +``` +输出: +``` +function listFiles() {/* +java.io.File[] listFiles() +java.io.File[] listFiles(java.io.FilenameFilter) +java.io.File[] listFiles(java.io.FileFilter) +*/} +``` +输出告诉我们,File类有listFiles方法的三种重载:一种不包含参数的,另一种包含一个FilenameFilter类型的参数,第三个包含一个FileFilter类型的参数。所有的方法都返回一个File对象数组。可以观察到Java方法的参数和返回类型在探索式编程中是非常有用的,尤其是在对一个方法的参数和返回对象不确定的时候。 +另一个有助于探索式编程的特性,是可以看到对象中定义的所有方法和属性。用JavaScript的for..in , 我们可以打印这些值: +``` +for (i in f) { log(i) } +``` +注意这里不仅列出了File类中的所有方法,也列出了从基类java.lang.Object中继承的方法,例如wait。这使得我们可以更好地处理那些有复杂继承关系的对象,因为我们可以看到对象中所有可用的方法。 + +Rhino可以通过属性名来方便地访问JavaBean的属性。一个JavaBean的属性foo被方法getFoo和setFoo定义,另外,一个也叫foo的boolean类型的属性,可以被isFoo来定义。比如, 下面的代码实际上调用了File对象的getName和isDirectory方法。 + +## 实现Java接口 + +``` + obj = { run: function () { log("\nrunning"); } }; + r = new java.lang.Runnable(obj); + t = new java.lang.Thread(r); + t.start(); +``` +若一个接口只有一个方法,也可以用直接用函数代替: +``` +t = java.lang.Thread(function () { print("\nrunning"); }); +t.start(); +``` + +## 创建Java数组、 + +Rhino没有提供特定的语法创建Java的数组,只能通过反射来创建。 +``` +a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2); +b = java.lang.reflect.Array.newInstance(java.lang.String, 5); +``` +之后可以像JavaScript数组一样使用 +``` +a[0] = 'a'; +a[1] = 'b'; +``` + +## Java字符串与JavaScript字符串 + +Java字符串与JavaScript字符串并不等同,但在某种程度上两者可以通用。例如,你可以传递一个JavaScript的字符串给一个需要Java字符串参数的函数。 +``` + javaString = new java.lang.String("Java"); + jsString = "JavaScript"; + log(javaString.length()); + log(jsString.length); +``` +也可以对Java字符串调用了一个在java.lang.String中没有定义的JavaScript方法,例如: +``` +log(javaString.match(/a.*/)); +``` + +## Java异常 + +例如: +``` +try { + java.lang.Class.forName("NonExistingClass"); +} catch (e) { + if (e.javaException instanceof java.lang.ClassNotFoundException) { + print("Class not found"); + } +} +``` +或者 +``` +function classForName(name) { + try { + return java.lang.Class.forName(name); + } catch (e if e.javaException instanceof java.lang.ClassNotFoundException) { + print("Class " + name + " not found"); + } catch (e if e.javaException instanceof java.lang.NullPointerException) { + print("Class name is null"); + } +} + +classForName("NonExistingClass"); +classForName(null); +``` + +## JavaImporter + +``` +var SwingGui = JavaImporter(Packages.javax.swing, + Packages.javax.swing.event, + Packages.javax.swing.border, + java.awt.event, + java.awt.Point, + java.awt.Rectangle, + java.awt.Dimension); +... + +with (SwingGui) { + var mybutton = new JButton(test); + var mypoint = new Point(10, 10); + var myframe = new JFrame(); +... +} +``` \ No newline at end of file diff --git a/app/src/main/assets/help/documentation/选择器.md b/app/src/main/assets/help/documentation/选择器.md index afe4d609..c86087fd 100644 --- a/app/src/main/assets/help/documentation/选择器.md +++ b/app/src/main/assets/help/documentation/选择器.md @@ -1,12 +1,15 @@ 目录: * [简介](#简介) * [选择器](#选择器) + * [筛选条件](#筛选条件) + * [搜索](#搜索) + * [操作](#操作) * [控件](#控件) * [控件集合](#控件集合) ## 简介 -选择器提供了更强大的定位界面控件与对齐进行操作的功能,可以用他们完成更多的自动操作。 +选择器提供了更强大的定位界面控件与对其进行操作的功能,可以用他们完成更多的自动操作和提取界面信息。 选择器通过附加筛选条件,筛选出符合条件的控件或控件集合,之后可以对这些控件这些操作。 可供选择的筛选条件包括: * id @@ -17,21 +20,21 @@ * bounds 在屏幕上的范围 * drawingOrder 绘制顺序 * 各种属性,主要包括: - * clickable - * longClickable - * checkable - * checked - * scrollable - * selected - * editable - * visibleToUser - * enabled + * clickable 可点击 + * longClickable 可长按 + * checkable 可勾选 + * checked 被勾选 + * scrollable 可滑动 + * selected 被选择 + * editable 可编辑 + * visibleToUser 可见 + * enabled 已启用 -要对选择器所确定的筛选条件,对屏幕上的控件进行筛选,调用方法find(), findOne(), untilFind(), untilFind()。其中find(), untilFind()返回控件的集合,findOne(), untilFindOne()返回一个控件。until***函数会一直寻找知道屏幕上出现满足条件的控件为止。 +要对选择器所确定的筛选条件,对屏幕上的控件进行筛选,调用方法find(), findOne(), untilFind()。其中find(), untilFind()返回控件的集合,findOne()返回一个控件。untilFind和FindOne函数会一直寻找直到屏幕上出现满足条件的控件为止。 一个完整的选择器示例如下: ``` var emoj = id("name").packageName("com.tencent.mobileqq").clickable() - .className("ImageView").drawingOrder(5).untilFindOne(); + .className("ImageView").drawingOrder(5).findOne(); emoj.click(); ``` 上述代码是找到QQ聊天界面下面的表情按钮(第5个按钮)并点击。也可以简化为: @@ -42,9 +45,11 @@ id("name").packageName("com.tencent.mobileqq").clickable() 最后的click()函数相当于untilFind().click()。也就是如果只需要在找到控件以后立即进行操作,只需要调用相应的操作函数,而不需要先调用find等函数等到控件再操作。 要获取屏幕上的控件的信息(例如id, text, desc, bounds等),可以开启悬浮窗,使用界面层次查看和界面范围查看。 -以下是具体的文档。 +因为选择器部分内容如果有安卓开发经验会更容易掌握,因而更适合UI测试。 -## 选择器 +# 选择器 + +## 筛选条件 ### id(resId) * resId \ 控件的id,以"包名:id/"开头,例如"com.tencent.mm:id/send_btn"。也可以不指定包名,这时会以当前正在运行的应用的包名来补全id。例如id("send_btn"),在QQ界面想当于id("com.tencent.mobileqq:id/send_btn")。 @@ -56,6 +61,8 @@ id("name").packageName("com.tencent.mobileqq").clickable() 附加控件id包含字符串str的筛选条件。 +> 控件的id属性通常是可以用来确定控件的唯一标识,如果一个控件有id,那么使用id来找到他是最好的方法。要查看屏幕上的控件的id,可以开启悬浮窗并使用界面工具,点击相应控件即可查看。若查看到的控件id为null, 表示该控件没有id。另外,在列表中会出现多个控件的id相同的情况。例如微信的联系人列表,每个头像的id都是一样的。此时不能用id来唯一确定控件。 + ### idStartsWith(prefix) * prefix \ id前缀 @@ -72,127 +79,358 @@ id("name").packageName("com.tencent.mobileqq").clickable() 附加id需要满足正则表达式。有关正则表达式,可以查看[菜鸟教程](http://www.runoob.com/regexp/regexp-example.html)。 ### text(str) +* str \ 控件文本 + +附加控件文本等于字符串str的筛选条件。 + +> 控件的text(文本)属性是控件上的显示的文字,例如微信左上角的"微信"文本。对于图片或者其他控件通常text属性为null。 ### textContains(str) +* str \ 要包含的字符串 + +附加控件文本需要以prefix开头的筛选条件。 + ### textStartsWith(prefix) +* prefix \ 前缀 + +附加控件文本需要以prefix开头的筛选条件。 + ### textEndsWith(suffix) +* suffix \ 后缀 + +附加控件文本需要以suffix结束的筛选条件。 ### textMatches(str) +* regex \ 要满足的正则表达式 -### desc(desc) +附加控件文本需要满足正则表达式的条件。 + +### desc(str) +* str \ 控件描述 + +附加控件描述等于字符串str的筛选条件。 + +> 控件的desc(描述,全称为Content-Description)属性是对一个控件的描述,例如网易云音乐右上角的放大镜图标的描述为搜索。要查看一个控件的描述,同样地可以借助悬浮窗查看。 ### descContains(str) +* str \ 要包含的字符串 + +附加控件描述需要以prefix开头的筛选条件。 + ### descStartsWith(prefix) +* prefix \ 前缀 + +附加控件描述需要以prefix开头的筛选条件。 + ### descEndsWith(suffix) +* suffix \ 后缀 + +附加控件描述需要以suffix结束的筛选条件。 ### descMatches(str) +* regex \ 要满足的正则表达式 -### className(className) +附加控件描述需要满足正则表达式的条件。 + +### className(str) +* str \ 控件类名 + +附加控件类名等于字符串str的筛选条件。 + +> 控件的className属性是一个控件的具体类型,例如图片控件通常为ImageView,文本控件通常为TextView, 输入框通常为EditText。(若一个控件是在android.widget包里的,那么android.widget可以省略。但除此之外的其他控件必须是类的全名,例如"com.stardust.theme.ThemeColorImageView")。 ### classNameContains(str) +* str \ 要包含的字符串 + +附加控件类名需要以prefix开头的筛选条件。 + ### classNameStartsWith(prefix) +* prefix \ 前缀 + +附加控件类名需要以prefix开头的筛选条件。 + ### classNameEndsWith(suffix) +* suffix \ 后缀 + +附加控件类名需要以suffix结束的筛选条件。 ### classNameMatches(str) +* regex \ 要满足的正则表达式 -### packageName(packageName) +附加控件类名需要满足正则表达式的条件。 + +### packageName(str) +* str \ 包名 + +附加包名等于字符串str的筛选条件。 + +> packageName属性也即控件所属的应用的包名(参见《shell命令:应用包名》),通常用来保证当前运行的应用是想要的应用。 ### packageNameContains(str) +* str \ 要包含的字符串 + +附加包名需要以prefix开头的筛选条件。 + ### packageNameStartsWith(prefix) +* prefix \ 前缀 + +附加包名需要以prefix开头的筛选条件。 + ### packageNameEndsWith(suffix) +* suffix \ 后缀 + +附加包名需要以suffix结束的筛选条件。 ### packageNameMatches(str) +* regex \ 要满足的正则表达式 + +附加包名需要满足正则表达式的条件。 ### bounds(l, t, r, b) +* l, t, r, b 用来定位控件在屏幕上的范围的四个整数 + ### boundsInside(l, t, r, b) ### boundsContains(l, t, r, b) ### drawingOrder(order) +* order \ 控件在父视图中的绘制顺序,在一些 ### checkable(b = true) +* b \ 表示控件是否可勾选, 默认为true + +附加控件是否可勾选的条件。可勾选指的是,例如QQ和微信发送图片时图片的勾选。 ### selected(b = true) - +* b \ 表示控件是否被选中,默认为true + +附加控件是否被选中的条件。被选中指的是,例如QQ聊天界面点击下方的表情按钮时,会出现自己收藏的表情,这时表情按钮便处于选中状态。 + ### clickable(b = true) +* b \ 表示控件是否可点击, 默认为true + +附加控件是否可点击的条件。但并非所有clickable为false的控件都真的不能点击,这取决于控件的实现。因而要确定一个控件的clickable属性,可以开启悬浮窗工具。 ### longClickable(b = true) +* b \ 表示控件是否可长按, 默认为true + +附加控件是否可长按的条件。 ### enabled(b = true) +* b \ 表示控件是否已启用, 默认为true -### password(b = true) +附加控件是否已启用的条件。大多数控件都是启用的状态,处于“禁用”状态通常是灰色并且不可点击。 ### scrollable(b = true) +* b \ 表示控件是否可滑动, 默认为true + +附加控件是否可滑动的条件。 ### editable(b = true) +* b \ 表示控件是否可编辑, 默认为true + +附加控件是否可编辑的条件。一般来说可编辑的控件为输入框(EditText)。 ### contentInvalid(b = true) +* b \ 表示控件是否是contentInvalid, 默认为true + +我也不知道是什么,下次再补充吧。 ### contextClickable(b = true) +* b \ 表示控件是否是contextClickable, 默认为true + +我也不知道是什么,下次再补充吧。 ### multiLine(b = true) +* b \ 表示文本或输入框控件是否是多行显示的, 默认为true -### dismissable(b = true) +附加控件是否文本或输入框控件是否是多行显示的条件。 + +## 搜索 + +### findOne() +根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到屏幕上出现满足条件的一个控件为止,并返回该控件。如果找不到控件,当屏幕内容发生变化时会重新寻找,直至找到。参见[控件](#控件)。 + +### find() +根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,找到所有满足条件的控件集合并返回。这个搜索只进行一次,并不保证一直会找到,因而会出现返回的控件集合为空的情况。参见[控件集合](#控件集合)。 + +### untilFind() +根据当前的选择器所确定的筛选条件,对屏幕上的控件进行搜索,直到找到至少一个满足条件的控件为止,并返回所有满足条件的控件集合。参见[控件集合](#控件集合)。 + +## 操作 ### click() +找到所有符合条件的控件并点击。相当于untilFind().click()。参见[click](#UiCollection.click)。 ### longClick() - -### accessibilityFocus() - -### clearAccessibilityFocus() - -### focus() - -### clearFocus() +找到所有符合条件的控件并长按。相当于untilFind().longClick()。参见[longClick](#UiCollection.longClick)。 ### copy() +找到所有符合条件的控件并复制其内容,只对文本框或输入框控件有文字选中时有效。相当于untilFind().copy()。参见[copy](#UiCollection.copy)。 ### paste() +找到所有符合条件的控件并粘贴,只对输入框控件有效。相当于untilFind().paste()。参见[paste](#UiCollection.paste)。 ### select() +找到所有符合条件的控件并选中。相当于untilFind().select()。参见[select](#UiCollection.select)。 ### cut() +找到所有符合条件的控件并剪切其内容,只对文本框或输入框控件有文字选中时有效。相当于untilFind().cut()。参见[cut](#UiCollection.cut)。 ### collapse() +找到所有符合条件的控件并折叠。相当于untilFind().collapse()。参见[collapse](#UiCollection.collapse)。 ### expand() - -### dismiss() +找到所有符合条件的控件并展开。相当于untilFind().expand()。参见[expand](#UiCollection.expand)。 ### show() +找到所有符合条件的控件并使其出现在屏幕上。相当于untilFind().show()。参见[show](#UiCollection.show)。 ### scrollForward() +找到所有符合条件的控件并向前滑。相当于untilFind().scrollForward()。参见[scrollForward](#UiCollection.scrollForward)。 +向前滑指的是,对于左右滑动的控件向右滑,上下滑动的控件向下滑。 ### scrollBackward() +找到所有符合条件的控件并向后滑。相当于untilFind().scrollBackward()。参见[scrollBackward](#UiCollection.scrollBackward)。 +向后滑指的是,对于左右滑动的控件向左滑,上下滑动的控件向上滑。 ### scrollUp() +找到所有符合条件的控件并向上滑。相当于untilFind().scrollUp()。参见[scrollUp](#UiCollection.scrollUp)。 ### scrollDown() +找到所有符合条件的控件并向下滑。相当于untilFind().scrollDown()。参见[scrollDown](#UiCollection.scrollDown)。 ### scrollLeft() +找到所有符合条件的控件并向左滑。相当于untilFind().scrollLeft()。参见[scrollLeft](#UiCollection.scrollLeft)。 ### scrollRight() +找到所有符合条件的控件并向右滑。相当于untilFind().scrollRight()。参见[scrollRight](#UiCollection.scrollRight)。 ### contextClick() ### setSelection(start, end) +* start \ 文字选中开始位置 +* end \ 文字选中结束位置 + +找到所有符合条件的控件并设置文字选中区域,只对文本控件和输入框有效。相当于untilFind().setSelection()。参见[setSelection](#UiCollection.setSelection)。 ### setText(text) +* text \ 要设置的文本 +找到所有符合条件的控件并设置文字,只对输入框有效。相当于untilFind().setText()。参见[setText](#UiCollection.setText)。 -### setProgress(value) +# 控件集合 +UiCollection, 通过选择器的find(), untilFind()方法返回的对象。可以对其进行操作或者获取其信息。 -### scrollTo(row, column) +### UiCollection.size() +返回集合中的控件数。 -## 控件 +### UiCollection.get(i) +* i \ 索引 -## 控件集合 \ No newline at end of file +返回集合中第i+1个控件(UiObject)。 + +### UiCollection.each(func) +* func \ 遍历函数 + +遍历集合。例如: +``` +var c = clickable(); +c.each(function(o){ + log(o.text()); +}); +``` + +### find(selector) +* selector \ 选择器 + +根据selector所确定的条件在该控件集合中找到所有符合条件的控件并返回控件集合。例如: +``` +var names = id("name"); +var clickableNames = names.find(clickable()); +``` + +## findOne(selector) +* selector \ 选择器 + +根据selector所确定的条件在该控件集合中找到一个符合条件的控件并返回控件。若找不到则返回null。 + +### UiCollection.click() +点击集合中所有控件,并返回是否全部点击成功。 + +### UiCollection.longClick() +长按集合中所有控件,并返回是否全部操作成功。 + +### UiCollection.copy() +对集合中所有控件执行复制操作,并返回是否全部操作成功。 + +### UiCollection.paste() +对集合中所有控件执行粘贴操作,并返回是否全部操作成功。 + +### UiCollection.select() +对集合中所有控件执行选中操作,并返回是否全部操作成功。 + +### UiCollection.cut() +对集合中所有控件执行剪切操作,并返回是否全部操作成功。 + +### UiCollection.collapse() +对集合中所有控件执行折叠操作,并返回是否全部操作成功。 + +### UiCollection.expand() +对集合中所有控件执行展开操作,并返回是否全部操作成功。 + +### UiCollection.show() +对集合中所有控件执行显示操作,并返回是否全部操作成功。 + +### UiCollection.scrollForward() +对集合中所有控件执行向前滑的操作,并返回是否全部操作成功。 + +### UiCollection.scrollBackward() +对集合中所有控件执行向后滑的操作,并返回是否全部操作成功。 + +### UiCollection.scrollUp() +对集合中所有控件执行向上滑的操作,并返回是否全部操作成功。 + +### UiCollection.scrollDown() +对集合中所有控件执行向下滑的操作,并返回是否全部操作成功。 + +### UiCollection.scrollLeft() +对集合中所有控件执行向左滑的操作,并返回是否全部操作成功。 + +### UiCollection.scrollRight() +对集合中所有控件执行向右滑的操作,并返回是否全部操作成功。 + +### UiCollection.contextClick() + +### UiCollection.setSelection(start, end) +* start \ 文字选中开始位置 +* end \ 文字选中结束位置 + +对集合中所有控件设置文字选中区域,并返回是否全部操作成功。 + +### UiCollection.setText(text) +* text \ 要设置的文本 +对集合中所有控件设置文本,并返回是否全部操作成功。 + +# 控件 + +UiObject, 选择器的findOne方法返回的对象。和UiCollection有相似的方法,不再赘述。除此之外还有以下方法。 + +### child(i) +* i \ 子控件索引 + +返回第i+1个子控件(UiObject)。 + +### parent() +返回父控件(UiObject)。 + +### children() +返回该控件的所有子控件组成的控件集合。 \ No newline at end of file diff --git a/app/src/main/assets/javasccript_engine_init.js b/app/src/main/assets/javasccript_engine_init.js index a2269859..54258a99 100644 --- a/app/src/main/assets/javasccript_engine_init.js +++ b/app/src/main/assets/javasccript_engine_init.js @@ -80,27 +80,11 @@ var select = function(a, b, c, d){ }, arguments); } -var focus = function(a, b, c, d){ - return performAction(function(target){ - return droid.focus(target); - }, arguments); -} - -var paste = function(a, b, c, d){ - return performAction(function(target){ - return droid.paste(target); - }, arguments); -} - -var editable = function(i){ - return droid.editable(i); -} - var input = function(a, b){ if(arguments.length == 1){ - return droid.setText(editable(-1), a); + return droid.setText(droid.editable(-1), a); }else{ - return droid.setText(editable(a), b); + return droid.setText(droid.editable(a), b); } } @@ -120,6 +104,8 @@ var log = function(str){ droid.log(str); } +var print = log; + var err = function(e){ droid.err(e); } @@ -137,16 +123,12 @@ var shell = function(cmd, root){ return droid.shell(cmd, root); } -var getTexts = function(){ - return droid.getTexts(); +var currentPackage = function(){ + return droid.currentPackage(); } -var getPackageName = function(){ - return droid.getPackageName(); -} - -var getActivityName = function(){ - return droid.getActivityName(); +var currentActivity = function(){ + return droid.currentActivity(); } var setClip = function(text){ @@ -229,6 +211,7 @@ for(var x in __selector__){ this[x] = (function(method) { return function(){ var s = droid.selector(); + //这里不知道怎么写。尴尬。只能写成这样。 if(arguments.length == 0){ return s[method](); }else if(arguments.length == 1){ diff --git a/app/src/main/assets/sample/JavaScript/E4X.js b/app/src/main/assets/sample/JavaScript/E4X.js new file mode 100644 index 00000000..af0351a9 --- /dev/null +++ b/app/src/main/assets/sample/JavaScript/E4X.js @@ -0,0 +1,190 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +print("----------------------------------------"); + +// Use the XML constructor to parse an string into an XML object +var John = "John25"; +var Sue ="Sue32"; +var tagName = "employees"; +var employees = new XML("<" + tagName +">" + John + Sue + ""); +print("The employees XML object constructed from a string is:\n" + employees); + +print("----------------------------------------"); + +// Use an XML literal to create an XML object +var order = + + John + Doe + + + Big Screen Television + 1299.99 + 1 + + + +// Construct the full customer name +var name = order.customer.firstname + " " + order.customer.lastname; + +// Calculate the total price +var total = order.item.price * order.item.quantity; + +print("The order XML object constructed using a literal is:\n" + order); +print("The total price of " + name + "'s order is " + total); + +print("----------------------------------------"); + +// construct a new XML object using expando and super-expando properties +var order = ; +order.customer.name = "Fred Jones"; +order.customer.address.street = "123 Long Lang"; +order.customer.address.city = "Underwood"; +order.customer.address.state = "CA"; +order.item[0] = ""; +order.item[0].description = "Small Rodents"; +order.item[0].quantity = 10; +order.item[0].price = 6.95; + +print("The order custructed using expandos and super-expandos is:\n" + order); + +// append a new item to the order +order.item += Catapult139.95; + +print("----------------------------------------"); + +print("The order after appending a new item is:\n" + order); + +print("----------------------------------------"); + +// dynamically construct an XML element using embedded expressions +var tagname = "name"; +var attributename = "id"; +var attributevalue = 5; +var content = "Fred"; + +var x = <{tagname} {attributename}={attributevalue}>{content}; + +print("The dynamically computed element value is:\n" + x.toXMLString()); + +print("----------------------------------------"); + +// Create a SOAP message +var message = + + + DIS + + + + +// declare the SOAP and stocks namespaces +var soap = new Namespace("http://schemas.xmlsoap.org/soap/envelope/"); +var stock = new Namespace ("http://mycompany.com/stocks"); + +// extract the soap encoding style and body from the soap message +var encodingStyle = message.@soap::encodingStyle; + +print("The encoding style of the soap message is specified by:\n" + encodingStyle); + +// change the stock symbol +message.soap::Body.stock::GetLastTradePrice.symbol = "MYCO"; + +var body = message.soap::Body; + +print("The body of the soap message is:\n" + body); + +print("----------------------------------------"); + +// create an manipulate an XML object using the default xml namespace + +default xml namespace = "http://default.namespace.com"; +var x = ; +x.a = "one"; +x.b = "two"; +x.c = three; + +print("XML object constructed using the default xml namespace:\n" + x); + +default xml namespace=""; + +print("----------------------------------------"); + +var order = + + John + Doe + + + Big Screen Television + 1299.99 + 1 + + + DVD Player + 399.99 + 1 + +; + + +// get the customer element from the orderprint("The customer is:\n" + order.customer); + +// get the id attribute from the order +print("The order id is:" + order.@id); + +// get all the child elements from the order element +print("The children of the order are:\n" + order.*); + +// get the list of all item descriptions +print("The order descriptions are:\n" + order.item.description); + + +// get second item by numeric index +print("The second item is:\n" + order.item[1]); + +// get the list of all child elements in all item elements +print("The children of the items are:\n" + order.item.*); + +// get the second child element from the order by index +print("The second child of the order is:\n" + order.*[1]); + +// calculate the total price of the order +var totalprice = 0; +for each (i in order.item) { + totalprice += i.price * i.quantity; +} +print("The total price of the order is: " + totalprice); + +print("----------------------------------------"); + +var e = + Joe20 + Sue30 +; + +// get all the names in e +print("All the employee names are:\n" + e..name); + +// employees with name Joe +print("The employee named Joe is:\n" + e.employee.(name == "Joe")); + +// employees with id's 1 & 2 +print("Employees with ids 1 & 2:\n" + e.employee.(@id == 1 || @id == 2)); + +// name of employee with id 1 +print("Name of the the employee with ID=1: " + e.employee.(@id == 1).name); + +print("----------------------------------------"); + +openConsole(); + + + + diff --git a/app/src/main/assets/sample/JavaScript/字符串.js b/app/src/main/assets/sample/JavaScript/字符串.js deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/assets/sample/Shell命令/冻结网易云音乐.js b/app/src/main/assets/sample/Shell命令/冻结网易云音乐.js new file mode 100644 index 00000000..3cc3d2c2 --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/冻结网易云音乐.js @@ -0,0 +1 @@ +shell("pm disable com.netease.cloudmusic", true); \ No newline at end of file diff --git a/app/src/main/assets/sample/Shell命令/微信扫一扫(Root).js b/app/src/main/assets/sample/Shell命令/微信扫一扫(Root).js new file mode 100644 index 00000000..9db54881 --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/微信扫一扫(Root).js @@ -0,0 +1 @@ +shell("am start -n com.tencent.mm/com.tencent.mm.plugin.scanner.ui.BaseScanUI", true); diff --git a/app/src/main/assets/sample/Shell命令/支付宝扫一扫(Root).js b/app/src/main/assets/sample/Shell命令/支付宝扫一扫(Root).js new file mode 100644 index 00000000..b7245907 --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/支付宝扫一扫(Root).js @@ -0,0 +1 @@ +shell("am start -n com.eg.android.AlipayGphone/com.alipay.mobile.scan.as.main.MainCaptureActivity", true); \ No newline at end of file diff --git a/app/src/main/assets/sample/Shell命令/结束所有后台进程.js b/app/src/main/assets/sample/Shell命令/结束所有后台进程.js new file mode 100644 index 00000000..e3d43a9c --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/结束所有后台进程.js @@ -0,0 +1 @@ +shell("am kill-all", true); \ No newline at end of file diff --git a/app/src/main/assets/sample/Shell命令/解冻并打开网易云音乐.js b/app/src/main/assets/sample/Shell命令/解冻并打开网易云音乐.js new file mode 100644 index 00000000..190ad29f --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/解冻并打开网易云音乐.js @@ -0,0 +1,2 @@ +shell("pm enable com.netease.cloudmusic", true); +launchApp("网易云音乐"); \ No newline at end of file diff --git a/app/src/main/assets/sample/Shell命令/锁屏.js b/app/src/main/assets/sample/Shell命令/锁屏.js new file mode 100644 index 00000000..c4322a80 --- /dev/null +++ b/app/src/main/assets/sample/Shell命令/锁屏.js @@ -0,0 +1,2 @@ +KeyCode("KEYCODE_POWER"); +//或者 KeyCode(26); \ No newline at end of file diff --git a/app/src/main/assets/sample/界面/QQ强制聊天.js b/app/src/main/assets/sample/界面/QQ强制聊天.js index e352acb1..854a635d 100644 --- a/app/src/main/assets/sample/界面/QQ强制聊天.js +++ b/app/src/main/assets/sample/界面/QQ强制聊天.js @@ -1,10 +1,10 @@ "ui"; -importClass("android.widget.EditText"); -importClass("android.widget.Button"); -importClass("android.widget.LinearLayout"); -importClass("android.content.Intent"); -importClass("android.net.Uri"); +importClass(android.widget.EditText); +importClass(android.widget.Button); +importClass(android.widget.LinearLayout); +importClass(android.content.Intent); +importClass(android.net.Uri); var qq = new EditText(activity); qq.setHint("请输入QQ号"); diff --git a/app/src/main/assets/sample/界面/简单计算器.js b/app/src/main/assets/sample/界面/简单计算器.js index 81ff0a49..8049325f 100644 --- a/app/src/main/assets/sample/界面/简单计算器.js +++ b/app/src/main/assets/sample/界面/简单计算器.js @@ -1,6 +1,6 @@ "ui"; -importClass("com.afollestad.materialdialogs.MaterialDialog"); +importClass(com.afollestad.materialdialogs.MaterialDialog); new MaterialDialog.Builder(activity) .title("简单计算器") diff --git a/app/src/main/assets/sample/自动操作/QQ自动抢红包.js b/app/src/main/assets/sample/自动操作/QQ自动抢红包.js index a7fc3d4e..fbf7be44 100644 --- a/app/src/main/assets/sample/自动操作/QQ自动抢红包.js +++ b/app/src/main/assets/sample/自动操作/QQ自动抢红包.js @@ -4,7 +4,7 @@ toast("出现红包时将会自动拆开并关闭"); while(!isStopped()){ if(click("点击拆开")){ //拆开红包后尝试10次关闭红包页面 - for(var i = 0; i < 10; i++){ + for(let i = 0; i < 10; i++){ sleep(300); if(click("关闭")) break; diff --git a/app/src/main/assets/sample/自动操作/屏蔽QQ通知.js b/app/src/main/assets/sample/自动操作/屏蔽QQ通知.js new file mode 100644 index 00000000..c7cf615c --- /dev/null +++ b/app/src/main/assets/sample/自动操作/屏蔽QQ通知.js @@ -0,0 +1,16 @@ +importClass(android.content.Intent); +importClass(android.net.Uri); +importClass(android.provider.Settings); + +function 打开App设置(packageName){ + var intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + var uri = Uri.fromParts("package", packageName, null); + intent.setData(uri); + context.startActivity(intent); +} + +打开App设置("com.tencent.mobileqq"); +while(!currentPackage().equals("com.android.settings")); +while(!click("通知")); +while(!click("全部阻止")); + \ No newline at end of file diff --git a/app/src/main/assets/sample/自动操作/打开自动运行的服务.js b/app/src/main/assets/sample/自动操作/打开自动运行的服务.js index 37c266f0..d51fd2c5 100644 --- a/app/src/main/assets/sample/自动操作/打开自动运行的服务.js +++ b/app/src/main/assets/sample/自动操作/打开自动运行的服务.js @@ -1,5 +1,5 @@ -function openRunningServices(){ +function 打开正在运行的服务(){ launch("com.android.settings"); var i = 0; while(!click("开发者选项")){ @@ -14,10 +14,10 @@ function openRunningServices(){ } -importClass("android.os.Build"); +importClass(android.os.Build); if(Build.VERSION.SDK_INT < 23){ toast("本代码只适用于Android6.0以上"); }else{ - openRunningServices(); + 打开正在运行的服务(); } \ No newline at end of file diff --git a/app/src/main/assets/sample/调用Java API/liveConnect.js b/app/src/main/assets/sample/调用Java API/liveConnect.js new file mode 100644 index 00000000..94faf954 --- /dev/null +++ b/app/src/main/assets/sample/调用Java API/liveConnect.js @@ -0,0 +1,26 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * liveConnect.js: a simple demonstration of JavaScript-to-Java connectivity + */ + +// Create a new StringBuffer. Note that the class name must be fully qualified +// by its package. Packages other than "java" must start with "Packages", i.e., +// "Packages.javax.servlet...". +var sb = new java.lang.StringBuffer(); + +// Now add some stuff to the buffer. +sb.append("hi, mom"); +sb.append(3); // this will add "3.0" to the buffer since all JS numbers + // are doubles by default +sb.append(true); + +// Now print it out. (The toString() method of sb is automatically called +// to convert the buffer to a string.) +// Should print "hi, mom3.0true". +print(sb); +openConsole(); diff --git a/app/src/main/assets/sample/选择器/QQ互赞脚本.js b/app/src/main/assets/sample/选择器/QQ互赞脚本.js new file mode 100644 index 00000000..51c7b443 --- /dev/null +++ b/app/src/main/assets/sample/选择器/QQ互赞脚本.js @@ -0,0 +1,22 @@ +function 下滑(){ + className("AbsListView").scrollable().scrollForward(); +} + +function 赞(){ + className("ImageView").desc("赞").click(); +} + +function 显示更多(){ + for(let i = 0; i < 2;i++){ + click("显示更多"); + } +} + +toast("请打开点赞自己的人的界面"); +while(notStopped()){ + for(let i = 0; i < 10; i++){ + 赞(); + } + 显示更多(); + 下滑(); +} \ No newline at end of file diff --git a/app/src/main/assets/sample/选择器/微信一键已读.js b/app/src/main/assets/sample/选择器/微信一键已读.js new file mode 100644 index 00000000..7fffdf8f --- /dev/null +++ b/app/src/main/assets/sample/选择器/微信一键已读.js @@ -0,0 +1,6 @@ +while(true){ + if(id("i4").untilFindOne().parent().parent().click()){ + sleep(700); + id("gf").click(); + } +} \ No newline at end of file diff --git a/app/src/main/assets/sample/选择器/网易云音乐签到.js b/app/src/main/assets/sample/选择器/网易云音乐签到.js new file mode 100644 index 00000000..c93c8524 --- /dev/null +++ b/app/src/main/assets/sample/选择器/网易云音乐签到.js @@ -0,0 +1,3 @@ +launchApp("网易云音乐"); +id("kp").click(); +text("签到").className("TextView").click(); \ No newline at end of file diff --git a/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java b/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java index 4638624e..bd39e7bb 100644 --- a/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java +++ b/app/src/main/java/com/stardust/app/OnActivityResultDelegate.java @@ -16,16 +16,16 @@ public interface OnActivityResultDelegate { void onActivityResult(int requestCode, int resultCode, Intent data); interface DelegateHost { - Manager getDelegateManger(); + Intermediary getDelegateManger(); } - class Manager implements OnActivityResultDelegate { + class Intermediary implements OnActivityResultDelegate { - private SparseArray specialDelegate = new SparseArray<>(); + private SparseArray mSpecialDelegate = new SparseArray<>(); private List mDelegates = new ArrayList<>(); public void onActivityResult(int requestCode, int resultCode, Intent data) { - OnActivityResultDelegate delegate = specialDelegate.get(requestCode); + OnActivityResultDelegate delegate = mSpecialDelegate.get(requestCode); if (delegate != null) { delegate.onActivityResult(requestCode, resultCode, data); } @@ -39,7 +39,14 @@ public interface OnActivityResultDelegate { } public void addDelegate(int requestCode, OnActivityResultDelegate delegate) { - specialDelegate.put(requestCode, delegate); + mSpecialDelegate.put(requestCode, delegate); + } + + public void removeDelegate(OnActivityResultDelegate delegate) { + if (mDelegates.remove(delegate)) { + // TODO: 2017/3/16 优化 + mSpecialDelegate.removeAt(mSpecialDelegate.indexOfValue(delegate)); + } } } diff --git a/app/src/main/java/com/stardust/hover/HoverMenuView.java b/app/src/main/java/com/stardust/hover/HoverMenuView.java index 165465fc..c93c85e5 100644 --- a/app/src/main/java/com/stardust/hover/HoverMenuView.java +++ b/app/src/main/java/com/stardust/hover/HoverMenuView.java @@ -183,7 +183,6 @@ public class HoverMenuView extends RelativeLayout { MagnetPositioner magnetPositioner = new MagnetPositioner(getResources().getDisplayMetrics(), mSidePullerPositioner, new MagnetPositioner.OnCompletionListener() { @Override public void onPullToSideCompleted() { - // TODO: this collapsed logic is duplicated mVisualState = COLLAPSED; enableDragging(); mTransitionListener.onCollapsed(); @@ -257,7 +256,6 @@ public class HoverMenuView extends RelativeLayout { removeAllTabs(); addAllTabs(); - // TODO: need null check in case selected tab is gone. Log.d(TAG, "Content change. Active id: " + mActiveTabId); Log.d(TAG, "Active tab view: " + findViewById(mActiveTabId.hashCode())); mActiveTab = findViewById(mActiveTabId.hashCode()); @@ -270,7 +268,7 @@ public class HoverMenuView extends RelativeLayout { super(context); mWindowDragWatcher = windowDragWatcher; mMenuAnchor = new CollapsedMenuAnchor(getResources().getDisplayMetrics(), 10); - mMenuAnchor.setAnchorAt((int) savedAnchor.x, savedAnchor.y); // TODO: savedAnchor isn't really a position, its a tuple of values (side, y-pos) + mMenuAnchor.setAnchorAt((int) savedAnchor.x, savedAnchor.y); mNavigator = null == navigator ? new DefaultNavigator(context) : navigator; init(); } @@ -335,7 +333,6 @@ public class HoverMenuView extends RelativeLayout { mExitRequestListener = null == exitRequestListener ? new NoOpHoverMenuExitRequestListener() : exitRequestListener; } - // TODO: create custom object to hold anchor state public PointF getAnchorState() { return new PointF(mMenuAnchor.getAnchorSide(), mMenuAnchor.getAnchorNormalizedY()); } @@ -493,7 +490,6 @@ public class HoverMenuView extends RelativeLayout { Log.d(TAG, "Enabling dragging - x: " + anchorPosition.x + ", y: " + anchorPosition.y); Log.d(TAG, "Drag width: " + mTabSizeInPx + ", height: " + mTabSizeInPx); - // TODO: should create a method specifically for placing the drag bounds. mWindowDragWatcher.deactivate(); mWindowDragWatcher.activate(mDragListener, new Rect( anchorPosition.x, @@ -548,7 +544,6 @@ public class HoverMenuView extends RelativeLayout { // Select the first tab. setActiveTab(mTabIds.get(0)); - // TODO: we might set an adapter while expanded - track down the need for this and change it. // We force a collapse because at this point we're in a weird initial state. doCollapse(true); } @@ -605,7 +600,6 @@ public class HoverMenuView extends RelativeLayout { return; } - // TODO: how can we avoid this null check? if (null != mActiveTab) { Log.d(TAG, "Adjusting tab position due to layout change."); getCollapsedMenuViewController().onDragTo(new Point( diff --git a/app/src/main/java/com/stardust/scriptdroid/App.java b/app/src/main/java/com/stardust/scriptdroid/App.java index e9997ec8..9e3c2a1b 100644 --- a/app/src/main/java/com/stardust/scriptdroid/App.java +++ b/app/src/main/java/com/stardust/scriptdroid/App.java @@ -2,30 +2,27 @@ package com.stardust.scriptdroid; import android.app.Activity; import android.app.Application; -import android.graphics.Paint; import android.os.Bundle; import android.preference.PreferenceManager; -import android.text.TextPaint; -import android.util.Log; import com.stardust.automator.AccessibilityEventCommandHost; import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider; +import com.stardust.scriptdroid.droid.runtime.DroidRuntime; +import com.stardust.scriptdroid.droid.script.JavaScriptEngine; +import com.stardust.scriptdroid.droid.script.RhinoJavaScriptEngine; +import com.stardust.scriptdroid.droid.script.file.ScriptFileList; +import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; import com.stardust.scriptdroid.layout_inspector.LayoutInspector; -import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate; +import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; import com.squareup.leakcanary.LeakCanary; import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate; -import com.stardust.scriptdroid.tool.ViewTool; import com.stardust.scriptdroid.ui.error.ErrorReportActivity; import com.stardust.theme.ThemeColor; import com.stardust.theme.ThemeColorManager; import com.stardust.util.CrashHandler; import com.stardust.util.StateObserver; -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.Arrays; - /** * Created by Stardust on 2017/1/27. */ @@ -34,12 +31,12 @@ public class App extends Application { private static final String TAG = "App"; - private static WeakReference instance; + private static App instance; private static StateObserver stateObserver; - private static WeakReference currentActivity; + private static Activity currentActivity; public static App getApp() { - return instance.get(); + return instance; } public static StateObserver getStateObserver() { @@ -49,26 +46,40 @@ public class App extends Application { public void onCreate() { super.onCreate(); - ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent))); - ThemeColorManager.init(this); + setUpDebugEnvironment(); + init(); + configApp(); + registerActivityLifecycleCallback(); + initAccessibilityServiceDelegates(); + } + + private void setUpDebugEnvironment() { if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); if (!BuildConfig.DEBUG) Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(ErrorReportActivity.class)); - instance = new WeakReference<>(this); + } + + private void init() { + ThemeColorManager.setDefaultThemeColor(new ThemeColor(getResources().getColor(R.color.colorPrimary), getResources().getColor(R.color.colorPrimaryDark), getResources().getColor(R.color.colorAccent))); + ThemeColorManager.init(this); + instance = this; stateObserver = new StateObserver(PreferenceManager.getDefaultSharedPreferences(this)); - registerActivityLifecycleCallback(); - initAccessibilityServiceDelegates(); + } + + private void configApp() { + ScriptFileList.setImpl(SharedPrefScriptFileList.getInstance()); + JavaScriptEngine.setDefault(new RhinoJavaScriptEngine(DroidRuntime.getRuntime())); } private void initAccessibilityServiceDelegates() { AccessibilityWatchDogService.addDelegateIfNeeded(100, ActionPerformAccessibilityDelegate.class); - AccessibilityWatchDogService.addDelegateIfNeeded(200, AccessibilityRecorderDelegate.getInstance()); - AccessibilityWatchDogService.addDelegateIfNeeded(300, AccessibilityEventCommandHost.instance); - AccessibilityWatchDogService.addDelegateIfNeeded(400, AccessibilityInfoProvider.instance); + AccessibilityWatchDogService.addDelegateIfNeeded(200, AccessibilityActionRecorder.getInstance()); + AccessibilityWatchDogService.addDelegateIfNeeded(300, AccessibilityEventCommandHost.getInstance()); + AccessibilityWatchDogService.addDelegateIfNeeded(400, AccessibilityInfoProvider.getInstance()); AccessibilityWatchDogService.addDelegateIfNeeded(500, LayoutInspector.getInstance()); } @@ -77,7 +88,7 @@ public class App extends Application { registerActivityLifecycleCallbacks(new SimpleActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - currentActivity = new WeakReference<>(activity); + currentActivity = activity; } @@ -88,14 +99,19 @@ public class App extends Application { @Override public void onActivityResumed(Activity activity) { - currentActivity = new WeakReference<>(activity); + currentActivity = activity; + } + + @Override + public void onActivityStopped(Activity activity) { + currentActivity = null; } }); } public static Activity currentActivity() { - return currentActivity.get(); + return currentActivity; } public static String getResString(int id) { diff --git a/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityEventHelper.java b/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityEventHelper.java new file mode 100644 index 00000000..ec34d900 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityEventHelper.java @@ -0,0 +1,33 @@ +package com.stardust.scriptdroid.accessibility; + +import android.util.SparseArray; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.stardust.scriptdroid.R; +import com.stardust.util.SparseArrayEntries; + +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_SCROLLED; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; + +/** + * Created by Stardust on 2017/3/16. + */ + +public class AccessibilityEventHelper { + + private static final SparseArray EVENT_ACTION_RES_ID_MAP = new SparseArrayEntries() + .entry(TYPE_VIEW_CLICKED, R.string.text_clicked) + .entry(TYPE_VIEW_LONG_CLICKED, R.string.text_long_clicked) + .entry(TYPE_VIEW_SCROLLED, R.string.text_scrolled) + .entry(TYPE_VIEW_TEXT_CHANGED, R.string.text_text_changed) + .sparseArray(); + + public static int getEventTypeNameResId(AccessibilityEvent event) { + return EVENT_ACTION_RES_ID_MAP.get(event.getEventType()); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityInfoProvider.java b/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityInfoProvider.java index 44477dba..0b5b9caa 100644 --- a/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityInfoProvider.java +++ b/app/src/main/java/com/stardust/scriptdroid/accessibility/AccessibilityInfoProvider.java @@ -15,11 +15,15 @@ import com.stardust.view.accessibility.AccessibilityDelegate; public class AccessibilityInfoProvider implements AccessibilityDelegate { - public static AccessibilityInfoProvider instance = new AccessibilityInfoProvider(); + private static AccessibilityInfoProvider instance = new AccessibilityInfoProvider(); private String mLatestPackage = ""; private String mLatestActivity = ""; + public static AccessibilityInfoProvider getInstance() { + return instance; + } + public synchronized String getLatestPackage() { return mLatestPackage; } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java index 88c94f96..5c3bb1b6 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java @@ -1,6 +1,7 @@ package com.stardust.scriptdroid.droid; import android.support.annotation.NonNull; +import android.util.Log; import com.jraska.console.timber.ConsoleTree; import com.stardust.scriptdroid.App; @@ -15,6 +16,9 @@ import com.stardust.scriptdroid.tool.FileUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; import timber.log.Timber; @@ -26,12 +30,6 @@ public class Droid { public static final java.lang.String STAY = "\"stay\";"; - static { - Timber.plant(new ConsoleTree.Builder() - .infoColor(0xcc000000) - .build()); - } - public static final String UI = "\"ui\";"; public interface OnRunFinishedListener extends Serializable { @@ -54,13 +52,13 @@ public class Droid { } private static final DroidRuntime RUNTIME = DroidRuntime.getRuntime(); - public static final JavaScriptEngine JAVA_SCRIPT_ENGINE = new RhinoJavaScriptEngine(RUNTIME); + 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 (!(e instanceof ScriptStopException)) { + if (!causedByInterruptedException(e)) { RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage()); - Timber.e(e, App.getApp().getString(R.string.text_error)); + Timber.e(e.getMessage()); } } }; @@ -73,12 +71,22 @@ public class Droid { 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); } public void runScriptFile(File file, OnRunFinishedListener listener) { - Timber.v("开始运行脚本文件: " + file); + Timber.v(DateFormat.getTimeInstance().format(new Date()) + "/" + App.getResString(R.string.text_start_running) + file); listener = listener == null ? DEFAULT_LISTENER : listener; try { checkFile(file); diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java index 1bf6d5b1..1ec1b880 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java @@ -6,15 +6,23 @@ 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.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.util.Log; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; +import com.jraska.console.timber.ConsoleTree; import com.stardust.scriptdroid.Pref; +import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider; import com.stardust.scriptdroid.droid.runtime.action.ActionTarget; import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; @@ -43,6 +51,18 @@ import timber.log.Timber; 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 final String TAG = "DroidRuntime"; private static DroidRuntime runtime = new DroidRuntime(); @@ -154,8 +174,11 @@ public class DroidRuntime { return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE)); } - public void log(@Nullable Object str) { - Timber.i("" + str); + 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) { @@ -184,7 +207,7 @@ public class DroidRuntime { return (T) action.getResult(); } - private void ensureAccessibilityServiceEnable() { + public void ensureAccessibilityServiceEnable() { if (AccessibilityWatchDogService.getInstance() == null) { String errorMessage = null; if (AccessibilityServiceUtils.isAccessibilityServiceEnabled(App.getApp(), AccessibilityWatchDogService.class)) { @@ -232,14 +255,14 @@ public class DroidRuntime { return Shell.execCommand(cmd, root != 0); } - public String getPackageName() { + public String currentPackage() { ensureAccessibilityServiceEnable(); - return ActionPerformAccessibilityDelegate.getLatestPackage(); + return AccessibilityInfoProvider.getInstance().getLatestPackage(); } - public String getActivityName() { + public String currentActivity() { ensureAccessibilityServiceEnable(); - return ActionPerformAccessibilityDelegate.getLatestActivity(); + return AccessibilityInfoProvider.getInstance().getLatestActivity(); } public UiSelector selector() { diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java index c133c428..b687c7a8 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/UiSelector.java @@ -69,8 +69,9 @@ public class UiSelector extends UiGlobalSelector { private static final String TAG = "UiSelector"; public UiObjectCollection find() { + DroidRuntime.getRuntime().ensureAccessibilityServiceEnable(); FindCommand command = new FindCommand(); - AccessibilityEventCommandHost.instance.executeAndWaitForEvent(command); + AccessibilityEventCommandHost.getInstance().executeAndWaitForEvent(command); return command.result; } @@ -85,11 +86,7 @@ public class UiSelector extends UiGlobalSelector { } public UiObject findOne() { - UiObjectCollection collection = find(); - if (collection == null || collection.size() == 0) { - return null; - } - return new UiObject(collection.get(0).getInfo()); + return untilFindOne(); } @NonNull @@ -103,7 +100,7 @@ public class UiSelector extends UiGlobalSelector { addFilter(new DfsFilter() { @Override protected boolean isIncluded(AccessibilityNodeInfo nodeInfo) { - String fullId = AccessibilityInfoProvider.instance.getLatestPackage() + ":id/" + id; + String fullId = AccessibilityInfoProvider.getInstance().getLatestPackage() + ":id/" + id; return fullId.equals(nodeInfo.getViewIdResourceName()); } }); diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java index 0189d379..48492697 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java @@ -23,28 +23,19 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate public static final Action NO_ACTION = null; + private static final Object ACTION_LOCK = new Object(); + private static Action action; - private static String latestPackage, latestActivity; public static void setAction(Action action) { - synchronized (ActionPerformAccessibilityDelegate.class) { + synchronized (ACTION_LOCK) { ActionPerformAccessibilityDelegate.action = action; } } - - public synchronized static String getLatestPackage() { - return latestPackage; - } - - public synchronized static String getLatestActivity() { - return latestActivity; - } - @Override public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) { - synchronized (ActionPerformAccessibilityDelegate.class) { - setLatestComponent(event.getPackageName(), event.getClassName()); + synchronized (ACTION_LOCK) { if (action == NO_ACTION) return false; AccessibilityNodeInfo root = service.getRootInActiveWindow(); @@ -66,26 +57,12 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate private void onActionPerformed() { - synchronized (ActionPerformAccessibilityDelegate.class) { + synchronized (ACTION_LOCK) { action = NO_ACTION; DroidRuntime.getRuntime().notifyActionPerformed(); } } - private static void setLatestComponent(CharSequence latestPackage, CharSequence latestClass) { - if (latestPackage == null) - return; - ActionPerformAccessibilityDelegate.latestPackage = latestPackage.toString(); - if (latestClass == null) - return; - try { - ComponentName componentName = new ComponentName(latestPackage.toString(), latestClass.toString()); - ActivityInfo activityInfo = App.getApp().getPackageManager().getActivityInfo(componentName, 0); - ActionPerformAccessibilityDelegate.latestActivity = activityInfo.name; - } catch (PackageManager.NameNotFoundException ignored) { - - } - } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java index 6dd894f8..881a450a 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/JavaScriptEngine.java @@ -14,6 +14,16 @@ import java.util.Map; 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); @@ -26,6 +36,8 @@ public abstract class JavaScriptEngine { public abstract int stopAll(); + public abstract String[] getGlobalFunctions(); + static class Init { private static final String INIT_SCRIPT = readInitScript(); diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/RhinoJavaScriptEngine.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/RhinoJavaScriptEngine.java index 3249d9f5..d0d68c35 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/RhinoJavaScriptEngine.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/RhinoJavaScriptEngine.java @@ -26,6 +26,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { } private final Set mThreads = new HashSet<>(); + private String[] mFunctions; public RhinoJavaScriptEngine(DroidRuntime runtime) { set("droid", runtime); @@ -51,6 +52,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { private Context createContext() { Context context = Context.enter(); context.setOptimizationLevel(-1); + context.setLanguageVersion(Context.VERSION_1_7); context.setInstructionObserverThreshold(10000); return context; } @@ -80,6 +82,24 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { return n; } + + @Override + public String[] getGlobalFunctions() { + if (mFunctions == null) + mFunctions = getGlobalFunctionsInner(); + return mFunctions; + } + + private String[] getGlobalFunctionsInner() { + Scriptable scriptable = (Scriptable) execute("this"); + Object[] ids = scriptable.getIds(); + String[] functions = new String[ids.length]; + for (int i = 0; i < ids.length; i++) { + functions[i] = ids[i].toString(); + } + return functions; + } + @Override public void removeAndDestroy() { synchronized (mThreads) { @@ -87,6 +107,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { } } + public static class InterruptibleContextFactory extends ContextFactory { @Override diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java index 55ac8080..ea6fab4f 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/script/file/ScriptFileList.java @@ -8,6 +8,16 @@ import java.io.File; public abstract class ScriptFileList { + private static ScriptFileList impl; + + public static void setImpl(ScriptFileList list) { + impl = list; + } + + public static ScriptFileList getImpl() { + return impl; + } + public abstract void add(ScriptFile scriptFile); public abstract ScriptFile get(int i); @@ -24,6 +34,6 @@ public abstract class ScriptFileList { return file.delete(); } - public abstract boolean containsPath(String path); + } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java index ac25b2ff..a2505b2e 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/FloatingWindowManger.java @@ -33,9 +33,11 @@ public class FloatingWindowManger { Intent intent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + packageName)); + Uri.parse("package:" + packageName)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else { - intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + packageName)); + intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + packageName)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } App.getApp().startActivity(intent); } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java index 89b46ccd..c5fcf3b9 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuAdapter.java @@ -37,11 +37,9 @@ import io.mattcarroll.hover.NavigatorContent; public class HoverMenuAdapter implements io.mattcarroll.hover.HoverMenuAdapter { - public static final String ID_MAIN = "intro"; + public static final String ID_MAIN = "main"; public static final String ID_RECORD = "record"; public static final String ID_SCRIPT_LIST = "script_list"; - public static final String MENU_ID = "menu"; - public static final String PLACEHOLDER_ID = "placeholder"; private final Context mContext; private final List mTabIds; diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java index 7193584e..419b845f 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/HoverMenuService.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.IBinder; import android.support.annotation.Nullable; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.WindowManager; import android.widget.Toast; @@ -50,8 +51,10 @@ public class HoverMenuService extends Service { public static final String MESSAGE_SHOW_LAYOUT_BOUNDS = "MESSAGE_SHOW_LAYOUT_BOUNDS"; public static final String MESSAGE_COLLAPSE_MENU = "MESSAGE_COLLAPSE_MENU"; public static final String MESSAGE_MENU_COLLAPSING = "MESSAGE_MENU_COLLAPSING"; + public static final String MESSAGE_MENU_EXPANDING = "MESSAGE_MENU_EXPANDING"; + public static final String MESSAGE_MENU_EXIT = "MESSAGE_MENU_EXIT"; - private static WeakReference service = new WeakReference<>(null); + private static HoverMenuService service = null; private static boolean sIsRunning; public static void startService(Context context) { @@ -64,8 +67,8 @@ public class HoverMenuService extends Service { } public static void stopService() { - if (isServiceRunning() && service.get() != null) { - service.get().stopSelf(); + if (isServiceRunning() && service != null) { + service.stopSelf(); setIsRunning(false); } } @@ -92,6 +95,7 @@ public class HoverMenuService extends Service { private HoverMenu.OnExitListener mWindowHoverMenuMenuExitListener = new HoverMenu.OnExitListener() { @Override public void onExitByUserRequest() { + EventBus.getDefault().post(new MessageEvent(MESSAGE_MENU_EXIT)); savePreferredLocation(); mWindowHoverMenu.hide(); stopSelf(); @@ -101,11 +105,11 @@ public class HoverMenuService extends Service { @Override public void onCreate() { super.onCreate(); - if (service.get() != null) { + if (service != null) { stopSelf(); return; } - service = new WeakReference<>(this); + service = this; EventBus.getDefault().register(this); mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); try { @@ -127,7 +131,7 @@ public class HoverMenuService extends Service { } private void initWindowMenu() { - mWindowHoverMenu = (WindowHoverMenu) new HoverMenuBuilder(this) + mWindowHoverMenu = (WindowHoverMenu) new HoverMenuBuilder(new ContextThemeWrapper(this, R.style.AppTheme)) .displayWithinWindow() .useAdapter(new HoverMenuAdapter(this)) .restoreVisualState(loadPreferredLocation()) @@ -138,6 +142,7 @@ public class HoverMenuService extends Service { mWindowHoverMenu.setHoverMenuTransitionListener(new SimpleHoverMenuTransitionListener() { @Override public void onExpanding() { + EventBus.getDefault().post(new MessageEvent(MESSAGE_MENU_EXPANDING)); captureCurrentWindow(); } @@ -166,17 +171,20 @@ public class HoverMenuService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - mWindowHoverMenu.show(); + if (mWindowHoverMenu != null) + mWindowHoverMenu.show(); return START_STICKY; } @Override public void onDestroy() { - mWindowHoverMenu.hide(); - EventBus.getDefault().unregister(this); + if (mWindowHoverMenu != null) + mWindowHoverMenu.hide(); + if (EventBus.getDefault().isRegistered(this)) + EventBus.getDefault().unregister(this); setIsRunning(false); - if (service.get() == this) - service.clear(); + if (service == this) + service = null; } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java index f53081f0..68cc4abe 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/MainMenuNavigatorContent.java @@ -1,21 +1,28 @@ package com.stardust.scriptdroid.external.floating_window.content; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.Intent; import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.view.View; +import android.widget.TextView; import android.widget.Toast; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.accessibility.AccessibilityInfoProvider; import com.stardust.scriptdroid.droid.Droid; import com.stardust.scriptdroid.external.floating_window.HoverMenuService; import com.stardust.scriptdroid.layout_inspector.LayoutInspector; +import com.stardust.scriptdroid.tool.ClipboardTool; +import com.stardust.scriptdroid.ui.main.MainActivity; import com.stardust.util.MessageEvent; import com.stardust.view.ViewBinder; import com.stardust.view.ViewBinding; import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; import java.util.ArrayList; import java.util.Arrays; @@ -36,11 +43,19 @@ public class MainMenuNavigatorContent implements NavigatorContent { private View mView; + @ViewBinding.Id(R.id.current_package) + private TextView mCurrentPackageTextView; + @ViewBinding.Id(R.id.current_activity) + private TextView mCurrentActivityTextView; + private String mCurrentPackage, mCurrentActivity; + private Context mContext; public MainMenuNavigatorContent(Context context) { + mContext = context; mView = View.inflate(context, R.layout.floating_window_main_menu, null); ViewBinder.bind(this); + EventBus.getDefault().register(this); } @ViewBinding.Click(R.id.layout_hierarchy) @@ -70,6 +85,13 @@ public class MainMenuNavigatorContent implements NavigatorContent { Toast.makeText(mView.getContext(), R.string.text_no_running_script, Toast.LENGTH_SHORT).show(); } + @ViewBinding.Click(R.id.open_launcher) + private void openMainActivity() { + App.getApp().startActivity(new Intent(App.getApp(), MainActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK)); + EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU)); + } + @NonNull @Override public View getView() { @@ -81,11 +103,40 @@ public class MainMenuNavigatorContent implements NavigatorContent { } + @SuppressLint("SetTextI18n") + private void syncCurrentInfo() { + mCurrentPackage = AccessibilityInfoProvider.getInstance().getLatestPackage(); + mCurrentActivity = AccessibilityInfoProvider.getInstance().getLatestActivity(); + mCurrentActivityTextView.setText(mContext.getString(R.string.text_current_activity) + mCurrentActivity); + mCurrentPackageTextView.setText(mContext.getString(R.string.text_current_package) + mCurrentPackage); + } + + @ViewBinding.Click(R.id.current_activity) + private void copyCurrentActivity() { + ClipboardTool.setClip(mCurrentActivity); + Toast.makeText(mContext, R.string.text_copied, Toast.LENGTH_SHORT).show(); + } + + @ViewBinding.Click(R.id.current_package) + private void copyCurrentPackage() { + ClipboardTool.setClip(mCurrentPackage); + Toast.makeText(mContext, R.string.text_copied, Toast.LENGTH_SHORT).show(); + } + @Override public void onHidden() { } + @Subscribe + public void onMessageEvent(MessageEvent event) { + if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXPANDING)) { + syncCurrentInfo(); + } else if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXIT)) { + EventBus.getDefault().unregister(this); + } + } + public View findViewById(int id) { return mView.findViewById(id); } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/RecordNavigatorContent.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/RecordNavigatorContent.java index a082f5ea..3d6630fd 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/RecordNavigatorContent.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/RecordNavigatorContent.java @@ -2,24 +2,61 @@ package com.stardust.scriptdroid.external.floating_window.content; import android.content.Context; import android.support.annotation.NonNull; +import android.support.v7.widget.SwitchCompat; import android.view.View; import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.accessibility.AccessibilityEventHelper; +import com.stardust.scriptdroid.external.floating_window.HoverMenuService; +import com.stardust.scriptdroid.record.Recorder; +import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder; +import com.stardust.scriptdroid.record.inputevent.InputEventConverter; +import com.stardust.scriptdroid.record.inputevent.TouchRecorder; +import com.stardust.scriptdroid.ui.main.MainActivity; +import com.stardust.util.MessageEvent; +import com.stardust.view.ViewBinder; +import com.stardust.view.ViewBinding; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; import io.mattcarroll.hover.Navigator; import io.mattcarroll.hover.NavigatorContent; + /** * Created by Stardust on 2017/3/12. */ -public class RecordNavigatorContent implements NavigatorContent { +public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStateChangedListener { private View mView; + @ViewBinding.Id(R.id.sw_recorded_by_root) + private SwitchCompat mRecordedByRootSwitch; + + @ViewBinding.Id(R.id.sw_record_toast) + private SwitchCompat mRecordToastSwitch; + + @ViewBinding.Id(R.id.img_start_or_pause) + private ImageView mStartOrPauseRecordIcon; + + @ViewBinding.Id(R.id.text_start_or_pause) + private TextView mStartOrPauseRecordText; + + + @ViewBinding.Id(R.id.stop_record) + private View mStopRecord; + + private Recorder mRecorder; + private Context mContext; public RecordNavigatorContent(Context context) { - mView = new View(context); + mContext = context; + mView = View.inflate(context, R.layout.floating_window_record, null); + ViewBinder.bind(this); } @NonNull @@ -30,11 +67,118 @@ public class RecordNavigatorContent implements NavigatorContent { @Override public void onShown(@NonNull Navigator navigator) { - + if (!EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().register(this); + } } @Override public void onHidden() { + if (EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().unregister(this); + } + } + + @ViewBinding.Click(R.id.sw_root_container) + private void toggleRecordedByRootSwitch() { + mRecordedByRootSwitch.toggle(); + } + + @ViewBinding.Click(R.id.sw_record_toast_container) + private void toggleRecordToastSwitch() { + mRecordToastSwitch.toggle(); + } + + @ViewBinding.Click(R.id.start_or_pause) + private void startOrPauseRecord() { + if (mRecorder == null) { + startRecord(); + } else if (mRecorder.getState() == Recorder.STATE_PAUSED) { + resumeRecord(); + } else { + pauseRecord(); + } + } + + private void resumeRecord() { + mRecorder.resume(); + setState(Recorder.STATE_RECORDING); + EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU)); + } + + private void pauseRecord() { + mRecorder.pause(); + setState(Recorder.STATE_PAUSED); + } + + private void startRecord() { + mRecorder = mRecordedByRootSwitch.isChecked() ? new TouchRecorder() : AccessibilityActionRecorder.getInstance(); + AccessibilityActionRecorder.getInstance().setRecordMessageEnabled(mRecordToastSwitch.isChecked()); + mRecorder.setOnStateChangedListener(this); + mRecorder.start(); + setState(Recorder.STATE_RECORDING); + EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU)); + } + + private void setState(int state) { + mStopRecord.setVisibility(state == Recorder.STATE_STOPPED ? View.GONE : View.VISIBLE); + mStartOrPauseRecordIcon.setImageResource(state == Recorder.STATE_RECORDING ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_48dp); + //我知道这样写代码会被打 但我懒... + mStartOrPauseRecordText.setText(state == Recorder.STATE_RECORDING ? R.string.text_pause_record : + state == Recorder.STATE_PAUSED ? R.string.text_resume_record : R.string.text_start_record); + + } + + @ViewBinding.Click(R.id.stop_record) + private void stopRecord() { + mRecorder.stop(); + setState(Recorder.STATE_STOPPED); + EventBus.getDefault().post(new MessageEvent(HoverMenuService.MESSAGE_COLLAPSE_MENU)); + } + + + public View findViewById(int id) { + return mView.findViewById(id); + } + + @Subscribe + public void onMessageEvent(MessageEvent event) { + if (event.message.equals(HoverMenuService.MESSAGE_MENU_EXPANDING)) { + pauseRecord(); + } + } + + @Subscribe + public void onAccessibilityActionRecordEvent(AccessibilityActionRecorder.AccessibilityActionRecordEvent event) { + if (mRecordToastSwitch.isChecked()) { + Toast.makeText(mContext, AccessibilityEventHelper.getEventTypeNameResId(event.getAccessibilityEvent()), Toast.LENGTH_SHORT).show(); + } + } + + @Subscribe + public void onTouchRecorderStateChanged(InputEventConverter.RecordStateChangeEvent event) { + if (!event.isRecording()) { + stopRecord(); + } + } + + @Override + public void onStart() { + Toast.makeText(mContext, R.string.text_start_record, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onStop() { + MainActivity.onActionRecordStopped(mContext, mRecorder.getCode()); + mRecorder = null; + } + + @Override + public void onPause() { + } + + @Override + public void onResume() { } } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/ScritpListNavigatorContent.java b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/ScritpListNavigatorContent.java index 2695f5ec..28cecc42 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/ScritpListNavigatorContent.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/floating_window/content/ScritpListNavigatorContent.java @@ -6,6 +6,7 @@ import android.view.ContextThemeWrapper; import android.view.View; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; import com.stardust.scriptdroid.external.floating_window.view.FloatingScriptFileListView; @@ -22,7 +23,7 @@ public class ScritpListNavigatorContent implements NavigatorContent { public ScritpListNavigatorContent(Context context) { mFloatingScriptFileListView = new FloatingScriptFileListView(new ContextThemeWrapper(context, R.style.AppTheme)); - mFloatingScriptFileListView.setScriptFileList(SharedPrefScriptFileList.getInstance()); + mFloatingScriptFileListView.setScriptFileList(ScriptFileList.getImpl()); } @NonNull diff --git a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordNotification.java b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordNotification.java new file mode 100644 index 00000000..01ab66dc --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordNotification.java @@ -0,0 +1,171 @@ +package com.stardust.scriptdroid.external.notification.record; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.app.NotificationCompat; +import android.widget.Toast; + +import com.stardust.scriptdroid.App; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.record.Recorder; +import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder; +import com.stardust.scriptdroid.service.AccessibilityWatchDogService; +import com.stardust.scriptdroid.service.VolumeChangeObverseService; +import com.stardust.scriptdroid.ui.main.MainActivity; + +/** + * Created by Stardust on 2017/2/14. + */ + +public class AccessibilityActionRecordNotification { + + private static final String INTENT_ACTION = " com.stardust.scriptdroid.Record"; + private static final String EXTRA_ACTION = "action"; + private static final int ACTION_STOP = 17771; + private static final int ACTION_START_OR_PAUSE = 17772; + private static final int ACTION_DELETE = 17773; + private static final int ACTION_REDO = 17774; + + private static final int NOTIFY_ID = 22236; + private static NotificationCompat.Builder builder; + private static BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int action = intent.getIntExtra(EXTRA_ACTION, 0); + switch (action) { + case ACTION_STOP: + stopRecord(context); + collapseNotificationBar(context); + break; + case ACTION_START_OR_PAUSE: + startOrPauseRecord(context); + collapseNotificationBar(context); + break; + case ACTION_DELETE: + AccessibilityActionRecordNotification.cancelNotification(); + stopRecordIfNeeded(context); + VolumeChangeObverseService.stopServiceIfNeeded(); + break; + case ACTION_REDO: + redoRecord(context); + break; + } + } + }; + + + static PendingIntent getStartOrPauseIntent() { + Intent intent = new Intent(INTENT_ACTION) + .putExtra(EXTRA_ACTION, ACTION_START_OR_PAUSE); + return PendingIntent.getBroadcast(App.getApp(), ACTION_START_OR_PAUSE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + static PendingIntent getStopIntent() { + Intent intent = new Intent(INTENT_ACTION) + .putExtra(EXTRA_ACTION, ACTION_STOP); + return PendingIntent.getBroadcast(App.getApp(), ACTION_STOP, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + static PendingIntent getDeleteIntent() { + Intent intent = new Intent(INTENT_ACTION) + .putExtra(EXTRA_ACTION, ACTION_DELETE); + return PendingIntent.getBroadcast(App.getApp(), ACTION_DELETE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + + static PendingIntent getRedoIntent() { + Intent intent = new Intent(INTENT_ACTION) + .putExtra(EXTRA_ACTION, ACTION_REDO); + return PendingIntent.getService(App.getApp(), ACTION_REDO, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + + public static void showOrUpdateNotification() { + if (builder == null) { + builder = new NotificationCompat.Builder(App.getApp()) + .setAutoCancel(false) + .setSmallIcon(R.drawable.ic_robot_head) + .setDeleteIntent(getDeleteIntent()) + .setCustomContentView(AccessibilityActionRecordSwitchView.getInstance()); + } + showNotification(builder.build()); + VolumeChangeObverseService.startServiceIfNeeded(App.getApp()); + } + + private static void showNotification(Notification notification) { + NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(NOTIFY_ID, notification); + App.getApp().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION)); + } + + public static void cancelNotification() { + NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(NOTIFY_ID); + App.getApp().unregisterReceiver(broadcastReceiver); + } + + private static void collapseNotificationBar(Context context) { + context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } + + private static void stopRecordIfNeeded(Context context) { + int state = AccessibilityActionRecorder.getInstance().getState(); + if (state == Recorder.STATE_RECORDING || state == Recorder.STATE_PAUSED) { + stopRecord(context); + } + } + + private static void startOrPauseRecord(Context context) { + int state = AccessibilityActionRecorder.getInstance().getState(); + if (state == Recorder.STATE_PAUSED) { + resumeRecord(context); + } else if (state == Recorder.STATE_RECORDING) { + pauseRecord(context); + } else { + //state == STOPPED + startRecord(context); + } + } + + public static void startRecord(Context context) { + if (AccessibilityWatchDogService.getInstance() == null) { + Toast.makeText(context, R.string.text_need_enable_accessibility_service, Toast.LENGTH_SHORT).show(); + return; + } + AccessibilityActionRecorder.getInstance().start(); + AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_RECORDING); + Toast.makeText(context, R.string.text_start_record, Toast.LENGTH_SHORT).show(); + } + + private static void pauseRecord(Context context) { + AccessibilityActionRecorder.getInstance().pause(); + AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_PAUSED); + } + + private static void resumeRecord(Context context) { + AccessibilityActionRecorder.getInstance().resume(); + AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_RECORDING); + } + + public static void stopRecord(Context context) { + if (AccessibilityActionRecorder.getInstance().getState() != Recorder.STATE_STOPPED) { + AccessibilityActionRecorder.getInstance().stop(); + String script = AccessibilityActionRecorder.getInstance().getCode(); + AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_STOPPED); + MainActivity.onActionRecordStopped(context, script); + } else { + Toast.makeText(App.getApp(), R.string.text_not_recording, Toast.LENGTH_SHORT).show(); + } + } + + private static void redoRecord(Context context) { + if (AccessibilityActionRecorder.getInstance().getState() != Recorder.STATE_STOPPED) { + AccessibilityActionRecorder.getInstance().stop(); + AccessibilityActionRecordSwitchView.getInstance().setState(Recorder.STATE_STOPPED); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchView.java b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordSwitchView.java similarity index 55% rename from app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchView.java rename to app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordSwitchView.java index cf861369..62748f65 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchView.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/AccessibilityActionRecordSwitchView.java @@ -4,53 +4,50 @@ import android.widget.RemoteViews; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.record.Recorder; /** * Created by Stardust on 2017/2/14. */ -public class ActionRecordSwitchView extends RemoteViews { +public class AccessibilityActionRecordSwitchView extends RemoteViews { - private static ActionRecordSwitchView instance; + private static AccessibilityActionRecordSwitchView instance; - public static final int STOPPED = 0; - public static final int PAUSED = 1; - public static final int RECORDING = 2; - - public static ActionRecordSwitchView getInstance() { + public static AccessibilityActionRecordSwitchView getInstance() { if (instance == null) { - instance = new ActionRecordSwitchView(); + instance = new AccessibilityActionRecordSwitchView(); } return instance; } - private ActionRecordSwitchView() { + private AccessibilityActionRecordSwitchView() { super(App.getApp().getPackageName(), R.layout.remote_views_record_switch); setUpOnClick(); } public void setState(int state) { switch (state) { - case STOPPED: + case Recorder.STATE_STOPPED: setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_play_arrow_grey600_48dp); setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_start_record)); break; - case RECORDING: + case Recorder.STATE_RECORDING: setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_pause_grey600_48dp); setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_pause_record)); break; - case PAUSED: + case Recorder.STATE_PAUSED: setImageViewResource(R.id.img_start_or_pause, R.drawable.ic_play_arrow_grey600_48dp); setTextViewText(R.id.text_start_or_pause, App.getApp().getString(R.string.text_resume_record)); break; } - ActionRecordSwitchNotification.showOrUpdateNotification(); + AccessibilityActionRecordNotification.showOrUpdateNotification(); } private void setUpOnClick() { - setOnClickPendingIntent(R.id.stop, ActionRecordSwitchHandleService.getStopIntent()); - setOnClickPendingIntent(R.id.start_or_pause, ActionRecordSwitchHandleService.getStartOrPauseIntent()); - setOnClickPendingIntent(R.id.close, ActionRecordSwitchHandleService.getDeleteIntent()); - setOnClickPendingIntent(R.id.redo, ActionRecordSwitchHandleService.getRedoIntent()); + setOnClickPendingIntent(R.id.stop, AccessibilityActionRecordNotification.getStopIntent()); + setOnClickPendingIntent(R.id.start_or_pause, AccessibilityActionRecordNotification.getStartOrPauseIntent()); + setOnClickPendingIntent(R.id.close, AccessibilityActionRecordNotification.getDeleteIntent()); + setOnClickPendingIntent(R.id.redo, AccessibilityActionRecordNotification.getRedoIntent()); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchHandleService.java b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchHandleService.java deleted file mode 100644 index 9749bb33..00000000 --- a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchHandleService.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.stardust.scriptdroid.external.notification.record; - -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.support.annotation.Nullable; -import android.widget.Toast; - -import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate; -import com.stardust.scriptdroid.App; -import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.service.AccessibilityWatchDogService; -import com.stardust.scriptdroid.service.VolumeChangeListenService; -import com.stardust.scriptdroid.ui.main.MainActivity; - -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.PAUSED; -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.RECORDING; -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED; - -/** - * Created by Stardust on 2017/2/15. - */ - -public class ActionRecordSwitchHandleService extends Service { - - - private static final String EXTRA_INTENT_VALID = "ActionRecordSwitchHandleService.intentValid"; - private static final String EXTRA_ACTION = "action"; - private static final int ACTION_STOP = 17771; - private static final int ACTION_START_OR_PAUSE = 17772; - private static final int ACTION_DELETE = 17773; - private static final int ACTION_REDO = 17774; - - public static PendingIntent getStartOrPauseIntent() { - Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class) - .putExtra(EXTRA_INTENT_VALID, true) - .putExtra(EXTRA_ACTION, ACTION_START_OR_PAUSE); - return PendingIntent.getService(App.getApp(), ACTION_START_OR_PAUSE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - public static PendingIntent getStopIntent() { - Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class) - .putExtra(EXTRA_INTENT_VALID, true) - .putExtra(EXTRA_ACTION, ACTION_STOP); - return PendingIntent.getService(App.getApp(), ACTION_STOP, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - public static PendingIntent getDeleteIntent() { - Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class) - .putExtra(EXTRA_INTENT_VALID, true) - .putExtra(EXTRA_ACTION, ACTION_DELETE); - return PendingIntent.getService(App.getApp(), ACTION_DELETE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - - public static PendingIntent getRedoIntent() { - Intent intent = new Intent(App.getApp(), ActionRecordSwitchHandleService.class) - .putExtra(EXTRA_INTENT_VALID, true) - .putExtra(EXTRA_ACTION, ACTION_REDO); - return PendingIntent.getService(App.getApp(), ACTION_REDO, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - boolean intentValid = intent.getBooleanExtra(EXTRA_INTENT_VALID, false); - if (intentValid) { - performAction(intent.getIntExtra(EXTRA_ACTION, 0)); - } - stopSelf(); - return super.onStartCommand(intent, flags, startId); - } - - private void performAction(int action) { - switch (action) { - case ACTION_STOP: - stopRecord(this); - collapseNotificationBar(); - break; - case ACTION_START_OR_PAUSE: - startOrPauseRecord(this); - collapseNotificationBar(); - break; - case ACTION_DELETE: - ActionRecordSwitchNotification.cancelNotification(); - stopRecordIfNeeded(); - VolumeChangeListenService.stopServiceIfNeeded(); - break; - case ACTION_REDO: - redoRecord(this); - break; - } - } - - - private void collapseNotificationBar() { - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - private void stopRecordIfNeeded() { - if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) { - stopRecord(this); - } - } - - private static void startOrPauseRecord(Context context) { - int state = AccessibilityRecorderDelegate.getInstance().getState(); - if (state == PAUSED) { - resumeRecord(context); - } else if (state == RECORDING) { - pauseRecord(context); - } else { - //state == STOPPED - startRecord(context); - } - } - - public static void startRecord(Context context) { - if (AccessibilityWatchDogService.getInstance() == null) { - Toast.makeText(context, R.string.text_need_enable_accessibility_service, Toast.LENGTH_SHORT).show(); - return; - } - AccessibilityRecorderDelegate.getInstance().startRecord(); - ActionRecordSwitchView.getInstance().setState(RECORDING); - Toast.makeText(context, R.string.text_start_record, Toast.LENGTH_SHORT).show(); - } - - private static void pauseRecord(Context context) { - AccessibilityRecorderDelegate.getInstance().pauseRecord(); - ActionRecordSwitchView.getInstance().setState(PAUSED); - } - - private static void resumeRecord(Context context) { - AccessibilityRecorderDelegate.getInstance().resumeRecord(); - ActionRecordSwitchView.getInstance().setState(RECORDING); - } - - public static void stopRecord(Context context) { - if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) { - String script = AccessibilityRecorderDelegate.getInstance().stopRecord(); - ActionRecordSwitchView.getInstance().setState(STOPPED); - MainActivity.onActionRecordStopped(context, script); - } else { - Toast.makeText(App.getApp(), R.string.text_not_recording, Toast.LENGTH_SHORT).show(); - } - } - - private static void redoRecord(Context context) { - if (AccessibilityRecorderDelegate.getInstance().getState() != STOPPED) { - AccessibilityRecorderDelegate.getInstance().stopRecord(); - ActionRecordSwitchView.getInstance().setState(STOPPED); - } - } - - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - -} diff --git a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchNotification.java b/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchNotification.java deleted file mode 100644 index 859cb588..00000000 --- a/app/src/main/java/com/stardust/scriptdroid/external/notification/record/ActionRecordSwitchNotification.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.stardust.scriptdroid.external.notification.record; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.support.v4.app.NotificationCompat; - -import com.stardust.scriptdroid.App; -import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.service.VolumeChangeListenService; - -/** - * Created by Stardust on 2017/2/14. - */ - -public class ActionRecordSwitchNotification { - - private static final int NOTIFY_ID = 22236; - private static NotificationCompat.Builder builder; - - - public static void showOrUpdateNotification() { - if (builder == null) { - builder = new NotificationCompat.Builder(App.getApp()) - .setAutoCancel(false) - .setSmallIcon(R.drawable.ic_robot_head) - .setDeleteIntent(ActionRecordSwitchHandleService.getDeleteIntent()) - .setCustomContentView(ActionRecordSwitchView.getInstance()); - } - showNotification(builder.build()); - VolumeChangeListenService.startServiceIfNeeded(App.getApp()); - } - - private static void showNotification(Notification notification) { - NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.notify(NOTIFY_ID, notification); - } - - public static void cancelNotification() { - NotificationManager notificationManager = (NotificationManager) App.getApp().getSystemService(Context.NOTIFICATION_SERVICE); - notificationManager.cancel(NOTIFY_ID); - } -} diff --git a/app/src/main/java/com/stardust/scriptdroid/external/open/ImportIntentActivity.java b/app/src/main/java/com/stardust/scriptdroid/external/open/ImportIntentActivity.java index 90252c74..fe57f9ca 100644 --- a/app/src/main/java/com/stardust/scriptdroid/external/open/ImportIntentActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/external/open/ImportIntentActivity.java @@ -9,7 +9,9 @@ import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.scriptdroid.droid.script.file.ScriptFile; +import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; +import com.stardust.scriptdroid.tool.FileUtils; import com.stardust.scriptdroid.ui.BaseActivity; import com.stardust.scriptdroid.ui.main.MainActivity; import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; @@ -31,8 +33,8 @@ public class ImportIntentActivity extends BaseActivity { } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, R.string.edit_and_run_handle_intent_error, Toast.LENGTH_LONG).show(); + finish(); } - finish(); } private void handleIntent() { @@ -41,19 +43,22 @@ public class ImportIntentActivity extends BaseActivity { if (!TextUtils.isEmpty(path)) { new ThemeColorMaterialDialogBuilder(this) .title(R.string.text_please_input_name) - .input(getString(R.string.text_name), new File(path).getName(), new MaterialDialog.InputCallback() { + .input(getString(R.string.text_name), FileUtils.getNameWithoutExtension(path), new MaterialDialog.InputCallback() { @Override public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { - SharedPrefScriptFileList.getInstance().add(new ScriptFile(input.toString(), path)); + ScriptFileList.getImpl().add(new ScriptFile(input.toString(), path)); startMainActivity(); } }) .show(); + } else { + finish(); } } private void startMainActivity() { startActivity(new Intent(this, MainActivity.class) .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)); + finish(); } } \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/record/AccessibilityRecorderDelegate.java b/app/src/main/java/com/stardust/scriptdroid/record/AccessibilityRecorderDelegate.java deleted file mode 100644 index a1c71771..00000000 --- a/app/src/main/java/com/stardust/scriptdroid/record/AccessibilityRecorderDelegate.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.stardust.scriptdroid.record; - -import android.accessibilityservice.AccessibilityService; -import android.view.accessibility.AccessibilityEvent; - -import com.stardust.view.accessibility.AccessibilityDelegate; - -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.PAUSED; -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.RECORDING; -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED; - -/** - * Created by Stardust on 2017/2/14. - */ - -public class AccessibilityRecorderDelegate implements AccessibilityDelegate { - - private static final int PRIORITY = 200; - private static final long RECORD_TIME_OUT = 10 * 60 * 1000; - - private static AccessibilityRecorderDelegate instance; - private int mState = STOPPED; - - public static AccessibilityRecorderDelegate getInstance() { - if (instance == null) { - instance = new AccessibilityRecorderDelegate(); - } - return instance; - } - - private ActionRecorder mRecorder = new ActionRecorder(); - private long mRecordStartMillis; - - public void startRecord() { - if (mState != STOPPED) { - throw new IllegalStateException("Recording"); - } - mState = RECORDING; - mRecorder = new ActionRecorder(); - mRecordStartMillis = System.currentTimeMillis(); - } - - - public String stopRecord() { - if (mState == STOPPED) { - throw new IllegalStateException("Not recording"); - } - mState = STOPPED; - String script = mRecorder.getScript(); - mRecorder = null; - return script; - } - - - public void pauseRecord() { - if (mState != RECORDING) { - throw new IllegalStateException("Not recording"); - } - mState = PAUSED; - } - - public void resumeRecord() { - if (mState != PAUSED) { - throw new IllegalStateException("Not paused"); - } - mRecorder.onResume(); - mState = RECORDING; - } - - public void stopRecordIfNeeded() { - if (mRecorder != null) { - mRecorder = null; - } - } - - public int getState() { - return mState; - } - - @Override - public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) { - if (mState == RECORDING) { - mRecorder.record(service, event); - checkTimeOut(); - } - return false; - } - - private void checkTimeOut() { - if (System.currentTimeMillis() - mRecordStartMillis > RECORD_TIME_OUT) { - stopRecord(); - } - } - -} diff --git a/app/src/main/java/com/stardust/scriptdroid/record/Recorder.java b/app/src/main/java/com/stardust/scriptdroid/record/Recorder.java new file mode 100644 index 00000000..70883ee1 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/record/Recorder.java @@ -0,0 +1,154 @@ +package com.stardust.scriptdroid.record; + +/** + * Created by Stardust on 2017/3/16. + */ + +public interface Recorder { + + interface OnStateChangedListener { + + void onStart(); + + void onStop(); + + void onPause(); + + void onResume(); + + } + + OnStateChangedListener NO_OPERATION_LISTENER = new OnStateChangedListener() { + @Override + public void onStart() { + + } + + @Override + public void onStop() { + + } + + @Override + public void onPause() { + + } + + @Override + public void onResume() { + + } + }; + + int STATE_NOT_START = 0; + int STATE_RECORDING = 1; + int STATE_PAUSED = 2; + int STATE_STOPPED = 3; + + void start(); + + void stop(); + + void pause(); + + void resume(); + + String getCode(); + + int getState(); + + void setOnStateChangedListener(OnStateChangedListener onStateChangedListener); + + abstract class DefaultIMPL implements Recorder { + + + private OnStateChangedListener mOnStateChangedListener = NO_OPERATION_LISTENER; + + + private final boolean mSync; + private int mState = STATE_NOT_START; + + public DefaultIMPL(boolean syncOfState) { + mSync = syncOfState; + } + + public DefaultIMPL() { + this(false); + } + + public void start() { + checkState(STATE_NOT_START); + setState(STATE_RECORDING); + startImpl(); + mOnStateChangedListener.onStart(); + } + + + private void checkState(int... expectedStates) { + for (int expectedState : expectedStates) { + if (mState == expectedState) + return; + } + throw new IllegalStateException(); + } + + + protected abstract void startImpl(); + + public void stop() { + checkState(STATE_RECORDING, STATE_PAUSED); + setState(STATE_STOPPED); + stopImpl(); + mOnStateChangedListener.onStop(); + } + + protected abstract void stopImpl(); + + public void pause() { + checkState(STATE_RECORDING); + setState(STATE_PAUSED); + pauseImpl(); + mOnStateChangedListener.onPause(); + } + + protected synchronized void setState(int state) { + if (mSync) { + synchronized (this) { + mState = state; + } + } else { + mState = state; + } + } + + public synchronized int getState() { + if (mSync) { + synchronized (this) { + return mState; + } + } else { + return mState; + } + } + + protected void pauseImpl() { + + } + + public void resume() { + checkState(STATE_PAUSED); + setState(STATE_RECORDING); + resumeImpl(); + mOnStateChangedListener.onResume(); + } + + protected void resumeImpl() { + + } + + public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) { + mOnStateChangedListener = onStateChangedListener == null ? NO_OPERATION_LISTENER : onStateChangedListener; + } + + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/record/ActionRecorder.java b/app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionConverter.java similarity index 90% rename from app/src/main/java/com/stardust/scriptdroid/record/ActionRecorder.java rename to app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionConverter.java index 8cb7fe8c..9df60736 100644 --- a/app/src/main/java/com/stardust/scriptdroid/record/ActionRecorder.java +++ b/app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionConverter.java @@ -1,4 +1,4 @@ -package com.stardust.scriptdroid.record; +package com.stardust.scriptdroid.record.accessibility; import android.accessibilityservice.AccessibilityService; import android.os.Build; @@ -11,6 +11,8 @@ import com.stardust.scriptdroid.layout_inspector.NodeInfo; import com.stardust.util.SparseArrayEntries; import com.stardust.view.accessibility.AccessibilityNodeInfoHelper; +import org.greenrobot.eventbus.EventBus; + import java.util.List; @@ -18,7 +20,7 @@ import java.util.List; * Created by Stardust on 2017/2/14. */ -public class ActionRecorder { +public class AccessibilityActionConverter { private static final SparseArray CONVERTER_MAP = new SparseArrayEntries() .entry(AccessibilityEvent.TYPE_VIEW_CLICKED, new DoUtilSucceedConverter("click")) @@ -36,15 +38,22 @@ public class ActionRecorder { private StringBuilder mScript = new StringBuilder(); private boolean mFirstAction = true; + public AccessibilityActionConverter(boolean shouldIgnoreFirstAction) { + mShouldIgnoreFirstAction = shouldIgnoreFirstAction; + } + + private boolean mShouldIgnoreFirstAction = false; + public void record(AccessibilityService service, AccessibilityEvent event) { EventToScriptConverter converter = CONVERTER_MAP.get(event.getEventType()); if (converter != null) { - if (mFirstAction) { + if (mFirstAction && mShouldIgnoreFirstAction) { mFirstAction = false; return; } converter.onAccessibilityEvent(service, event, mScript); mScript.append("\n"); + EventBus.getDefault().post(new AccessibilityActionRecorder.AccessibilityActionRecordEvent(event)); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionRecorder.java b/app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionRecorder.java new file mode 100644 index 00000000..eeb9d5da --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/record/accessibility/AccessibilityActionRecorder.java @@ -0,0 +1,95 @@ +package com.stardust.scriptdroid.record.accessibility; + +import android.accessibilityservice.AccessibilityService; +import android.view.accessibility.AccessibilityEvent; + +import com.stardust.scriptdroid.record.Recorder; +import com.stardust.view.accessibility.AccessibilityDelegate; + + +/** + * Created by Stardust on 2017/2/14. + */ + +public class AccessibilityActionRecorder extends Recorder.DefaultIMPL implements AccessibilityDelegate { + + public static class AccessibilityActionRecordEvent { + + private final AccessibilityEvent mAccessibilityEvent; + + public AccessibilityActionRecordEvent(AccessibilityEvent event) { + mAccessibilityEvent = event; + } + + public AccessibilityEvent getAccessibilityEvent() { + return mAccessibilityEvent; + } + } + + private static final long RECORD_TIME_OUT = 10 * 60 * 1000; + + private static AccessibilityActionRecorder instance; + private boolean mRecordMessageEnabled; + + private boolean mShouldIgnoreFirstAction = false; + + public static AccessibilityActionRecorder getInstance() { + if (instance == null) { + instance = new AccessibilityActionRecorder(); + } + return instance; + } + + private AccessibilityActionConverter mConverter; + private long mRecordStartMillis; + + public AccessibilityActionRecorder(){ + super(true); + } + + @Override + protected void startImpl() { + mConverter = new AccessibilityActionConverter(mShouldIgnoreFirstAction); + mRecordStartMillis = System.currentTimeMillis(); + } + + @Override + protected void stopImpl() { + setState(STATE_NOT_START); + } + + @Override + protected void resumeImpl() { + mConverter.onResume(); + } + + @Override + public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) { + if (getState() == STATE_RECORDING) { + mConverter.record(service, event); + checkTimeOut(); + } + return false; + } + + private void checkTimeOut() { + if (System.currentTimeMillis() - mRecordStartMillis > RECORD_TIME_OUT) { + stop(); + } + } + + @Override + public String getCode() { + return mConverter.getScript(); + } + + public void setShouldIgnoreFirstAction(boolean shouldIgnoreFirstAction) { + mShouldIgnoreFirstAction = shouldIgnoreFirstAction; + } + + + public void setRecordMessageEnabled(boolean recordMessageEnabled) { + // TODO: 2017/3/16 添加录制提示 + mRecordMessageEnabled = recordMessageEnabled; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventConverter.java b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventConverter.java index c685b354..5fc60b1d 100644 --- a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventConverter.java +++ b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventConverter.java @@ -7,6 +7,8 @@ import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.droid.runtime.DroidRuntime; import com.stardust.scriptdroid.ui.main.MainActivity; +import org.greenrobot.eventbus.EventBus; + import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +19,21 @@ import java.util.regex.Pattern; public abstract class InputEventConverter { + public static class RecordStateChangeEvent { + + private boolean mIsRecording; + + public RecordStateChangeEvent(boolean isRecording) { + this.mIsRecording = isRecording; + } + + + public boolean isRecording() { + return mIsRecording; + } + + } + static class Event { static final Pattern PATTERN = Pattern.compile("^\\[([^\\]]*)\\]\\s+([^:]*):\\s+([^\\s]*)\\s+([^\\s]*)\\s+([^\\s]*)\\s*$"); @@ -100,11 +117,11 @@ public abstract class InputEventConverter { } public void notifyRecordStopped() { - MainActivity.onRootRecordStopped(App.getApp()); + EventBus.getDefault().post(new RecordStateChangeEvent(false)); } public void notifyRecordStarted() { - DroidRuntime.getRuntime().toast(App.getApp().getString(R.string.text_start_record)); + EventBus.getDefault().post(new RecordStateChangeEvent(true)); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventRecorder.java b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventRecorder.java index ea92d84c..e667c0bb 100644 --- a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventRecorder.java +++ b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventRecorder.java @@ -3,10 +3,9 @@ package com.stardust.scriptdroid.record.inputevent; import android.preference.PreferenceManager; import com.stardust.scriptdroid.App; +import com.stardust.scriptdroid.record.Recorder; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import jackpal.androidterm.ShellTermSession; import jackpal.androidterm.emulatorview.TermSession; @@ -16,9 +15,8 @@ import jackpal.androidterm.util.TermSettings; * Created by Stardust on 2017/3/6. */ -public abstract class InputEventRecorder { +public abstract class InputEventRecorder extends Recorder.DefaultIMPL { - private Thread mRecordThread; private TermSession mTermSession; private String mGetEventCommand; protected InputEventConverter mInputEventConverter; @@ -31,78 +29,61 @@ public abstract class InputEventRecorder { public void listen() { TermSettings settings = new TermSettings(App.getApp().getResources(), PreferenceManager.getDefaultSharedPreferences(App.getApp())); try { - mTermSession = new ShellTermSession(settings, "su\r") { - boolean enter = false; - - @Override - protected void processInput(byte[] data, int offset, int count) { - String[] lines = new String(data, offset, count).split("\n"); - for (String line : lines) { - System.out.println(line); - if (!enter && line.endsWith("data # ")) { - mTermSession.write(mGetEventCommand + "\r"); - enter = true; - } else if (enter) { - parseAndRecordEvent(line); - } - } - appendToEmulator(data, offset, count); - } - }; + mTermSession = new MyShellTermSession(settings, "su\r"); mTermSession.initializeEmulator(80, 40); mTermSession.write("su\r"); - mRecordThread = new Thread(new Runnable() { - @Override - public void run() { - try { - //readOutput(mTermSession); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - mRecordThread.start(); } catch (IOException e) { throw new RuntimeException(e); } } - public void start() { + @Override + protected void startImpl() { mInputEventConverter.start(); } - public void pause() { + @Override + protected void pauseImpl() { mInputEventConverter.pause(); } - private void readOutput(TermSession shell) { - boolean enter = false; - String line; - try { - BufferedReader succeedReader = new BufferedReader(new InputStreamReader(mTermSession.getTermIn())); - while ((line = succeedReader.readLine()) != null) { - System.out.println(line); - if (!enter && line.endsWith("data # ")) { - mTermSession.write(mGetEventCommand + "\r"); - enter = true; - } else if (enter) { - parseAndRecordEvent(line); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } + @Override + protected void resumeImpl() { + mInputEventConverter.start(); } - protected abstract void parseAndRecordEvent(String eventStr); - - public abstract String getCode(); - - public void stop() { - mRecordThread.interrupt(); + @Override + protected void stopImpl() { mTermSession.finish(); mInputEventConverter.stop(); } + + protected abstract void parseAndRecordEvent(String eventStr); + + private class MyShellTermSession extends ShellTermSession { + + private boolean mGettingEvents = false; + + public MyShellTermSession(TermSettings settings, String initialCommand) throws IOException { + super(settings, initialCommand); + } + + + @Override + protected void processInput(byte[] data, int offset, int count) { + String[] lines = new String(data, offset, count).split("\n"); + for (String line : lines) { + System.out.println(line); + if (!mGettingEvents && line.endsWith("data # ")) { + mTermSession.write(mGetEventCommand + "\r"); + mGettingEvents = true; + } else if (mGettingEvents) { + parseAndRecordEvent(line); + } + } + appendToEmulator(data, offset, count); + } + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsConverter.java b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsConverter.java index 0c173054..d9d7c030 100644 --- a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsConverter.java +++ b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsConverter.java @@ -13,7 +13,6 @@ import java.util.Map; public class InputEventToJsConverter extends InputEventConverter { - interface EventHandler { void handle(Event event); } @@ -22,10 +21,6 @@ public class InputEventToJsConverter extends InputEventConverter { private Map mEventHandlerMap = new HashMap<>(); - EventHandler getHandler(String key) { - return mEventHandlerMap.get(key); - } - Router put(String key, EventHandler handler) { mEventHandlerMap.put(key, handler); return this; @@ -112,6 +107,7 @@ public class InputEventToJsConverter extends InputEventConverter { .entry("KEY_VOLUMEDOWN", "VolumeDown") .entry("KEY_VOLUMEUP", "VolumeUp") .entry("KEY_BACK", "Back") + .entry("KEY_CAMERA", "Camera") .map(); @Override @@ -124,10 +120,10 @@ public class InputEventToJsConverter extends InputEventConverter { mSwipe = false; } } else if (event.value.equals("UP")) { - if (!mStarted && event.code.equals("KEY_VOLUMEUP")) { + if (!mStarted && event.code.equals(mStartTriggerKey)) { mStarted = true; notifyRecordStarted(); - } else if (mStarted && event.code.equals("KEY_VOLUMEDOWN")) { + } else if (mStarted && event.code.equals(mStopTriggerKey)) { mStarted = false; notifyRecordStopped(); } else { @@ -145,6 +141,7 @@ public class InputEventToJsConverter extends InputEventConverter { } private EventHandler mEventHandler = new TypeRouter(); + private String mStartTriggerKey, mStopTriggerKey; private StringBuilder mCode = new StringBuilder().append("var sh = new Shell(true);\n"); private Point mTouchPoint = new Point(), mLastTouchPoint = new Point(); private boolean mTouchDown = false, mSwipe = false; @@ -166,5 +163,13 @@ public class InputEventToJsConverter extends InputEventConverter { return mCode.toString(); } + public void setStartTriggerKey(String startTriggerKey) { + mStartTriggerKey = startTriggerKey; + } + + public void setStopTriggerKey(String stopTriggerKey) { + mStopTriggerKey = stopTriggerKey; + } + } diff --git a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsRecorder.java b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsRecorder.java index edfd6887..5efaf70d 100644 --- a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsRecorder.java +++ b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/InputEventToJsRecorder.java @@ -6,9 +6,11 @@ package com.stardust.scriptdroid.record.inputevent; public class InputEventToJsRecorder extends InputEventRecorder { + private InputEventToJsConverter mInputEventToJsConverter; public InputEventToJsRecorder() { super("getevent -t -l", new InputEventToJsConverter()); + mInputEventToJsConverter = (InputEventToJsConverter) mInputEventConverter; } @Override @@ -20,4 +22,12 @@ public class InputEventToJsRecorder extends InputEventRecorder { public String getCode() { return mInputEventConverter.getCode(); } + + public void setStartTriggerKey(String startTriggerKey) { + mInputEventToJsConverter.setStartTriggerKey(startTriggerKey); + } + + public void setStopTriggerKey(String stopTriggerKey) { + mInputEventToJsConverter.setStopTriggerKey(stopTriggerKey); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/record/inputevent/TouchRecorder.java b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/TouchRecorder.java new file mode 100644 index 00000000..e497bd6a --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/record/inputevent/TouchRecorder.java @@ -0,0 +1,26 @@ +package com.stardust.scriptdroid.record.inputevent; + +import com.stardust.scriptdroid.App; +import com.stardust.scriptdroid.Pref; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.record.Recorder; + +/** + * Created by Stardust on 2017/3/16. + */ + +public class TouchRecorder extends InputEventToJsRecorder { + + + public TouchRecorder() { + listen(); + setUpTriggers(); + } + + private void setUpTriggers() { + setStartTriggerKey(Pref.def().getString(App.getResString(R.string.key_start_record_trigger), null)); + setStopTriggerKey(Pref.def().getString(App.getResString(R.string.key_stop_record_trigger), null)); + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java b/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java index ab8b3229..5550ec1b 100644 --- a/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java +++ b/app/src/main/java/com/stardust/scriptdroid/sample/SampleFileManager.java @@ -23,15 +23,6 @@ import java.util.Map; */ public class SampleFileManager { - private static final Map SAMPLES = new MapEntries<>(new LinkedHashMap()) - .entry("sample/自动操作/sample_open_running_services.js", R.string.text_sample_open_running_services) - .entry("sample/自动操作/sample_qq_hongbao.js", R.string.text_sample_qq_hongbao) - .entry("sample/自动操作/sample_simple_calculator.js", R.string.text_sample_simple_calculator) - .entry("sample/自动操作/sample_force_qq_chat.js", R.string.text_sample_for_qq_chat) - .entry("sample/自动操作/sample_wechat_scan.js", R.string.text_sample_wechat_scan) - .entry("sample/自动操作/sample_alipay_scan.js", R.string.text_sample_alipay_scan) - .map(); - private static SampleFileManager instance = new SampleFileManager(); public static SampleFileManager getInstance() { @@ -42,33 +33,6 @@ public class SampleFileManager { return App.getApp(); } - public void copySampleScriptFileIfNeeded() { - if (!Pref.def().getBoolean(Pref.SAMPLE_SCRIPTS_COPIED, false)) { - copySampleScriptFile(); - } - } - - public int copySampleScriptFile() { - ScriptFileList list = SharedPrefScriptFileList.getInstance(); - int failCount = 0; - for (Map.Entry entry : SAMPLES.entrySet()) { - String assetFile = entry.getKey(); - String name = getContext().getString(entry.getValue()); - String path = ScriptFile.DEFAULT_FOLDER + name + ".js"; - if (!list.containsPath(path)) { - if (FileUtils.copyAsset(assetFile, path)) { - list.add(new ScriptFile(name, path)); - } else { - failCount++; - } - } - } - if (failCount < SAMPLES.size()) { - Pref.def().edit().putBoolean(Pref.SAMPLE_SCRIPTS_COPIED, true).apply(); - } - return failCount; - } - public List getSamplesFromAssets(AssetManager assets, String path) { List sampleGroups = new ArrayList<>(); try { diff --git a/app/src/main/java/com/stardust/scriptdroid/service/AccessibilityWatchDogService.java b/app/src/main/java/com/stardust/scriptdroid/service/AccessibilityWatchDogService.java index 8acdfc77..88dc1b27 100644 --- a/app/src/main/java/com/stardust/scriptdroid/service/AccessibilityWatchDogService.java +++ b/app/src/main/java/com/stardust/scriptdroid/service/AccessibilityWatchDogService.java @@ -81,7 +81,7 @@ public class AccessibilityWatchDogService extends AccessibilityService { Log.v(TAG, "onAccessibilityEvent: " + event); synchronized (mDelegates) { for (Map.Entry entry : mDelegates.entrySet()) { - Log.v(TAG, "delegate: " + entry.getValue().getClass().getName()); + //Log.v(TAG, "delegate: " + entry.getValue().getClass().getName()); if (entry.getValue().onAccessibilityEvent(this, event)) break; } diff --git a/app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeListenService.java b/app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeObverseService.java similarity index 62% rename from app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeListenService.java rename to app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeObverseService.java index c667d7a9..0ffc5da9 100644 --- a/app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeListenService.java +++ b/app/src/main/java/com/stardust/scriptdroid/service/VolumeChangeObverseService.java @@ -8,34 +8,35 @@ import android.content.IntentFilter; import android.os.IBinder; import android.support.annotation.Nullable; -import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchHandleService; -import com.stardust.scriptdroid.record.AccessibilityRecorderDelegate; +import com.stardust.scriptdroid.external.notification.record.AccessibilityActionRecordNotification; +import com.stardust.scriptdroid.record.Recorder; +import com.stardust.scriptdroid.record.accessibility.AccessibilityActionRecorder; import com.stardust.scriptdroid.Pref; import java.lang.ref.WeakReference; -import static com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchView.STOPPED; /** * Created by Stardust on 2017/2/16. */ -public class VolumeChangeListenService extends Service { +public class VolumeChangeObverseService extends Service { private static final String ACTION_VOLUME_CHANGE = "android.media.VOLUME_CHANGED_ACTION"; private VolumeChangeReceiver mVolumeChangeReceiver; - private static WeakReference instance; + private static VolumeChangeObverseService instance; public static void stopServiceIfNeeded() { - if (instance != null && instance.get() != null) { - instance.get().stopSelf(); + if (instance != null) { + instance.stopSelf(); + instance = null; } } public static void startServiceIfNeeded(Context context) { - if (Pref.isRecordVolumeControlEnable() && (instance == null || instance.get() == null)) { - context.startService(new Intent(context, VolumeChangeListenService.class)); + if (Pref.isRecordVolumeControlEnable() && instance == null) { + context.startService(new Intent(context, VolumeChangeObverseService.class)); } } @@ -43,10 +44,10 @@ public class VolumeChangeListenService extends Service { @Override public void onCreate() { super.onCreate(); - if (instance != null && instance.get() != null) { - instance.get().stopSelf(); + if (instance != null) { + instance.stopSelf(); } - instance = new WeakReference<>(this); + instance = this; mVolumeChangeReceiver = new VolumeChangeReceiver(); registerReceiver(mVolumeChangeReceiver, new IntentFilter(ACTION_VOLUME_CHANGE)); } @@ -60,6 +61,7 @@ public class VolumeChangeListenService extends Service { @Override public void onDestroy() { super.onDestroy(); + instance = null; unregisterReceiver(mVolumeChangeReceiver); } @@ -74,10 +76,10 @@ public class VolumeChangeListenService extends Service { return; } mLastChangeMillis = System.currentTimeMillis(); - if (AccessibilityRecorderDelegate.getInstance().getState() == STOPPED) { - ActionRecordSwitchHandleService.startRecord(VolumeChangeListenService.this); + if (AccessibilityActionRecorder.getInstance().getState() == Recorder.STATE_STOPPED) { + AccessibilityActionRecordNotification.startRecord(VolumeChangeObverseService.this); } else { - ActionRecordSwitchHandleService.stopRecord(VolumeChangeListenService.this); + AccessibilityActionRecordNotification.stopRecord(VolumeChangeObverseService.this); } } } diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java b/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java index 9bf1663a..7350a3ff 100644 --- a/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/BackPressedHandler.java @@ -25,7 +25,7 @@ public interface BackPressedHandler { mActivity = activity; } - public DoublePressExit setDoublePressInterval(long doublePressInterval) { + public DoublePressExit doublePressInterval(long doublePressInterval) { mDoublePressInterval = doublePressInterval; return this; } diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java index ad6db814..e41030d2 100644 --- a/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/ImageSelector.java @@ -16,15 +16,15 @@ import com.stardust.scriptdroid.R; public class ImageSelector implements OnActivityResultDelegate { public interface ImageSelectorCallback { - void onImageSelected(String path); + void onImageSelected(ImageSelector selector, String path); } private static final int REQUEST_CODE = "LOVE EATING".hashCode() >> 16; private Activity mActivity; private ImageSelectorCallback mCallback; - public ImageSelector(Activity activity, Manager manager, ImageSelectorCallback callback) { - manager.addDelegate(REQUEST_CODE, this); + public ImageSelector(Activity activity, Intermediary intermediary, ImageSelectorCallback callback) { + intermediary.addDelegate(REQUEST_CODE, this); mActivity = activity; mCallback = callback; } @@ -48,7 +48,7 @@ public class ImageSelector implements OnActivityResultDelegate { int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String filePath = cursor.getString(columnIndex); cursor.close(); - mCallback.onImageSelected(filePath); + mCallback.onImageSelected(this, filePath); } diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/Shell.java b/app/src/main/java/com/stardust/scriptdroid/tool/Shell.java index c218f38b..879dbcbb 100644 --- a/app/src/main/java/com/stardust/scriptdroid/tool/Shell.java +++ b/app/src/main/java/com/stardust/scriptdroid/tool/Shell.java @@ -38,49 +38,6 @@ public class Shell { private StringBuilder mSucceedOutput = new StringBuilder(); private StringBuilder mErrorOutput = new StringBuilder(); - public static String bytesToString(byte[] data, int base, int length) { - StringBuilder buf = new StringBuilder(); - - for(int i = 0; i < length; ++i) { - byte b = data[base + i]; - if(b >= 32 && b <= 126) { - buf.append((char)b); - } else { - buf.append(String.format("\\x%02x", new Object[]{Byte.valueOf(b)})); - } - } - - return buf.toString(); - } - - - public static void test(final Activity activity) { - TermSettings settings = new TermSettings(App.getApp().getResources(), PreferenceManager.getDefaultSharedPreferences(App.getApp())); - try { - final ShellTermSession termSession = new ShellTermSession(settings, ""); - termSession.initializeEmulator(80, 40); - termSession.write("su\r"); - termSession.write("getevent\r"); - new Thread(new Runnable() { - @Override - public void run() { - BufferedReader reader = new BufferedReader(new InputStreamReader(termSession.getTermIn())); - try { - while (reader.ready()){ - String line = reader.readLine(); - System.out.println(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - }).start(); - } catch (IOException e) { - e.printStackTrace(); - } - - } - public Shell() { this(false); } @@ -97,7 +54,6 @@ public class Shell { } public Shell execute(String command) { - try { mCommandOutputStream.writeBytes(command); if (!command.endsWith(COMMAND_LINE_END)) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java index 717d74a6..6d69f232 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java @@ -38,6 +38,9 @@ import com.stardust.theme.ThemeColorManager; import com.stardust.view.ViewBinder; import java.io.File; +import java.util.Timer; + +import timber.log.Timber; /** * Created by Stardust on 2017/1/29. @@ -98,8 +101,10 @@ public class EditActivity extends Editor920Activity { if (intent.getAction().equals(ACTION_ON_RUN_FINISHED)) { setMenuStatus(R.id.run, MenuDef.STATUS_NORMAL); String msg = intent.getStringExtra(EXTRA_EXCEPTION_MESSAGE); - if (msg != null) + if (msg != null) { Snackbar.make(mView, getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG).show(); + Timber.e(msg); + } } } }; diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java index 41304f59..b07dd5d3 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java @@ -101,11 +101,16 @@ public class CodeCompletion implements TextWatcher { private static final String[] KEYWORDS = {"arguments", "break", "case", "catch", "class", "continue", "default", "do", "else", "eval", "export", "false", "for", "function", "if", "import", "in", "int", "new", "null", "package", "return", "switch", "this", "throw", "throws", "true", "try", "typeof", "var", "volatile", "while", "with", "Array", "Date", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "name", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"}; private static final int KEY_WORD_LENGTH_MAX = 15; - private static final String[] FUNCTIONS = {"toast", "launchPackage", "launch", "launchApp", "click", "longClick", "scrollUp", "scrollDown", "select", "focus", "paste", "input", "sleep", "isStopped", "notStopped", "log", "err", "openConsole", "clearConsole", "shell", "getTexts", "getPackageName", "getActivityName", "setClip", "addAccessibilityDelegate"}; + + public void setFunctions(String[] functions) { + mFunctions = functions; + } + + private String[] mFunctions = {"toast", "launchPackage", "launch", "launchApp", "click", "longClick", "scrollUp", "scrollDown", "select", "focus", "paste", "input", "sleep", "isStopped", "notStopped", "log", "err", "openConsole", "clearConsole", "shell", "getTexts", "getPackageName", "getActivityName", "setClip", "addAccessibilityDelegate"}; private boolean searchCodeCompletion(String str) { Collection c = searchWordCompletion(str); - c.addAll(searchCodeCompletion(str, FUNCTIONS)); + c.addAll(searchCodeCompletion(str, mFunctions)); c.addAll(searchKeyWordCompletion(str)); if (c.size() > 0) { mOnCodeCompletionChangeListener.OnCodeCompletionChange(c, DEFAULT_CODE_COMPLETION_LIST); diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java index d2d0e124..e0eda3ab 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java @@ -14,6 +14,7 @@ import android.widget.TextView; import android.widget.Toast; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.droid.script.JavaScriptEngine; import java.util.ArrayList; import java.util.Collection; @@ -73,6 +74,7 @@ public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletio private void init() { setAdapter(new CodeCompletionAdapter()); setLayoutManager(new LinearLayoutManager(getContext(), HORIZONTAL, false)); + mCodeCompletion.setFunctions(JavaScriptEngine.getDefault().getGlobalFunctions()); } public void setEditTextBridge(EditTextBridge editTextBridge) { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java index 0ecbecac..214e4736 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/editor920/Editor920Activity.java @@ -11,6 +11,8 @@ import com.jecelyin.editor.v2.ui.IActivity; import com.jecelyin.editor.v2.ui.TabManager; import java.io.File; +import java.lang.reflect.Field; +import java.util.Map; /** * Created by Stardust on 2017/1/30. @@ -21,6 +23,18 @@ public class Editor920Activity extends IActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Pref.getInstance(this).setReadOnly(false); + setPref(Pref.KEY_PREF_KEEP_BACKUP_FILE, false); + + } + + private void setPref(String key, Object value) { + try { + Field field = Pref.class.getDeclaredField("map"); + field.setAccessible(true); + ((Map) field.get(Pref.getInstance(this))).put(key, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } } @Override diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java index 83128fdd..9cf500e7 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/error/IssueReportActivity.java @@ -66,11 +66,19 @@ public class IssueReportActivity extends IssueReporterActivity { final String errorDetail = getIntent().getStringExtra("error"); if (errorDetail != null) { ((EditText) findViewById(R.id.air_inputDescription)).setText(errorDetail); - ((EditText) findViewById(R.id.air_inputTitle)).setText(R.string.text_crash_en); + String title = getFirstLine(errorDetail); + ((EditText) findViewById(R.id.air_inputTitle)).setText(title); mCrash = true; } } + private String getFirstLine(String str) { + int i = str.indexOf('\n'); + if(i < 0) + return str; + return str.substring(0, i); + } + private boolean validateInput() { if (mValidateInput == null) { try { diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java index 60709df6..23fb1d72 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/help/DocumentationActivity.java @@ -9,12 +9,17 @@ import com.stardust.widget.CommonMarkdownView; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.tool.FileUtils; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; /** * Created by Stardust on 2017/2/1. */ + public class DocumentationActivity extends BaseActivity { private CommonMarkdownView mCommonMarkdownView; diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java index f7c55f3e..16dbac3c 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java @@ -33,7 +33,7 @@ import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.droid.script.file.ScriptFile; import com.stardust.scriptdroid.droid.script.file.ScriptFileList; import com.stardust.scriptdroid.droid.script.file.SharedPrefScriptFileList; -import com.stardust.scriptdroid.external.notification.record.ActionRecordSwitchNotification; +import com.stardust.scriptdroid.external.notification.record.AccessibilityActionRecordNotification; import com.stardust.scriptdroid.record.inputevent.InputEventRecorder; import com.stardust.scriptdroid.record.inputevent.InputEventToJsRecorder; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; @@ -68,12 +68,14 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private static final String ARGUMENT_SCRIPT = "ARGUMENT_SCRIPT"; private static final String ACTION_ON_ROOT_RECORD_STOPPED = "ACTION_ON_ROOT_RECORD_STOPPED"; - private SlidingUpPanel mAddFilePanel; private ScriptFileList mScriptFileList; + private DrawerLayout mDrawerLayout; + @ViewBinding.Id(R.id.bottom_menu) + private SlidingUpPanel mAddBottomMenuPanel; + private MyScriptListFragment mMyScriptListFragment; private SampleScriptListFragment mSampleScriptListFragment; - private DrawerLayout mDrawerLayout; - private OnActivityResultDelegate.Manager mManager = new OnActivityResultDelegate.Manager(); + private OnActivityResultDelegate.Intermediary mActivityResultIntermediary = new OnActivityResultDelegate.Intermediary(); private BackPressedHandler.Observer mBackPressObserver = new BackPressedHandler.Observer(); @Override @@ -113,7 +115,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private void addScriptFile(final String path) { new ThemeColorMaterialDialogBuilder(this).title(R.string.text_name) .inputType(InputType.TYPE_CLASS_TEXT) - .input(getString(R.string.text_please_input_name), new File(path).getName(), new MaterialDialog.InputCallback() { + .input(getString(R.string.text_please_input_name), FileUtils.getNameWithoutExtension(path), new MaterialDialog.InputCallback() { @Override public void onInput(@NonNull MaterialDialog dialog, CharSequence input) { MainActivity.this.addScriptFile(input.toString(), path); @@ -130,7 +132,6 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File mDrawerLayout = (DrawerLayout) View.inflate(this, R.layout.activity_main, null); setContentView(mDrawerLayout); setUpFragment(); - mAddFilePanel = $(R.id.bottom_menu); setUpToolbar(); setUpTabLayout(); setUpDrawerHeader(); @@ -141,7 +142,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File private void setUpFragment() { SlideMenuFragment.setFragment(this, R.id.fragment_slide_menu); mMyScriptListFragment = new MyScriptListFragment(); - mScriptFileList = SharedPrefScriptFileList.getInstance(); + mScriptFileList = ScriptFileList.getImpl(); mSampleScriptListFragment = new SampleScriptListFragment(); } @@ -188,7 +189,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @ViewBinding.Click(R.id.add) private void showAddFilePanel() { - mAddFilePanel.show(); + mAddBottomMenuPanel.show(); } @ViewBinding.Click(R.id.create_new_file) @@ -238,7 +239,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File Snackbar.make(mDrawerLayout, R.string.text_need_enable_accessibility_service, Snackbar.LENGTH_SHORT).show(); return; } - ActionRecordSwitchNotification.showOrUpdateNotification(); + AccessibilityActionRecordNotification.showOrUpdateNotification(); Snackbar.make(mDrawerLayout, R.string.hint_start_record, Snackbar.LENGTH_SHORT).show(); } @@ -265,11 +266,12 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @ViewBinding.Click(R.id.drawer_header_img) public void selectHeaderImage() { - new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() { + new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() { @Override - public void onImageSelected(String path) { + public void onImageSelected(ImageSelector selector, String path) { setDrawerHeaderImage(path); Pref.def().edit().putString(Pref.KEY_DRAWER_HEADER_IMAGE_PATH, path).apply(); + mActivityResultIntermediary.removeDelegate(selector); } }).select(); } @@ -334,8 +336,8 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @Override public void onBackPressed() { - if (mAddFilePanel.isShowing()) { - mAddFilePanel.dismiss(); + if (mAddBottomMenuPanel.isShowing()) { + mAddBottomMenuPanel.dismiss(); } else if (mDrawerLayout.isDrawerOpen(Gravity.START)) { mDrawerLayout.closeDrawer(Gravity.START); } else { @@ -350,7 +352,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - mManager.onActivityResult(requestCode, resultCode, data); + mActivityResultIntermediary.onActivityResult(requestCode, resultCode, data); } public static void onActionRecordStopped(Context context, String script) { @@ -370,11 +372,12 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File } public void OnAppBarClick(View view) { - new ImageSelector(this, mManager, new ImageSelector.ImageSelectorCallback() { + new ImageSelector(this, mActivityResultIntermediary, new ImageSelector.ImageSelectorCallback() { @Override - public void onImageSelected(String path) { + public void onImageSelected(ImageSelector selector, String path) { Pref.def().edit().putString(Pref.KEY_APP_BAR_IMAGE_PATH, path).apply(); setAppBarImage(path); + mActivityResultIntermediary.removeDelegate(selector); } }).select(); } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java index 652429c6..4dae45ae 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/MyScriptListFragment.java @@ -39,8 +39,7 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler @Nullable @Override public View createView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_my_script_list, container, false); - return view; + return inflater.inflate(R.layout.fragment_my_script_list, container, false); } @Override @@ -55,7 +54,7 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler private void setUpScriptList() { mScriptListRecyclerView = $(R.id.script_list); - mScriptFileList = SharedPrefScriptFileList.getInstance(); + mScriptFileList = ScriptFileList.getImpl(); mNoScriptHint = $(R.id.hint_no_script); mScriptListRecyclerView.getAdapter().registerAdapterDataObserver(new SimpleAdapterDataObserver() { @Override @@ -91,6 +90,18 @@ public class MyScriptListFragment extends Fragment implements BackPressedHandler } } + @Override + public void onResume() { + super.onResume(); + EventBus.getDefault().register(mScriptListRecyclerView); + } + + @Override + public void onPause() { + super.onPause(); + EventBus.getDefault().unregister(mScriptListRecyclerView); + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java index 451b259e..62c7e913 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/my_script_list/ScriptListRecyclerView.java @@ -139,18 +139,6 @@ public class ScriptListRecyclerView extends ThemeColorRecyclerView { Snackbar.make(this, event.messageResId, Snackbar.LENGTH_SHORT).show(); } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - EventBus.getDefault().register(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - EventBus.getDefault().unregister(this); - } - private class Adapter extends RecyclerView.Adapter { @Override diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java index 06532372..a9f99d9b 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListFragment.java @@ -2,13 +2,27 @@ package com.stardust.scriptdroid.ui.main.sample_list; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.app.Fragment; import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.droid.script.file.ScriptFile; +import com.stardust.scriptdroid.droid.script.file.ScriptFileList; +import com.stardust.scriptdroid.sample.Sample; import com.stardust.scriptdroid.sample.SampleFileManager; +import com.stardust.scriptdroid.tool.FileUtils; +import com.stardust.scriptdroid.ui.edit.EditActivity; +import com.stardust.scriptdroid.ui.main.my_script_list.MyScriptListFragment; +import com.stardust.util.MessageEvent; + +import org.greenrobot.eventbus.EventBus; /** * Created by Stardust on 2017/3/13. @@ -28,7 +42,47 @@ public class SampleScriptListFragment extends Fragment { protected void afterCreateView() { mSampleScriptListRecyclerView = $(R.id.script_list); mSampleScriptListRecyclerView.setSamples(SampleFileManager.getInstance().getSamplesFromAssets(getContext().getAssets(), "sample")); + mSampleScriptListRecyclerView.setOnItemLongClickListener(new SampleScriptListRecyclerView.OnItemLongClickListener() { + @Override + public void onItemLongClick(Sample sample) { + showMenuDialog(sample); + } + }); + mSampleScriptListRecyclerView.setOnItemClickListener(new SampleScriptListRecyclerView.OnItemClickListener() { + @Override + public void onItemClick(Sample sample) { + viewSample(sample); + } + }); + } + private void showMenuDialog(final Sample sample) { + new MaterialDialog.Builder(getActivity()) + .title(sample.name) + .items(getString(R.string.text_copy_to_my_scripts)) + .itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text) { + copySampleToMyScripts(sample); + } + + }) + .show(); + } + + private void copySampleToMyScripts(Sample sample) { + String path = ScriptFile.DEFAULT_FOLDER + sample.name + ".js"; + if (!ScriptFileList.getImpl().containsPath(path) && FileUtils.copyAsset(sample.path, path)) { + ScriptFileList.getImpl().add(new ScriptFile(sample.name, path)); + EventBus.getDefault().post(new MessageEvent(MyScriptListFragment.MESSAGE_SCRIPT_FILE_ADDED)); + Snackbar.make(mSampleScriptListRecyclerView, R.string.text_import_succeed, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(mSampleScriptListRecyclerView, R.string.text_file_exists, Snackbar.LENGTH_SHORT).show(); + } + } + + private void viewSample(Sample sample) { + EditActivity.editAssetFile(getContext(), sample.name, sample.path); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java index d11cd2de..771cd8f7 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/main/sample_list/SampleScriptListRecyclerView.java @@ -29,14 +29,36 @@ import java.util.List; public class SampleScriptListRecyclerView extends RecyclerView { + public interface OnItemLongClickListener { + void onItemLongClick(Sample sample); + } - private OnClickListener mOnItemClickListener = new OnClickListener() { + public interface OnItemClickListener { + void onItemClick(Sample sample); + } + + private OnClickListener mOnItemClickListenerProxy = new OnClickListener() { @Override public void onClick(View v) { - SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v); - viewSample(viewHolder.getChild()); + if (mOnItemClickListener != null) { + SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v); + mOnItemClickListener.onItemClick(viewHolder.getChild()); + } } }; + private OnLongClickListener mOnLongClickListenerProxy = new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mOnItemLongClickListener != null) { + SampleViewHolder viewHolder = (SampleViewHolder) getChildViewHolder(v); + mOnItemLongClickListener.onItemLongClick(viewHolder.getChild()); + return true; + } + return false; + } + }; + private OnItemLongClickListener mOnItemLongClickListener; + private OnItemClickListener mOnItemClickListener; private Adapter mAdapter; @@ -57,6 +79,14 @@ public class SampleScriptListRecyclerView extends RecyclerView { init(); } + public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { + mOnItemLongClickListener = onItemLongClickListener; + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + mOnItemClickListener = onItemClickListener; + } + public void setSamples(List samples) { mSampleGroups.clear(); for (com.stardust.scriptdroid.sample.SampleGroup sampleGroup : samples) { @@ -71,12 +101,6 @@ public class SampleScriptListRecyclerView extends RecyclerView { addItemDecoration(new DividerItemDecoration(getContext(), VERTICAL)); } - - - private void viewSample(Sample sample) { - EditActivity.editAssetFile(getContext(), sample.name, sample.path); - } - private class SampleGroup implements Parent { private com.stardust.scriptdroid.sample.SampleGroup mSampleGroup; @@ -108,7 +132,8 @@ public class SampleScriptListRecyclerView extends RecyclerView { super(itemView); name = (TextView) itemView.findViewById(R.id.name); ((LevelBeamView) itemView.findViewById(R.id.level)).setLevel(2); - itemView.setOnClickListener(mOnItemClickListener); + itemView.setOnClickListener(mOnItemClickListenerProxy); + itemView.setOnLongClickListener(mOnLongClickListenerProxy); } void bind(Sample sample) { diff --git a/app/src/main/java/com/stardust/view/ViewBinder.java b/app/src/main/java/com/stardust/view/ViewBinder.java index 237b2c0b..2ac04a9d 100644 --- a/app/src/main/java/com/stardust/view/ViewBinder.java +++ b/app/src/main/java/com/stardust/view/ViewBinder.java @@ -3,11 +3,14 @@ package com.stardust.view; import android.view.View; import android.widget.CompoundButton; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by Stardust on 2017/1/30. + * + * 哈?你说我为什么不用AA框架?之前还不知道嘛所以现在用了。 */ public class ViewBinder { @@ -21,6 +24,7 @@ public class ViewBinder { throw new RuntimeException("You must implement findViewById to use view binding", e); } Method[] methods = o.getClass().getDeclaredMethods(); + bindId(o, findViewById); for (Method method : methods) { method.setAccessible(true); bindClick(o, method, findViewById); @@ -28,6 +32,20 @@ public class ViewBinder { } } + private static void bindId(Object o, Method findViewById) { + 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) { + throw new RuntimeException(e); + } + } + } + private static void bindCheck(final Object o, final Method method, Method findViewById) { ViewBinding.Check annotation = method.getAnnotation(ViewBinding.Check.class); if (annotation == null || annotation.value() == 0) diff --git a/app/src/main/java/com/stardust/view/ViewBinding.java b/app/src/main/java/com/stardust/view/ViewBinding.java index a9cc1194..8efe994f 100644 --- a/app/src/main/java/com/stardust/view/ViewBinding.java +++ b/app/src/main/java/com/stardust/view/ViewBinding.java @@ -32,4 +32,10 @@ public @interface ViewBinding { @interface Check { int value(); } + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @interface Id { + int value(); + } } diff --git a/app/src/main/res/drawable/ic_pause_white_24dp.png b/app/src/main/res/drawable/ic_pause_white_24dp.png new file mode 100644 index 00000000..f49aed75 Binary files /dev/null and b/app/src/main/res/drawable/ic_pause_white_24dp.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 38144418..154b5c4f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -124,6 +124,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:visibility="gone" android:background="?attr/selectableItemBackgroundBorderless" android:gravity="center" android:orientation="vertical"> @@ -148,6 +149,7 @@ android:id="@+id/root_record" android:layout_width="0dp" android:layout_height="wrap_content" + android:visibility="gone" android:layout_weight="1" android:background="?attr/selectableItemBackgroundBorderless" android:gravity="center" diff --git a/app/src/main/res/layout/floating_window_main_menu.xml b/app/src/main/res/layout/floating_window_main_menu.xml index 230f7866..8c6a7c45 100644 --- a/app/src/main/res/layout/floating_window_main_menu.xml +++ b/app/src/main/res/layout/floating_window_main_menu.xml @@ -49,4 +49,47 @@ android:layout_width="match_parent" android:layout_height="1dp" android:background="#c4c4c4"/> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/floating_window_record.xml b/app/src/main/res/layout/floating_window_record.xml new file mode 100644 index 00000000..483a7b0e --- /dev/null +++ b/app/src/main/res/layout/floating_window_record.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/licenses.xml b/app/src/main/res/raw/licenses.xml index bb1834dc..d467f748 100644 --- a/app/src/main/res/raw/licenses.xml +++ b/app/src/main/res/raw/licenses.xml @@ -75,13 +75,13 @@ hover 2016 (C) Copyright Open-RnD Sp. z o.o. - https://github.com/open-rnd/android-multi-level-listview + https://github.com/google/hover Apache Software License 2.0 event-bus 2016 (C) Copyright Open-RnD Sp. z o.o. - https://github.com/open-rnd/android-multi-level-listview + https://github.com/greenrobot/EventBus Apache Software License 2.0 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 90aaf885..297bbbd1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,6 @@ #009688 #8ea22a #00796B - #FF4081 + #536DFE #99444444 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 78ea63b9..b540df42 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,7 +76,7 @@ 选择一个文件 /脚本/ 错误 - 文档加载失败/(ㄒoㄒ)/~~ + 还没有文档/(ㄒoㄒ)/~~ 复制调试信息 这是软件开发者(。・・)ノ 略略略 @@ -114,7 +114,7 @@ 文件写入失败 需要打开\"自动操作服务“才能录制脚本 加入QQ交流群 - 已复制剪贴板 + 已复制到剪贴板 关闭 重新录制 使用音量键控制 @@ -147,4 +147,36 @@ 点击右上角新建第一个脚本吧~(。・・)ノ 重置背景图片设置 重启软件生效 + 使用root权限录制 + 录制提示 + 文字改变 + 滑动 + 长按 + 点击 + 打开主界面 + 导入到我的脚本 + 导入成功 + 免Root脚本录制 + Root脚本录制 + 开始录制按键 + 结束录制按键 + NONE + key_start_record_trigger + key_stop_record_trigger + 当前活动: + 当前应用包名: + + + + 音量上键 + 音量下键 + 照相键 + + + + NONE + KEY_VOLUMEUP + KEY_VOLUMEDOWN + KEY_CAMERA + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index bf765c31..a114f788 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,7 +1,7 @@ - + + + + + + + + Testing documentation */ public class ExampleUnitTest { - // TODO: 2017/3/3 引擎兼容性 - // TODO: 2017/3/3 更多的示例 - // TODO: 2017/3/3 自定义函数 - // TODO: 2017/3/3 + // TODO: 2017/3/3 自定义函数 @Test public void test() { - int i = 0xffffffff; - long l = i & 0xffffffffL; - System.out.println(l); + Context context = Context.enter(); + context.setOptimizationLevel(-1); + context.setLanguageVersion(Context.VERSION_1_7); + context.setInstructionObserverThreshold(10000); + ImporterTopLevel importerTopLevel = new ImporterTopLevel(); + importerTopLevel.initStandardObjects(context, false); + context.evaluateString(importerTopLevel, "var a = 1;var b = function(){};", "", 1, null); + String[] ids = (String[]) importerTopLevel.getIds(); + System.out.println(ids[0].getClass()); + System.out.println(Arrays.toString(ids)); + for(Object id : ids){ + System.out.println(importerTopLevel.get(id)); + } } } \ No newline at end of file diff --git a/automator/src/main/java/com/stardust/automator/AccessibilityEventCommandHost.java b/automator/src/main/java/com/stardust/automator/AccessibilityEventCommandHost.java index 5c0f584e..0890b293 100644 --- a/automator/src/main/java/com/stardust/automator/AccessibilityEventCommandHost.java +++ b/automator/src/main/java/com/stardust/automator/AccessibilityEventCommandHost.java @@ -15,12 +15,18 @@ import java.util.Queue; public class AccessibilityEventCommandHost implements AccessibilityDelegate { + + public interface Command { void execute(AccessibilityService service, AccessibilityEvent event); } - public static final AccessibilityEventCommandHost instance = new AccessibilityEventCommandHost(); + private static final AccessibilityEventCommandHost instance = new AccessibilityEventCommandHost(); + + public static AccessibilityEventCommandHost getInstance() { + return instance; + } private static final String TAG = "CommandHostDelegate"; diff --git a/automator/src/main/java/com/stardust/automator/UiObject.java b/automator/src/main/java/com/stardust/automator/UiObject.java index 2ffeb3b5..fd3cb2c8 100644 --- a/automator/src/main/java/com/stardust/automator/UiObject.java +++ b/automator/src/main/java/com/stardust/automator/UiObject.java @@ -33,6 +33,14 @@ public class UiObject extends AccessibilityNodeInfoCompat { return new UiObject(getChild(i).getInfo()); } + public UiObjectCollection find(UiGlobalSelector selector) { + return selector.findOf((AccessibilityNodeInfo) getInfo()); + } + + public UiObject findOne(UiGlobalSelector selector) { + return selector.findOneOf((AccessibilityNodeInfo) getInfo()); + } + public UiObjectCollection children() { ArrayList list = new ArrayList<>(getChildCount()); for (int i = 0; i < getChildCount(); i++) { @@ -58,7 +66,6 @@ public class UiObject extends AccessibilityNodeInfoCompat { } public CharSequence packageName() { - return getPackageName(); } diff --git a/automator/src/main/java/com/stardust/automator/UiObjectCollection.java b/automator/src/main/java/com/stardust/automator/UiObjectCollection.java index 5c64d6e9..76b0b637 100644 --- a/automator/src/main/java/com/stardust/automator/UiObjectCollection.java +++ b/automator/src/main/java/com/stardust/automator/UiObjectCollection.java @@ -7,6 +7,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import com.stardust.util.Consumer; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.*; @@ -25,7 +26,6 @@ import static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat. public class UiObjectCollection { - public static UiObjectCollection ofCompat(List list) { return new UiObjectCollection(list); } @@ -187,6 +187,7 @@ public class UiObjectCollection { return mUiObjects[i]; } + private void ensureUiObjectAt(int i) { if (mUiObjects == null) { mUiObjects = new UiObject[mNodes.size()]; diff --git a/automator/src/test/java/com/stardust/automator/ExampleUnitTest.java b/automator/src/test/java/com/stardust/automator/ExampleUnitTest.java index 9b67e597..92a7cd0d 100644 --- a/automator/src/test/java/com/stardust/automator/ExampleUnitTest.java +++ b/automator/src/test/java/com/stardust/automator/ExampleUnitTest.java @@ -1,5 +1,6 @@ package com.stardust.automator; + import org.junit.Test; import static org.junit.Assert.*; @@ -12,6 +13,6 @@ import static org.junit.Assert.*; public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); + } } \ No newline at end of file diff --git a/projectFilesBackup/.idea/workspace.xml b/projectFilesBackup/.idea/workspace.xml deleted file mode 100644 index ef994cff..00000000 --- a/projectFilesBackup/.idea/workspace.xml +++ /dev/null @@ -1,3898 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Encapsulation issuesJava - - - Java - - - - - Android - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C:\Users\Stardust\AppData\Roaming\Subversion - - - - - 1485137799315 - - - 1485656163163 - - - 1485745863661 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - file://$PROJECT_DIR$/app/src/main/java/com/stardust/scriptdroid/ui/main/operation/ScriptFileOperationPopupMenu.java - 132 - - - - file://$PROJECT_DIR$/app/src/main/java/com/stardust/scriptdroid/ui/main/MainActivity.java - 81 - - - - file://$PROJECT_DIR$/app/src/cpp/input_recorder.c - 25 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/screen-captures/ss1.png b/screen-captures/ss1.png index d396109a..357e398e 100644 Binary files a/screen-captures/ss1.png and b/screen-captures/ss1.png differ diff --git a/screen-captures/ss2.png b/screen-captures/ss2.png index 63003375..67c9a134 100644 Binary files a/screen-captures/ss2.png and b/screen-captures/ss2.png differ diff --git a/screen-captures/ss3.png b/screen-captures/ss3.png index b6d7720b..1a0ae7b2 100644 Binary files a/screen-captures/ss3.png and b/screen-captures/ss3.png differ diff --git a/screen-captures/ss4.png b/screen-captures/ss4.png index d499b2d7..e5f9429b 100644 Binary files a/screen-captures/ss4.png and b/screen-captures/ss4.png differ diff --git a/screen-captures/ss5.png b/screen-captures/ss5.png new file mode 100644 index 00000000..a0656709 Binary files /dev/null and b/screen-captures/ss5.png differ diff --git a/screen-captures/ss6.png b/screen-captures/ss6.png new file mode 100644 index 00000000..442e8646 Binary files /dev/null and b/screen-captures/ss6.png differ diff --git a/shell/.gitignore b/shell/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/shell/.gitignore @@ -0,0 +1 @@ +/build diff --git a/shell/build.gradle b/shell/build.gradle new file mode 100644 index 00000000..04adf8ef --- /dev/null +++ b/shell/build.gradle @@ -0,0 +1,39 @@ +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' + } + } +} + +repositories { + flatDir { + dirs 'libs' + } +} + +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(name: 'libtermexec-release', ext: 'aar') + compile(name: 'emulatorview-release', ext: 'aar') + compile(name: 'term-debug', ext: 'aar') + testCompile 'junit:junit:4.12' +} diff --git a/shell/proguard-rules.pro b/shell/proguard-rules.pro new file mode 100644 index 00000000..27b6816c --- /dev/null +++ b/shell/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/shell/src/androidTest/java/com/stardust/shell/ExampleInstrumentedTest.java b/shell/src/androidTest/java/com/stardust/shell/ExampleInstrumentedTest.java new file mode 100644 index 00000000..18eab70e --- /dev/null +++ b/shell/src/androidTest/java/com/stardust/shell/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.stardust.shell; + +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.stardust.shell.test", appContext.getPackageName()); + } +} diff --git a/shell/src/main/AndroidManifest.xml b/shell/src/main/AndroidManifest.xml new file mode 100644 index 00000000..97f203e3 --- /dev/null +++ b/shell/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/shell/src/main/res/values/strings.xml b/shell/src/main/res/values/strings.xml new file mode 100644 index 00000000..2bdb4fc7 --- /dev/null +++ b/shell/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + shell + diff --git a/shell/src/test/java/com/stardust/shell/ExampleUnitTest.java b/shell/src/test/java/com/stardust/shell/ExampleUnitTest.java new file mode 100644 index 00000000..8f3be516 --- /dev/null +++ b/shell/src/test/java/com/stardust/shell/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.stardust.shell; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file