first blood
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
android/build
|
||||||
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" />
|
||||||
15
package.json
Normal file
15
package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "rnauto",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "React Native 无障碍自动化库",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": ["react-native", "accessibility", "automation"],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/index.ts
Normal file
123
src/index.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
|
||||||
|
import { NativeModules, Platform } from 'react-native';
|
||||||
|
|
||||||
|
const { RNBotModule } = NativeModules;
|
||||||
|
|
||||||
|
export interface NodeBounds {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
centerX: number;
|
||||||
|
centerY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeInfo {
|
||||||
|
text?: string;
|
||||||
|
name?: string;
|
||||||
|
id?: string;
|
||||||
|
className?: string;
|
||||||
|
bounds: NodeBounds;
|
||||||
|
clickable: boolean;
|
||||||
|
editable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RNAuto {
|
||||||
|
private checkAndroid(): boolean {
|
||||||
|
if (Platform.OS !== 'android') {
|
||||||
|
console.warn('RNAuto 仅支持 Android');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 请求无障碍权限 */
|
||||||
|
async requestPermission(): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 检查服务是否运行 */
|
||||||
|
async isServiceEnabled(): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.isServiceEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ID 查找节点 */
|
||||||
|
async id(resId: string): Promise<NodeInfo[]> {
|
||||||
|
if (!this.checkAndroid()) return [];
|
||||||
|
return await RNBotModule.findNodes('id', resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据文本查找节点 */
|
||||||
|
async text(text: string): Promise<NodeInfo[]> {
|
||||||
|
if (!this.checkAndroid()) return [];
|
||||||
|
return await RNBotModule.findNodes('text', text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 ContentDescription 查找节点 */
|
||||||
|
async name(name: string): Promise<NodeInfo[]> {
|
||||||
|
if (!this.checkAndroid()) return [];
|
||||||
|
return await RNBotModule.findNodes('name', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据类名查找节点 */
|
||||||
|
async className(className: string): Promise<NodeInfo[]> {
|
||||||
|
if (!this.checkAndroid()) return [];
|
||||||
|
return await RNBotModule.findNodes('className', className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击坐标 */
|
||||||
|
async click(x: number, y: number): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.click(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击节点 */
|
||||||
|
async clickNode(resId: string): Promise<boolean> {
|
||||||
|
const nodes = await this.id(resId);
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
const { centerX, centerY } = nodes[0].bounds;
|
||||||
|
return await this.click(centerX, centerY);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设置文本 */
|
||||||
|
async setText(resId: string, text: string): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.setText(resId, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 返回键 */
|
||||||
|
async back(): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Home 键 */
|
||||||
|
async home(): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.home();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 最近任务键 */
|
||||||
|
async recents(): Promise<boolean> {
|
||||||
|
if (!this.checkAndroid()) return false;
|
||||||
|
return await RNBotModule.recents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 延迟 */
|
||||||
|
delay(ms: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 别名兼容
|
||||||
|
fullId = this.id;
|
||||||
|
findById = this.id;
|
||||||
|
findByText = this.text;
|
||||||
|
findByName = this.name;
|
||||||
|
findByClassName = this.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new RNAuto();
|
||||||
Reference in New Issue
Block a user