add: root_automator

This commit is contained in:
hyb1996
2017-08-01 21:42:32 +08:00
parent 8734ae3992
commit 6a3392cfe6
22 changed files with 474 additions and 112 deletions

View File

@@ -3,6 +3,7 @@
<words>
<w>dismissable</w>
<w>interruptible</w>
<w>loopers</w>
<w>prefill</w>
<w>scriptable</w>
<w>tasker</w>

View File

@@ -89,7 +89,7 @@ events.onKeyDown("menu", function(event){
### events.onTouch(listener)
* listener \<Function\> 参数为Point的函数
注册一个触摸监听函数。
注册一个触摸监听函数。相当于`on("touch", listener)`
例如:
```
@@ -147,6 +147,28 @@ events.loop();
```
### obverseNotification()
### toast事件
### 事件: 'notification'
* notification \<Notification\> 通知
```
events.observeNotification();
events.on("notification", function(notification){
log(notification.
});
```
# EventEmitter
### EventEmitter.defaultMaxListeners
@@ -155,7 +177,7 @@ events.loop();
设置 EventEmitter.defaultMaxListeners 要谨慎,因为会影响所有 EventEmitter 实例,包括之前创建的。 因而,调用 emitter.setMaxListeners(n) 优先于 EventEmitter.defaultMaxListeners。
注意与NodeJs不同**这是一个硬性限制**。 EventEmitter 实例不允许添加更多的监听器监听器超过最大数量时会抛出TooManyListenersException。
注意与Node.js不同**这是一个硬性限制**。 EventEmitter 实例不允许添加更多的监听器监听器超过最大数量时会抛出TooManyListenersException。
```
emitter.setMaxListeners(emitter.getMaxListeners() + 1);
emitter.once('event', () => {

View File

@@ -1,5 +1,5 @@
{
"global" : {
"global": {
"modules": [
"app",
"automator",
@@ -39,7 +39,7 @@
"openConsole",
"clearConsole"
],
"dialogs":[
"dialogs": [
"rawInput",
"input",
"alert",
@@ -142,12 +142,12 @@
"Text"
],
"timers": [
"loop",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"setImmediate",
"loop",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"setImmediate",
"clearImmediate"
],
"web": [
@@ -211,7 +211,7 @@
"error",
"assert"
],
"dialogs":[
"dialogs": [
"select",
"singleChoice",
"multiChoice",
@@ -248,6 +248,8 @@
"onKeyUp",
"onceKeyDown",
"onceKeyUp",
"onToast",
"onNotification",
"removeAllKeyDownListeners",
"removeAllKeyUpListeners",
"onTouch",

View File

@@ -1,6 +1,7 @@
package com.stardust.scriptdroid.autojs;
import android.app.Activity;
import android.app.Notification;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -36,6 +37,7 @@ import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.scriptdroid.ui.console.JraskaConsole;
import com.stardust.view.accessibility.AccessibilityServiceUtils;
import com.stardust.view.accessibility.LayoutInspector;
import com.stardust.view.accessibility.NotificationListener;
/**
@@ -55,6 +57,7 @@ public class AutoJs {
}
private final AccessibilityActionRecorder mAccessibilityActionRecorder = new AccessibilityActionRecorder();
private final NotificationListener.Observer mNotificationObserver;
private final LayoutInspector mLayoutInspector = new LayoutInspector();
private final Context mContext;
private final UiHandler mUiHandler;
@@ -68,6 +71,7 @@ public class AutoJs {
mContext = context;
mUiHandler = new UiHandler(context);
mAppUtils = new AppUtils(context);
mNotificationObserver = new NotificationListener.Observer(context);
mAccessibilityInfoProvider = new AccessibilityInfoProvider(context.getPackageManager());
mScriptEngineService = buildScriptEngineService();
addAccessibilityServiceDelegates();
@@ -130,6 +134,7 @@ public class AutoJs {
private void addAccessibilityServiceDelegates() {
AccessibilityService.addDelegate(100, mAccessibilityInfoProvider);
AccessibilityService.addDelegate(200, mNotificationObserver);
AccessibilityService.addDelegate(300, mAccessibilityActionRecorder);
}
@@ -198,6 +203,11 @@ public class AutoJs {
return mAccessibilityInfoProvider;
}
@Override
public NotificationListener.Observer getNotificationObserver() {
return mNotificationObserver;
}
}
private class ScreenCaptureRequesterImpl extends ScreenCaptureRequester.AbstractScreenCaptureRequester {

View File

@@ -20,7 +20,6 @@ import com.stardust.scriptdroid.autojs.AutoJs;
import com.stardust.view.accessibility.NodeInfo;
import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayoutBoundsView;
import com.stardust.scriptdroid.external.floatingwindow.menu.view.FloatingLayoutHierarchyView;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
import com.stardust.theme.ThemeColorManagerCompat;
import com.stardust.util.IntentUtil;
import com.stardust.util.MessageEvent;
@@ -189,7 +188,6 @@ public class HoverMenuService extends Service {
tryInitViews();
if (mWindowHoverMenu != null)
mWindowHoverMenu.show();
AccessibilityServiceTool.enableAccessibilityServiceByRootIfNeeded();
return START_STICKY;
}
@@ -200,6 +198,8 @@ public class HoverMenuService extends Service {
if (eventBus.isRegistered(this))
eventBus.unregister(this);
setIsRunning(false);
if (mWindowViewController == null)
return;
mWindowViewController.removeView(mFloatingLayoutBoundsView);
mWindowViewController.removeView(mFloatingLayoutHierarchyView);
}

View File

@@ -85,9 +85,6 @@ public class RecordNavigatorContent implements NavigatorContent, Recorder.OnStat
ButterKnife.bind(this, mView);
HoverMenuService.getEventBus().register(this);
AccessibilityService.getStickOnKeyObserver().addListener(mVolumeKeyListener);
if (Pref.isRecordVolumeControlEnable()) {
AutoJs.getInstance().ensureAccessibilityServiceEnabled();
}
if (Pref.hasRecordTrigger()) {
mKeyObserver = new KeyObserver(mContext);
mKeyObserver.startListening();

View File

@@ -14,10 +14,14 @@ public class ProgressDialog {
private MaterialDialog mDialog;
public ProgressDialog(Context context) {
this(context, R.string.text_on_progress);
}
public ProgressDialog(Context context, int resId) {
mDialog = new MaterialDialog.Builder(context)
.progress(true, 0)
.cancelable(false)
.content(R.string.text_on_progress)
.content(resId)
.show();
}

View File

@@ -133,7 +133,12 @@ public class SideMenuFragment extends android.support.v4.app.Fragment {
AccessibilityServiceTool.goToAccessibilitySetting();
return;
}
final ProgressDialog progress = new ProgressDialog(getContext());
enableAccessibilityServiceByRoot();
}
private void enableAccessibilityServiceByRoot() {
final ProgressDialog progress = new ProgressDialog(getContext(), R.string.text_enable_accessibility_service_by_root_ing);
UnderuseExecutors.execute(new Runnable() {
@Override
public void run() {
@@ -156,6 +161,9 @@ public class SideMenuFragment extends android.support.v4.app.Fragment {
void setFloatingWindowEnable(CompoundButton button, boolean enable) {
if (enable && !HoverMenuManger.isHoverMenuShowing()) {
HoverMenuManger.showHoverMenu();
if (Pref.enableAccessibilityServiceByRoot()) {
enableAccessibilityServiceByRoot();
}
} else if (!enable && HoverMenuManger.isHoverMenuShowing()) {
HoverMenuManger.hideHoverMenu();
}

View File

@@ -212,6 +212,7 @@
<string name="text_enable_accessibitliy_service_by_root_failed">使用root权限开启失败</string>
<string name="text_on_progress">处理中</string>
<string name="text_delete_failed">删除失败</string>
<string name="text_enable_accessibility_service_by_root_ing">正在使用root权限启用无障碍服务</string>
<string-array name="record_control_keys">

Binary file not shown.

View File

@@ -47,4 +47,4 @@ require("__general__")(__runtime__, this);
__importClass__(android.view.KeyEvent);
__importClass__(com.stardust.autojs.runtime.api.Shell);
__importClass__(com.stardust.autojs.runtime.api.InputEventSender);
__importClass__(com.stardust.autojs.runtime.api.RootAutomator);

View File

@@ -0,0 +1,8 @@
package com.stardust.autojs.engine.preprocess;
/**
* Created by Stardust on 2017/8/1.
*/
public class RootAutomatorEngine {
}

View File

@@ -1,11 +1,13 @@
package com.stardust.autojs.runtime;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.accessibility.AccessibilityNodeInfo;
import com.stardust.view.accessibility.AccessibilityInfoProvider;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.NotificationListener;
/**
* Created by Stardust on 2017/4/2.
@@ -37,6 +39,7 @@ public abstract class AccessibilityBridge {
}
@NonNull
public abstract AccessibilityInfoProvider getInfoProvider();
@@ -44,4 +47,6 @@ public abstract class AccessibilityBridge {
mMode = mode;
}
@NonNull
public abstract NotificationListener.Observer getNotificationObserver();
}

View File

@@ -1,25 +1,24 @@
package com.stardust.autojs.runtime.api;
import android.app.Notification;
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.autojs.runtime.AccessibilityBridge;
import com.stardust.autojs.runtime.ScriptException;
import com.stardust.autojs.runtime.record.inputevent.TouchObserver;
import com.stardust.view.accessibility.AccessibilityService;
import com.stardust.view.accessibility.NotificationListener;
import com.stardust.view.accessibility.OnKeyListener;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Stardust on 2017/7/18.
*/
public class Events extends EventEmitter implements OnKeyListener, TouchObserver.OnTouchEventListener {
public class Events extends EventEmitter implements OnKeyListener, TouchObserver.OnTouchEventListener, NotificationListener {
private static final String PREFIX_KEY_DOWN = "__key_down__#";
private static final String PREFIX_KEY_UP = "__key_up__#";
@@ -32,6 +31,7 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
private boolean mListeningKey = false;
private Loopers mLoopers;
private Handler mHandler;
private boolean mListeningNotification = false;
public Events(Context context, AccessibilityBridge accessibilityBridge, ScriptBridges bridges, Loopers loopers) {
super(bridges);
@@ -122,6 +122,25 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
}
public void observeNotification() {
if (mListeningNotification)
return;
mListeningNotification = true;
mAccessibilityBridge.getNotificationObserver()
.addListener(this);
}
public Events onNotification(Object listener) {
on("notification", listener);
return this;
}
public Events onToast(Object listener) {
on("toast", listener);
return this;
}
public void recycle() {
if (mListeningKey) {
AccessibilityService service = mAccessibilityBridge.getService();
@@ -133,6 +152,9 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
if (mTouchObserver != null) {
mTouchObserver.stop();
}
if (mListeningNotification) {
mAccessibilityBridge.getNotificationObserver().removeListener(this);
}
}
@Override
@@ -169,4 +191,24 @@ public class Events extends EventEmitter implements OnKeyListener, TouchObserver
}
@Override
public void onNotification(final AccessibilityEvent event, final String[] notification) {
mHandler.post(new Runnable() {
@Override
public void run() {
emit("toast", new Object[]{notification});
}
});
}
@Override
public void onNotification(final AccessibilityEvent event, final Notification notification) {
mHandler.post(new Runnable() {
@Override
public void run() {
emit("notification", notification);
}
});
}
}

View File

@@ -1,86 +0,0 @@
package com.stardust.autojs.runtime.api;
import com.stardust.util.ScreenMetrics;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by Stardust on 2017/7/16.
*/
public class InputEventSender {
private DataOutputStream mDeviceFile;
private ScreenMetrics mScreenMetrics;
public InputEventSender(String devicePath) throws FileNotFoundException {
mDeviceFile = new DataOutputStream(new FileOutputStream(devicePath));
}
public InputEventSender(int i) throws FileNotFoundException {
this("/dev/input/event" + i);
}
public InputEventSender() {
}
public void sendEvent(int type, int code, int value) throws IOException {
for (int i = 0; i < 16; i++)
mDeviceFile.writeByte(0);
mDeviceFile.writeShort(type);
mDeviceFile.writeShort(code);
mDeviceFile.writeInt(value);
mDeviceFile.flush();
}
public void setInputDevice(int i) throws IOException {
if (mDeviceFile != null) {
mDeviceFile.close();
}
mDeviceFile = new DataOutputStream(new FileOutputStream("/dev/input/event" + i));
}
public void Touch(int x, int y) throws IOException {
TouchX(x);
TouchY(y);
}
public void setScreenMetrics(int width, int height) {
if (mScreenMetrics == null) {
mScreenMetrics = new ScreenMetrics();
}
mScreenMetrics.setScreenMetrics(width, height);
}
public void TouchX(int x) throws IOException {
sendEvent(3, 53, scaleX(x));
}
private int scaleX(int x) {
return mScreenMetrics.scaleX(x);
}
public void TouchY(int y) throws IOException {
sendEvent(3, 54, scaleY(y));
}
private int scaleY(int y) {
return mScreenMetrics.scaleY(y);
}
public void close() throws IOException {
mDeviceFile.close();
}
}

View File

@@ -0,0 +1,112 @@
package com.stardust.autojs.runtime.api;
import android.content.Context;
import com.stardust.pio.PFile;
import com.stardust.pio.UncheckedIOException;
import com.stardust.util.ScreenMetrics;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by Stardust on 2017/7/16.
*/
public class RootAutomator {
private static final int DATA_TYPE_SLEEP = 0;
private static final int DATA_TYPE_EVENT = 1;
private static final String ROOT_AUTOMATOR_EXECUTABLE_ASSET = "binary/root_automator";
private DataOutputStream mTmpFileOutputStream;
private File mEventTmpFile;
private ScreenMetrics mScreenMetrics;
private String mDevicePath;
public RootAutomator() {
try {
mEventTmpFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".auto");
mEventTmpFile.deleteOnExit();
mTmpFileOutputStream = new DataOutputStream(new FileOutputStream(mEventTmpFile));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void sendEvent(int type, int code, int value) throws IOException {
mTmpFileOutputStream.writeByte(DATA_TYPE_EVENT);
mTmpFileOutputStream.writeShort(type);
mTmpFileOutputStream.writeShort(code);
mTmpFileOutputStream.writeInt(value);
}
public void setInputDevice(int i) throws IOException {
mDevicePath = "/dev/input/event" + i;
}
public void touch(int x, int y) throws IOException {
touchX(x);
touchY(y);
}
public void setScreenMetrics(int width, int height) {
if (mScreenMetrics == null) {
mScreenMetrics = new ScreenMetrics();
}
mScreenMetrics.setScreenMetrics(width, height);
}
public void touchX(int x) throws IOException {
sendEvent(3, 53, scaleX(x));
}
private int scaleX(int x) {
return mScreenMetrics.scaleX(x);
}
public void touchY(int y) throws IOException {
sendEvent(3, 54, scaleY(y));
}
public void sendSync() throws IOException {
sendEvent(0, 0, 0);
}
private int scaleY(int y) {
return mScreenMetrics.scaleY(y);
}
public void sleep(int n) throws IOException {
mTmpFileOutputStream.writeByte(DATA_TYPE_SLEEP);
mTmpFileOutputStream.writeInt(n);
}
public AbstractShell.Result writeToDevice(Context context) {
try {
mTmpFileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
String executablePath = getExecutablePath(context);
return ProcessShell.execCommand(new String[]{
"chmod 777 " + executablePath,
executablePath + " " + mEventTmpFile.getAbsolutePath() + " " + mDevicePath
}, true);
}
public static String getExecutablePath(Context context) {
File tmp = new File(context.getCacheDir(), "root_automator");
if (!tmp.exists()) {
PFile.copyAsset(context, ROOT_AUTOMATOR_EXECUTABLE_ASSET, tmp.getAbsolutePath());
}
return tmp.getAbsolutePath();
}
}

View File

@@ -0,0 +1,8 @@
package com.stardust.autojs.runtime.record.inputevent;
/**
* Created by Stardust on 2017/8/1.
*/
public class InputDevice {
}

View File

@@ -0,0 +1,120 @@
package com.stardust.autojs.runtime.record.inputevent;
import android.support.annotation.NonNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.stardust.util.ScreenMetrics.getDeviceScreenHeight;
import static com.stardust.util.ScreenMetrics.getDeviceScreenWidth;
/**
* Created by Stardust on 2017/8/1.
*/
public class InputEventToRootAutomatorConverter extends InputEventConverter {
private double mLastEventTime;
private StringBuilder mCode = new StringBuilder();
private int mTouchDevice = -1;
private int mLastTouchX = -1;
private int mLastTouchY = -1;
public InputEventToRootAutomatorConverter() {
mCode.append("var ra = new RootAutomator();\n")
.append("ra.setScreenMetrics(").append(getDeviceScreenWidth()).append(", ")
.append(getDeviceScreenHeight()).append(");\n");
}
@Override
public void convertEvent(@NonNull Event event) {
if (mLastEventTime == 0) {
mLastEventTime = event.time;
} else if (event.time - mLastEventTime > 0.005) {
mCode.append("ra.sleep(").append((long) (1000L * (event.time - mLastEventTime))).append(");\n");
mLastEventTime = event.time;
}
int device = parseDeviceNumber(event.device);
int type = (int) Long.parseLong(event.type, 16);
int code = (int) Long.parseLong(event.code, 16);
int value = (int) Long.parseLong(event.value, 16);
if (type == 3) {
if (code == 53) {
onTouchX(device, value);
return;
}
if (code == 54) {
onTouchY(device, value);
return;
}
}
checkLastTouch();
if (device != mTouchDevice) {
return;
}
if (type == 0 && code == 0 && value == 0) {
mCode.append("ra.sendSync();\n");
return;
}
mCode.append("ra.sendEvent(");
mCode.append(type).append(", ")
.append(code).append(", ")
.append(value).append(");\n");
}
private void checkLastTouch() {
if (mLastTouchX >= 0) {
mCode.append("ra.touchX(").append(mLastTouchX).append(");\n");
mLastTouchX = -1;
}
if (mLastTouchY >= 0) {
mCode.append("ra.touchY(").append(mLastTouchY).append(");\n");
mLastTouchY = -1;
}
}
private void onTouchX(int device, int value) {
if (mTouchDevice == -1) {
setTouchDevice(device);
}
mLastTouchX = value;
}
private void onTouchY(int device, int value) {
if (mTouchDevice == -1) {
setTouchDevice(device);
}
if (mLastTouchX >= 0) {
mCode.append("ra.touch(")
.append(mLastTouchX).append(", ")
.append(value).append(");\n");
mLastTouchX = -1;
} else {
mLastTouchY = value;
}
}
private void setTouchDevice(int i) {
mCode.append("ra.setInputDevice(").append(i).append(");\n");
mTouchDevice = i;
}
@Override
public String getGetEventCommand() {
return "getevent -t";
}
public String getCode() {
return mCode.toString();
}
@Override
public void stop() {
super.stop();
mCode.append("log(ra.writeToDevice(context));");
}
}

View File

@@ -9,7 +9,7 @@ import android.content.Context;
public class TouchRecorder extends InputEventRecorder {
public TouchRecorder(Context context) {
super(context, new InputEventToSendEventJsConverter());
super(context, new InputEventToRootAutomatorConverter());
listen();
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
android:canPerformGestures="true"
android:canRequestEnhancedWebAccessibility="true"

View File

@@ -0,0 +1,97 @@
package com.stardust.view.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.app.Notification;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.stardust.util.ArrayUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by Stardust on 2017/8/1.
*/
public interface NotificationListener {
void onNotification(AccessibilityEvent event, String[] notification);
void onNotification(AccessibilityEvent event, Notification notification);
class Observer implements NotificationListener, AccessibilityDelegate {
private static final Set<Integer> EVENT_TYPES = Collections.singleton(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
private static final String TAG = "NotificationObserver";
private static final String[] EMPTY = new String[0];
private Context mContext;
private CopyOnWriteArrayList<NotificationListener> mNotificationListeners = new CopyOnWriteArrayList<>();
public Observer(Context context) {
mContext = context;
}
@Override
public void onNotification(AccessibilityEvent event, String[] notification) {
for (NotificationListener listener : mNotificationListeners) {
try {
listener.onNotification(event, notification);
} catch (Exception e) {
Log.e(TAG, "Error onNotification: " + Arrays.toString(notification) + " Listener: " + listener, e);
}
}
}
@Override
public void onNotification(AccessibilityEvent event, Notification notification) {
for (NotificationListener listener : mNotificationListeners) {
try {
listener.onNotification(event, notification);
} catch (Exception e) {
Log.e(TAG, "Error onNotification: " + notification + " Listener: " + listener, e);
}
}
}
public void addListener(NotificationListener listener) {
mNotificationListeners.add(listener);
}
public boolean removeListener(NotificationListener listener) {
return mNotificationListeners.remove(listener);
}
@Override
public boolean onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
if (event.getParcelableData() instanceof Notification) {
Notification notification = (Notification) event.getParcelableData();
Log.d(TAG, "onNotification: " + notification + "; " + event);
onNotification(event, notification);
} else {
List<CharSequence> list = event.getText();
Log.d(TAG, "onNotification: " + list + "; " + event);
if (event.getPackageName().equals(mContext.getPackageName())) {
return false;
}
if (list != null) {
onNotification(event, ArrayUtils.toStringArray(list));
}
}
return false;
}
@Nullable
@Override
public Set<Integer> getEventTypes() {
return EVENT_TYPES;
}
}
}

View File

@@ -1,5 +1,7 @@
package com.stardust.util;
import java.util.List;
/**
* Created by Stardust on 2017/5/8.
*/
@@ -23,4 +25,13 @@ public class ArrayUtils {
}
return unbox;
}
public static String[] toStringArray(List<?> list) {
int i = 0;
String[] str = new String[list.size()];
for (Object o : list) {
str[i] = o == null ? null : o.toString();
}
return str;
}
}