first blood
This commit is contained in:
42
android/build.gradle
Normal file
42
android/build.gradle
Normal file
@@ -0,0 +1,42 @@
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "31.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 31
|
||||
targetSdkVersion = 31
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.2.1")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
}
|
||||
17
android/src/main/AndroidManifest.xml
Normal file
17
android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.rnbot">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".RNBotAccessibilityService"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service_config" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
160
android/src/main/java/com/rnbot/RNBotAccessibilityService.java
Normal file
160
android/src/main/java/com/rnbot/RNBotAccessibilityService.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package com.rnbot;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.GestureDescription;
|
||||
import android.graphics.Path;
|
||||
import android.os.Bundle;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RNBotAccessibilityService extends AccessibilityService {
|
||||
private static RNBotAccessibilityService instance;
|
||||
|
||||
public static RNBotAccessibilityService getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static boolean isServiceRunning() {
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
// 可以在这里监听各种事件
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt() {
|
||||
// 服务中断
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onServiceConnected() {
|
||||
super.onServiceConnected();
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public List<AccessibilityNodeInfo> findNodesByType(String type, String value) {
|
||||
List<AccessibilityNodeInfo> result = new ArrayList<>();
|
||||
AccessibilityNodeInfo root = getRootInActiveWindow();
|
||||
if (root == null) return result;
|
||||
|
||||
switch (type) {
|
||||
case "id":
|
||||
findNodesByIdRecursive(root, value, result);
|
||||
break;
|
||||
case "text":
|
||||
findNodesByTextRecursive(root, value, result);
|
||||
break;
|
||||
case "name":
|
||||
findNodesByNameRecursive(root, value, result);
|
||||
break;
|
||||
case "className":
|
||||
findNodesByClassNameRecursive(root, value, result);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void findNodesByIdRecursive(AccessibilityNodeInfo node, String resId, List<AccessibilityNodeInfo> result) {
|
||||
if (node == null) return;
|
||||
|
||||
String id = node.getViewIdResourceName();
|
||||
if (id != null && id.equals(resId)) {
|
||||
result.add(node);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (child != null) {
|
||||
findNodesByIdRecursive(child, resId, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findNodesByTextRecursive(AccessibilityNodeInfo node, String text, List<AccessibilityNodeInfo> result) {
|
||||
if (node == null) return;
|
||||
|
||||
CharSequence nodeText = node.getText();
|
||||
if (nodeText != null && nodeText.toString().contains(text)) {
|
||||
result.add(node);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (child != null) {
|
||||
findNodesByTextRecursive(child, text, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findNodesByNameRecursive(AccessibilityNodeInfo node, String name, List<AccessibilityNodeInfo> result) {
|
||||
if (node == null) return;
|
||||
|
||||
CharSequence desc = node.getContentDescription();
|
||||
if (desc != null && desc.toString().contains(name)) {
|
||||
result.add(node);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (child != null) {
|
||||
findNodesByNameRecursive(child, name, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findNodesByClassNameRecursive(AccessibilityNodeInfo node, String className, List<AccessibilityNodeInfo> result) {
|
||||
if (node == null) return;
|
||||
|
||||
CharSequence cls = node.getClassName();
|
||||
if (cls != null && cls.toString().equals(className)) {
|
||||
result.add(node);
|
||||
}
|
||||
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
AccessibilityNodeInfo child = node.getChild(i);
|
||||
if (child != null) {
|
||||
findNodesByClassNameRecursive(child, className, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean clickAt(int x, int y) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Path path = new Path();
|
||||
path.moveTo(x, y);
|
||||
|
||||
GestureDescription.Builder builder = new GestureDescription.Builder();
|
||||
GestureDescription gesture = builder
|
||||
.addStroke(new GestureDescription.StrokeDescription(path, 0, 50))
|
||||
.build();
|
||||
|
||||
return dispatchGesture(gesture, null, null);
|
||||
}
|
||||
|
||||
public boolean setTextById(String resId, String text) {
|
||||
List<AccessibilityNodeInfo> nodes = findNodesByType("id", resId);
|
||||
if (nodes.isEmpty()) return false;
|
||||
|
||||
AccessibilityNodeInfo node = nodes.get(0);
|
||||
if (!node.isEditable()) return false;
|
||||
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
|
||||
return node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
|
||||
}
|
||||
}
|
||||
185
android/src/main/java/com/rnbot/RNBotModule.java
Normal file
185
android/src/main/java/com/rnbot/RNBotModule.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package com.rnbot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RNBotModule extends ReactContextBaseJavaModule {
|
||||
private final ReactApplicationContext reactContext;
|
||||
|
||||
public RNBotModule(ReactApplicationContext context) {
|
||||
super(context);
|
||||
this.reactContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RNBotModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void requestPermission(Promise promise) {
|
||||
try {
|
||||
Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
promise.reject("NO_ACTIVITY", "Activity is null");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
|
||||
activity.startActivity(intent);
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void isServiceEnabled(Promise promise) {
|
||||
promise.resolve(RNBotAccessibilityService.isServiceRunning());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void findNodes(String type, String value, Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AccessibilityNodeInfo> nodes = service.findNodesByType(type, value);
|
||||
WritableArray result = new WritableNativeArray();
|
||||
|
||||
for (AccessibilityNodeInfo node : nodes) {
|
||||
WritableMap map = nodeToMap(node);
|
||||
result.pushMap(map);
|
||||
}
|
||||
|
||||
promise.resolve(result);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void click(int x, int y, Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
boolean success = service.clickAt(x, y);
|
||||
promise.resolve(success);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setText(String resId, String text, Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
boolean success = service.setTextById(resId, text);
|
||||
promise.resolve(success);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void back(Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
boolean success = service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
|
||||
promise.resolve(success);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void home(Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
boolean success = service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
|
||||
promise.resolve(success);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void recents(Promise promise) {
|
||||
try {
|
||||
RNBotAccessibilityService service = RNBotAccessibilityService.getInstance();
|
||||
if (service == null) {
|
||||
promise.reject("SERVICE_NOT_RUNNING", "无障碍服务未运行");
|
||||
return;
|
||||
}
|
||||
boolean success = service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS);
|
||||
promise.resolve(success);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private WritableMap nodeToMap(AccessibilityNodeInfo node) {
|
||||
WritableMap map = new WritableNativeMap();
|
||||
|
||||
if (node.getText() != null) {
|
||||
map.putString("text", node.getText().toString());
|
||||
}
|
||||
if (node.getContentDescription() != null) {
|
||||
map.putString("name", node.getContentDescription().toString());
|
||||
}
|
||||
if (node.getViewIdResourceName() != null) {
|
||||
map.putString("id", node.getViewIdResourceName());
|
||||
}
|
||||
if (node.getClassName() != null) {
|
||||
map.putString("className", node.getClassName().toString());
|
||||
}
|
||||
|
||||
android.graphics.Rect bounds = new android.graphics.Rect();
|
||||
node.getBoundsInScreen(bounds);
|
||||
|
||||
WritableMap boundsMap = new WritableNativeMap();
|
||||
boundsMap.putInt("left", bounds.left);
|
||||
boundsMap.putInt("top", bounds.top);
|
||||
boundsMap.putInt("right", bounds.right);
|
||||
boundsMap.putInt("bottom", bounds.bottom);
|
||||
boundsMap.putInt("centerX", (bounds.left + bounds.right) / 2);
|
||||
boundsMap.putInt("centerY", (bounds.top + bounds.bottom) / 2);
|
||||
|
||||
map.putMap("bounds", boundsMap);
|
||||
map.putBoolean("clickable", node.isClickable());
|
||||
map.putBoolean("editable", node.isEditable());
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
24
android/src/main/java/com/rnbot/RNBotPackage.java
Normal file
24
android/src/main/java/com/rnbot/RNBotPackage.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.rnbot;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RNBotPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
modules.add(new RNBotModule(reactContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRetrieveInteractiveWindows|flagReportViewIds"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:canPerformGestures="true"
|
||||
android:notificationTimeout="100" />
|
||||
Reference in New Issue
Block a user