Added: Record with root permission

This commit is contained in:
hyb1996
2017-03-08 16:52:08 +08:00
parent 38c22646e0
commit a211d208d0
41 changed files with 776 additions and 391 deletions

View File

@@ -159,7 +159,10 @@ var Tap = function(x, y){
return shell("input tap " + x + " " + y, true).code == 1;
}
var Swipe = function(x1, y1, x2, y2){
var Swipe = function(x1, y1, x2, y2, duration){
if(arguments.length == 5){
return shell("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + duration, true).code == 1;
}
return shell("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2, true).code == 1;
}
@@ -219,6 +222,8 @@ var Text = function(text){
return shell("input text " + text, true).code == 1;
}
__importClassOld__(com.stardust.scriptdroid.droid.runtime.Shell);
/*
importClass("com.stardust.scriptdroid.service.AccessibilityDelegate");

View File

@@ -2,7 +2,7 @@
importClass("com.afollestad.materialdialogs.MaterialDialog");
new ThemeColorMaterialDialogBuilder(activity)
new MaterialDialog.Builder(activity)
.title("简单计算器")
.input("请输入算式", "1+1", function(dialog, input){
eval("var ans = (" + input + ")");

View File

@@ -15,9 +15,6 @@ import com.stardust.theme.ThemeColorManager;
import com.stardust.util.CrashHandler;
import com.stardust.util.StateObserver;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import java.lang.ref.WeakReference;
/**

View File

@@ -25,7 +25,6 @@ import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.scriptdroid.droid.runtime.action.GetTextAction;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import com.stardust.scriptdroid.file.FileUtils;
import com.stardust.scriptdroid.service.AccessibilityDelegate;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
@@ -45,7 +44,7 @@ import timber.log.Timber;
* Created by Stardust on 2017/1/27.
*/
public class DroidRuntime implements IDroidRuntime {
public class DroidRuntime {
private static final String TAG = "DroidRuntime";
private static DroidRuntime runtime = new DroidRuntime();
@@ -61,18 +60,15 @@ public class DroidRuntime implements IDroidRuntime {
mUIHandler = new Handler(App.getApp().getMainLooper());
}
@Override
public void launchPackage(String packageName) {
PackageManager packageManager = App.getApp().getPackageManager();
App.getApp().startActivity(packageManager.getLaunchIntentForPackage(packageName));
}
@Override
public void launch(String packageName, String className) {
App.getApp().startActivity(new Intent().setClassName(packageName, className).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
@Override
public void launchApp(String appName) {
PackageManager packageManager = App.getApp().getPackageManager();
List<ApplicationInfo> installedApplications = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
@@ -84,22 +80,18 @@ public class DroidRuntime implements IDroidRuntime {
}
}
@Override
public String[] getPackageName(String appName) {
return new String[0];
}
@Override
public ActionTarget text(String text, int i) {
return new ActionTarget.TextActionTarget(text, i);
}
@Override
public ActionTarget bounds(int left, int top, int right, int bottom) {
return new ActionTarget.BoundsActionTarget(new Rect(left, top, right, bottom));
}
@Override
public ActionTarget editable(int i) {
return new ActionTarget.EditableActionTarget(i);
}
@@ -108,32 +100,26 @@ public class DroidRuntime implements IDroidRuntime {
return new ActionTarget.IdActionTarget(id);
}
@Override
public boolean click(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
}
@Override
public boolean longClick(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_LONG_CLICK));
}
@Override
public boolean scrollUp(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
@Override
public boolean scrollDown(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
@Override
public boolean scrollUp(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, i));
}
@Override
public boolean scrollDown(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, i));
}
@@ -146,17 +132,14 @@ public class DroidRuntime implements IDroidRuntime {
return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
@Override
public boolean focus(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_FOCUS));
}
@Override
public boolean select(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SELECT));
}
@Override
public boolean setText(ActionTarget target, String text) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SET_TEXT, text));
}
@@ -170,7 +153,6 @@ public class DroidRuntime implements IDroidRuntime {
});
}
@Override
public boolean paste(ActionTarget target) {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
}
@@ -230,7 +212,6 @@ public class DroidRuntime implements IDroidRuntime {
return performAction(new GetTextAction());
}
@Override
public void toast(final String text) {
mUIHandler.post(new Runnable() {
@Override
@@ -240,7 +221,6 @@ public class DroidRuntime implements IDroidRuntime {
});
}
@Override
public void sleep(long millis) {
try {
Thread.sleep(millis);
@@ -275,12 +255,10 @@ public class DroidRuntime implements IDroidRuntime {
return ActionPerformAccessibilityDelegate.getLatestActivity();
}
@Override
public boolean isStopped() {
return Thread.currentThread().isInterrupted();
}
@Override
public void stop() {
Thread.interrupted();
}
@@ -289,7 +267,6 @@ public class DroidRuntime implements IDroidRuntime {
return FileUtils.readString(new File(Environment.getExternalStorageDirectory() + "/" + path));
}
@Override
public ThemeColorMaterialDialogBuilder dialog() {
if (App.currentActivity() == null) {
Toast.makeText(App.getApp(), R.string.text_cannot_create_dialog_when_app_invisible, Toast.LENGTH_SHORT).show();

View File

@@ -0,0 +1,85 @@
package com.stardust.scriptdroid.droid.runtime;
/**
* Created by Stardust on 2017/3/7.
*/
public class Shell extends com.stardust.scriptdroid.tool.Shell {
public Shell(boolean root) {
super(root);
}
public void Tap(int x, int y) {
execute("input tap " + x + " " + y);
}
public void Swipe(int x1, int y1, int x2, int y2) {
execute("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2);
}
public void Swipe(int x1, int y1, int x2, int y2, long duration) {
execute("input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " " + duration);
}
public void KeyCode(int keyCode) {
execute("input keyevent " + keyCode);
}
public void KeyCode(String keyCode) {
execute("input keyevent " + keyCode);
}
public void Home() {
KeyCode(3);
}
public void Back() {
KeyCode(4);
}
public void Power() {
KeyCode(26);
}
public void Up() {
KeyCode(19);
}
public void Down() {
KeyCode(20);
}
public void Left() {
KeyCode(21);
}
public void Right() {
KeyCode(22);
}
public void OK() {
KeyCode(23);
}
public void VolumeUp() {
KeyCode(24);
}
public void VolumeDown() {
KeyCode(25);
}
public void Menu() {
KeyCode(1);
}
public void Camera() {
KeyCode(27);
}
public void Text(String text) {
execute("input text " + text);
}
}

View File

@@ -1,55 +0,0 @@
package com.stardust.scriptdroid.droid.runtime.api;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.scriptdroid.droid.runtime.action.ActionTarget;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
/**
* Created by Stardust on 2017/1/27.
*/
public interface IDroidRuntime {
void launchPackage(String packageName);
void launch(String packageName, String className);
void launchApp(String appName);
String[] getPackageName(String appName);
ActionTarget text(String text, int i);
ActionTarget bounds(int left, int top, int right, int bottom);
ActionTarget editable(int i);
boolean click(ActionTarget target);
boolean longClick(ActionTarget target);
boolean scrollUp(ActionTarget target);
boolean scrollDown(ActionTarget target);
boolean scrollUp(int i);
boolean scrollDown(int i);
boolean focus(ActionTarget target);
boolean select(ActionTarget target);
boolean setText(ActionTarget target, String text);
boolean paste(ActionTarget target);
void toast(String text);
void sleep(long millis);
boolean isStopped();
void stop();
ThemeColorMaterialDialogBuilder dialog();
}

View File

@@ -1,19 +1,5 @@
package com.stardust.scriptdroid.droid.script;
import com.efurture.script.JSTransformer;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by Stardust on 2017/1/27.
*/

View File

@@ -2,8 +2,9 @@ package com.stardust.scriptdroid.droid.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.droid.script.file.AssetScript;
import com.stardust.scriptdroid.file.FileUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -15,10 +16,6 @@ public abstract class JavaScriptEngine {
Map<String, Object> mVariableMap = new HashMap<>();
protected String preprocess(String script) {
return script;
}
public abstract Object execute(String script);
public void set(String varName, Object value) {
@@ -33,11 +30,17 @@ public abstract class JavaScriptEngine {
private static final String INIT_SCRIPT = readInitScript();
private static String readInitScript() {
return AssetScript.read(App.getApp(), "javasccript_engine_init.js");
try {
return FileUtils.readString(App.getApp().getAssets().open("javasccript_engine_init.js"));
} catch (IOException e) {
e.printStackTrace();
return "Error: Unable to read init script.";
}
}
static String getInitScript() {
if (BuildConfig.DEBUG) {
// 调试时不缓存INIT_SCRIPT否则修改javasccript_engine_init.js后不会更新
return readInitScript();
} else {
return INIT_SCRIPT;

View File

@@ -2,8 +2,8 @@ package com.stardust.scriptdroid.droid.script;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.droid.runtime.ScriptStopException;
import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
@@ -27,7 +27,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
private final Set<Thread> mThreads = new HashSet<>();
public RhinoJavaScriptEngine(IDroidRuntime runtime) {
public RhinoJavaScriptEngine(DroidRuntime runtime) {
set("droid", runtime);
}

View File

@@ -1,74 +0,0 @@
package com.stardust.scriptdroid.droid.script.file;
import android.content.Context;
import android.util.Log;
import com.efurture.script.JSTransformer;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
public class AssetScript {
public static String read(Context context, String fileName){
try {
InputStream is = context.getAssets().open(fileName);
ByteArrayOutputStream output = new ByteArrayOutputStream();
BufferedInputStream bufferIn = new BufferedInputStream(is);
byte[] buffer = new byte[2048];
int length = 0;
while ((length = bufferIn.read(buffer)) >= 0){
output.write(buffer, 0, length);
}
String script = output.toString();
Log.e("compiled ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(Context context, String fileName){
try {
InputStream is = context.getAssets().open(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(String fileName){
try {
InputStream is = new FileInputStream(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String toScript(InputStream is){
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String script = JSTransformer.parse(br);
Log.e("ScriptEngine", script);
return script;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,25 @@
package com.stardust.scriptdroid.record.inputevent;
/**
* Created by Stardust on 2017/3/7.
*/
public class EventFormatException extends RuntimeException {
public static EventFormatException forEventStr(String eventStr, NumberFormatException e) {
return new EventFormatException(eventStr, e);
}
public EventFormatException(Exception e) {
super(e);
}
public EventFormatException(String eventStr, Exception e) {
super("eventStr=" + eventStr, e);
}
public EventFormatException(String eventStr) {
super("eventStr=" + eventStr);
}
}

View File

@@ -0,0 +1,110 @@
package com.stardust.scriptdroid.record.inputevent;
import android.support.annotation.NonNull;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.scriptdroid.ui.main.MainActivity;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by Stardust on 2017/3/7.
*/
public abstract class InputEventConverter {
static class Event {
static final Pattern PATTERN = Pattern.compile("^\\[([^\\]]*)\\]\\s+([^:]*):\\s+([^\\s]*)\\s+([^\\s]*)\\s+([^\\s]*)\\s*$");
static Event parseEvent(String eventStr) {
Matcher matcher = Event.PATTERN.matcher(eventStr);
if (!matcher.matches()) {
throw new EventFormatException(eventStr);
}
double time;
try {
time = Double.parseDouble(matcher.group(1));
} catch (NumberFormatException e) {
throw new EventFormatException(eventStr, e);
}
return new Event(time, matcher.group(2), matcher.group(3), matcher.group(4), matcher.group(5));
}
double time;
String device;
String type;
String code;
String value;
public Event(double time, String device, String type, String code, String value) {
this.time = time;
this.device = device;
this.type = type;
this.code = code;
this.value = value;
}
@Override
public String toString() {
return "Event{" +
"time=" + time +
", device='" + device + '\'' +
", type='" + type + '\'' +
", code='" + code + '\'' +
", value='" + value + '\'' +
'}';
}
}
protected boolean mStarted = false;
public void parseAndAddEventIfFormatCorrect(String eventStr) {
Event event = parseEventOrNull(eventStr);
if (event != null) {
addEvent(event);
}
}
public abstract void addEvent(@NonNull Event event);
public void start() {
mStarted = true;
}
public void pause() {
mStarted = false;
}
public void stop() {
mStarted = false;
}
public abstract String getCode();
public Event parseEventOrNull(String eventStr) {
try {
return Event.parseEvent(eventStr);
} catch (EventFormatException e) {
e.printStackTrace();
return null;
}
}
public void notifyRecordStopped() {
MainActivity.onRootRecordStopped(App.getApp());
}
public void notifyRecordStarted() {
DroidRuntime.getRuntime().toast(App.getApp().getString(R.string.text_start_record));
}
}

View File

@@ -0,0 +1,116 @@
package com.stardust.scriptdroid.record.inputevent;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Pair;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.tool.Shell;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jackpal.androidterm.ShellTermSession;
import jackpal.androidterm.emulatorview.TermSession;
import jackpal.androidterm.util.TermSettings;
/**
* Created by Stardust on 2017/3/6.
*/
public abstract class InputEventRecorder {
private Thread mRecordThread;
private TermSession mTermSession;
private String mGetEventCommand;
protected InputEventConverter mInputEventConverter;
protected InputEventRecorder(String getEventCommand, InputEventConverter inputEventConverter) {
mGetEventCommand = getEventCommand;
mInputEventConverter = inputEventConverter;
}
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.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() {
mInputEventConverter.start();
}
public void pause() {
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);
}
}
protected abstract void parseAndRecordEvent(String eventStr);
public abstract String getCode();
public void stop() {
mRecordThread.interrupt();
mTermSession.finish();
mInputEventConverter.stop();
}
}

View File

@@ -0,0 +1,171 @@
package com.stardust.scriptdroid.record.inputevent;
import android.support.annotation.NonNull;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
import com.stardust.util.MapEntries;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Stardust on 2017/3/7.
*/
public class InputEventToJsConverter extends InputEventConverter {
interface EventHandler {
void handle(Event event);
}
abstract static class Router implements EventHandler {
private Map<String, EventHandler> mEventHandlerMap = new HashMap<>();
EventHandler getHandler(String key) {
return mEventHandlerMap.get(key);
}
Router put(String key, EventHandler handler) {
mEventHandlerMap.put(key, handler);
return this;
}
@Override
public void handle(Event event) {
EventHandler handler = mEventHandlerMap.get(getKey(event));
if (handler != null) {
handler.handle(event);
}
}
protected abstract String getKey(Event event);
}
private class TypeRouter extends Router {
TypeRouter() {
put("EV_ABS", new AbsHandler());
put("EV_SYN", new SyncHandler());
put("EV_KEY", new KeyHandler());
}
@Override
protected String getKey(Event event) {
return event.type;
}
}
private static class Point {
int x, y;
}
private class AbsHandler implements EventHandler {
@Override
public void handle(Event event) {
if (event.code.equals("ABS_MT_POSITION_X")) {
mTouchPoint.x = Integer.parseInt(event.value, 16);
mTouchTime = event.time;
} else if (event.code.equals("ABS_MT_POSITION_Y")) {
mTouchPoint.y = Integer.parseInt(event.value, 16);
mTouchTime = event.time;
}
}
}
private class SyncHandler implements EventHandler {
@Override
public void handle(Event event) {
if (mTouchDown) {
if (mSwipe) {
appendCodeIfNotStopped(event);
mSwipe = false;
} else {
mLastTouchPoint.x = mTouchPoint.x;
mLastTouchPoint.y = mTouchPoint.y;
mSwipe = true;
}
}
}
private void appendCodeIfNotStopped(Event event) {
if (!mStarted)
return;
long interval = (long) (1000 * (event.time - mTouchTime));
mCode.append("sh.Swipe(")
.append(mLastTouchPoint.x).append(", ").append(mLastTouchPoint.y).append(", ")
.append(mTouchPoint.x).append(", ").append(mTouchPoint.y);
if (interval >= 1) {
mCode.append(", ").append(interval);
}
mCode.append(");\n");
}
}
private class KeyHandler implements EventHandler {
private Map<String, String> mKeyPressCodeMap = new MapEntries<>(new HashMap<String, String>())
.entry("KEY_HOME", "Home")
.entry("KEY_MENU", "Menu")
.entry("KEY_VOLUMEDOWN", "VolumeDown")
.entry("KEY_VOLUMEUP", "VolumeUp")
.entry("KEY_BACK", "Back")
.map();
@Override
public void handle(Event event) {
if (event.code.equals("BTN_TOUCH")) {
mTouchDown = event.value.equals("DOWN");
if (!mTouchDown && mSwipe) {
if (mStarted)
mCode.append("sh.Tap(").append(mLastTouchPoint.x).append(", ").append(mLastTouchPoint.y).append(");\n");
mSwipe = false;
}
} else if (event.value.equals("UP")) {
if (!mStarted && event.code.equals("KEY_VOLUMEUP")) {
mStarted = true;
notifyRecordStarted();
} else if (mStarted && event.code.equals("KEY_VOLUMEDOWN")) {
mStarted = false;
notifyRecordStopped();
} else {
appendKeyPressCode(event);
}
}
}
private void appendKeyPressCode(Event event) {
String code = mKeyPressCodeMap.get(event.code);
if (code != null) {
mCode.append("sh.").append(code).append("();\n");
}
}
}
private EventHandler mEventHandler = new TypeRouter();
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;
private double mTouchTime;
@Override
public void addEvent(@NonNull Event event) {
mEventHandler.handle(event);
}
@Override
public void stop() {
super.stop();
mCode.append("sh.exitAndWaitFor();");
}
public String getCode() {
return mCode.toString();
}
}

View File

@@ -0,0 +1,23 @@
package com.stardust.scriptdroid.record.inputevent;
/**
* Created by Stardust on 2017/3/7.
*/
public class InputEventToJsRecorder extends InputEventRecorder {
public InputEventToJsRecorder() {
super("getevent -t -l", new InputEventToJsConverter());
}
@Override
protected void parseAndRecordEvent(String eventStr) {
mInputEventConverter.parseAndAddEventIfFormatCorrect(eventStr);
}
@Override
public String getCode() {
return mInputEventConverter.getCode();
}
}

View File

@@ -0,0 +1,49 @@
package com.stardust.scriptdroid.record.inputevent;
import android.support.annotation.NonNull;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by Stardust on 2017/3/7.
*/
public class InputEventToSendEventConverter extends InputEventConverter {
private static final DecimalFormat DELAY_FORMAT = new DecimalFormat("#.###");
private double mLastEventTime;
private StringBuilder mSendEventCommands = new StringBuilder();
@Override
public void addEvent(@NonNull Event event) {
if (mLastEventTime == 0) {
mLastEventTime = event.time;
} else if (event.time - mLastEventTime > 0.1) {
mSendEventCommands.append("sleep ").append(DELAY_FORMAT.format(event.time - mLastEventTime)).append("\n");
mLastEventTime = event.time;
}
mSendEventCommands.append("sendevent ")
.append(event.device).append(" ")
.append(hex2dec(event.type)).append(" ")
.append(hex2dec(event.code)).append(" ")
.append(hex2dec(event.value)).append("\n");
}
public String getCode() {
return mSendEventCommands.toString();
}
private static String hex2dec(String hex) {
try {
return String.valueOf((int) Long.parseLong(hex, 16));
} catch (NumberFormatException e) {
throw new EventFormatException(e);
}
}
}

View File

@@ -0,0 +1,26 @@
package com.stardust.scriptdroid.record.inputevent;
/**
* Created by Stardust on 2017/3/7.
*/
public class InputEventToSendEventRecorder extends InputEventRecorder {
private InputEventToSendEventConverter mEventConverter;
protected InputEventToSendEventRecorder() {
super("getevent -t", new InputEventToSendEventConverter());
}
@Override
protected void parseAndRecordEvent(String eventStr) {
mEventConverter.parseAndAddEventIfFormatCorrect(eventStr);
}
@Override
public String getCode() {
return mEventConverter.getCode();
}
}

View File

@@ -1,110 +0,0 @@
package com.stardust.scriptdroid.record.root;
import android.text.TextUtils;
import android.util.Pair;
import com.jecelyin.common.utils.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by Stardust on 2017/3/6.
*/
public class InputEventConverter {
public static Pair<Double, String> convert(String line) {
int j = line.indexOf(']');
double time = 0;
if (j > 0) {
time = Double.parseDouble(line.substring(1, j));
line = line.substring(j + 2);
}
String[] str = line.split(" ", 4);
for (int i = 1; i < 4; i++) {
str[i] = hex2dec(str[i]);
}
str[0] = str[0].substring(0, str[0].length() - 1);
return new Pair<>(time, "sendevent " + TextUtils.join(" ", str));
}
private static String hex2dec(String hex) {
return String.valueOf((int) Long.parseLong(hex, 16));
}
private static class SingleEvent {
private static final Pattern PATTERN = Pattern.compile("^\\[([^\\]]*)\\]\\s+([^:]*):\\s+([^\\s]*)\\s+([^\\s]*)\\s+([^\\s]*)$");
double time;
String device;
String type;
String code;
String value;
SingleEvent(String str) {
Matcher matcher = PATTERN.matcher(str);
matcher.matches();
time = Double.parseDouble(matcher.group(1));
device = matcher.group(2);
type = matcher.group(3);
code = matcher.group(4);
value = matcher.group(5);
}
@Override
public String toString() {
return "SingleEvent{" +
"time=" + time +
", device='" + device + '\'' +
", type='" + type + '\'' +
", code='" + code + '\'' +
", value='" + value + '\'' +
'}';
}
}
public String convertToCode(String events) {
BufferedReader reader = new BufferedReader(new StringReader(events));
String line;
StringBuilder code = new StringBuilder();
try {
EventConverter converter = null;
while ((line = reader.readLine()) != null) {
SingleEvent event = new SingleEvent(line);
if (event.value.equals("DOWN") && event.type.equals("BIN_TOUCH")) {
converter = new EventConverter();
} else if (event.value.equals("UP") && event.type.equals("BIN_TOUCH")) {
code.append(converter.toString());
converter = null;
} else if (converter != null) {
}
}
} catch (IOException e) {
e.printStackTrace();
}
return code.toString();
}
public static class EventConverter {
List<SingleEvent> mSingleEvents = new ArrayList<>();
public void add(SingleEvent event) {
if (event.type.equals("ABS_MT_POSITION_X") || event.type.equals("ABS_MT_POSITION_Y")) {
}
}
}
}

View File

@@ -1,70 +0,0 @@
package com.stardust.scriptdroid.record.root;
import android.util.Pair;
import com.stardust.scriptdroid.tool.Shell;
import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
/**
* Created by Stardust on 2017/3/6.
*/
public class InputEventRecorder {
private Thread mRecordThread;
private Shell mShell;
public void record() {
mShell = new Shell(true);
mRecordThread = new Thread(new Runnable() {
@Override
public void run() {
try {
mShell.execute("getevent -t");
readOutput(mShell);
} catch (Exception e) {
e.printStackTrace();
}
}
});
mRecordThread.start();
}
private void readOutput(Shell shell) {
String line;
try {
BufferedReader succeedReader = shell.getSucceedReader();
StringBuilder succeedOutput = shell.getSucceedOutput();
double time = 0;
DecimalFormat df = new DecimalFormat("#.###");
while ((line = succeedReader.readLine()) != null) {
if (line.startsWith("[")) {
Pair<Double, String> c = InputEventConverter.convert(line);
if (time == 0) {
time = c.first;
} else if (c.first - time > 0.1f) {
succeedOutput.append("sleep ").append(df.format(c.first - time)).append("\n");
time = c.first;
}
succeedOutput.append(c.second).append("\n");
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String stop() {
mRecordThread.interrupt();
mShell.destroy();
return mShell.getSucceedOutput().toString();
}
}

View File

@@ -1,14 +1,29 @@
package com.stardust.scriptdroid.tool;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.util.Log;
import com.stardust.scriptdroid.record.root.InputEventConverter;
import com.stardust.scriptdroid.App;
import com.stericson.RootShell.RootShell;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import jackpal.androidterm.ShellTermSession;
import jackpal.androidterm.Term;
import jackpal.androidterm.emulatorview.TermSession;
import jackpal.androidterm.util.TermSettings;
/**
* Created by Stardust on 2017/1/20.
* <p>
@@ -33,13 +48,56 @@ 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);
}
public Shell(boolean root) {
try {
mProcess = Runtime.getRuntime().exec(root ? COMMAND_SU : COMMAND_SH);
mProcess = new ProcessBuilder(root ? COMMAND_SU : COMMAND_SH).redirectErrorStream(true).start();
mCommandOutputStream = new DataOutputStream(mProcess.getOutputStream());
mSucceedReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
mErrorReader = new BufferedReader(new InputStreamReader(mProcess.getErrorStream()));
@@ -49,6 +107,7 @@ public class Shell {
}
public Shell execute(String command) {
try {
mCommandOutputStream.writeBytes(command);
if (!command.endsWith(COMMAND_LINE_END)) {
@@ -76,9 +135,9 @@ public class Shell {
}
public Shell readSucceedOutput() {
String line;
try {
while ((line = mSucceedReader.readLine()) != null) {
while (mSucceedReader.ready()) {
String line = mSucceedReader.readLine();
mSucceedOutput.append(line).append("\n");
}
} catch (IOException e) {
@@ -129,6 +188,14 @@ public class Shell {
return mErrorReader;
}
public int waitFor() {
try {
return mProcess.waitFor();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* Command执行结果
*

View File

@@ -29,20 +29,25 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.folderselector.FileChooserDialog;
import com.stardust.app.NotRemindAgainDialog;
import com.stardust.app.OnActivityResultDelegate;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.BuildConfig;
import com.stardust.scriptdroid.Pref;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
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.file.FileUtils;
import com.stardust.scriptdroid.file.SampleFileManager;
import com.stardust.scriptdroid.record.root.InputEventRecorder;
import com.stardust.scriptdroid.record.inputevent.InputEventRecorder;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsConverter;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsRecorder;
import com.stardust.scriptdroid.service.AccessibilityWatchDogService;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.tool.BackPressedHandler;
import com.stardust.scriptdroid.tool.ImageSelector;
import com.stardust.scriptdroid.tool.Shell;
import com.stardust.scriptdroid.ui.BaseActivity;
import com.stardust.scriptdroid.ui.main.operation.ScriptFileOperation;
import com.stardust.scriptdroid.ui.settings.SettingsActivity;
@@ -64,6 +69,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
public static final String ACTION_NOTIFY_SCRIPT_LIST_CHANGE = "ACTION_NOTIFY_SCRIPT_LIST_CHANGE";
private static final String ACTION_ON_ACTION_RECORD_STOPPED = "ACTION_ON_ACTION_RECORD_STOPPED";
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 ScriptListRecyclerView mScriptListRecyclerView;
@@ -79,6 +85,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
checkPermissions();
registerReceivers();
handleIntent(getIntent());
//Shell.test(this);
}
private void registerReceivers() {
@@ -226,6 +233,17 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
Snackbar.make(mDrawerLayout, R.string.hint_start_record, Snackbar.LENGTH_SHORT).show();
}
private InputEventRecorder mInputEventRecorder;
@ViewBinding.Click(R.id.root_record)
private void startRootRecord() {
if (mInputEventRecorder == null) {
mInputEventRecorder = new InputEventToJsRecorder();
mInputEventRecorder.listen();
}
Snackbar.make(mDrawerLayout, R.string.hint_start_root_record, Snackbar.LENGTH_SHORT).show();
}
@ViewBinding.Click(R.id.setting)
private void startSettingActivity() {
startActivity(new Intent(this, SettingsActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -275,6 +293,13 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
case ACTION_ON_ACTION_RECORD_STOPPED:
handleRecordedScript(intent.getStringExtra(ARGUMENT_SCRIPT));
break;
case ACTION_ON_ROOT_RECORD_STOPPED:
if (mInputEventRecorder != null) {
mInputEventRecorder.stop();
handleRecordedScript(mInputEventRecorder.getCode());
mInputEventRecorder = null;
}
break;
}
}
@@ -294,6 +319,14 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
}
}
})
.negativeText(R.string.text_cancel)
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
dialog.dismiss();
}
})
.canceledOnTouchOutside(false)
.show();
}
@@ -331,6 +364,14 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
}
public static void onRootRecordStopped(Context context) {
Intent intent = new Intent(context, MainActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_ACTION, ACTION_ON_ROOT_RECORD_STOPPED);
context.startActivity(intent);
}
private class Receiver extends BroadcastReceiver {
@Override

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -133,6 +133,31 @@
</LinearLayout>
<LinearLayout
android:id="@+id/root_record"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:orientation="vertical">
<com.stardust.theme.widget.ThemeColorImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:padding="10dp"
android:src="@drawable/ic_video_record"/>
<com.stardust.theme.widget.ThemeColorTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/text_root_script_record"
android:textColor="@color/colorPrimary"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
</com.stardust.widget.SlidingUpPanel>

View File

@@ -135,4 +135,6 @@
<string name="text_theme_color">主题色</string>
<string name="text_select_image">选择图片</string>
<string name="text_help">帮助</string>
<string name="text_root_script_record">录制脚本(root)</string>
<string name="hint_start_root_record">使用音量上键,音量下键结束录制</string>
</resources>

View File

@@ -1,28 +1,9 @@
package com.stardust.scriptdroid;
import android.text.TextUtils;
import android.util.Pair;
import com.stardust.scriptdroid.record.inputevent.InputEventToJsConverter;
import com.stardust.scriptdroid.record.inputevent.InputEventToSendEventConverter;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.stardust.scriptdroid.record.root.InputEventConverter;
import com.stardust.scriptdroid.ui.help.HelpCatalogueActivity;
import org.json.JSONObject;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Example local unit test, which will execute on the development machine (host).
@@ -38,6 +19,8 @@ public class ExampleUnitTest {
@Test
public void test() {
System.out.println(new SingleEvent("[ 109722.085815] /dev/input/event1: EV_SYN SYN_REPORT 00000000"));
InputEventToJsConverter converter = new InputEventToJsConverter();
converter.parseAndAddEventIfFormatCorrect("[ 140123.672100] /dev/input/event3: EV_ABS ABS_MT_POSITION_X 0000005f");
System.out.println(converter.getCode());
}
}