一.Android Accessibility 簡(jiǎn)介
對(duì)于那些失明或低視力谆膳,色盲苹威,耳聾或聽力受損腕铸,以及運(yùn)動(dòng)技能受限的用戶间驮,Android
提供了Accessibility
(輔助功能/無(wú)障礙)更加簡(jiǎn)單地操作設(shè)備躬厌,包括文字轉(zhuǎn)語(yǔ)音、觸覺(jué)反饋竞帽、手勢(shì)操作扛施、軌跡球和手柄操作等。
二.簡(jiǎn)單使用
1.在你的AndroidManifest
中:
<!--
android:label(可選)在輔助功能(無(wú)障礙)的系統(tǒng)設(shè)置會(huì)使用該名稱,若不設(shè)置,則會(huì)使用<applicationandroid:label
android:process(可選)把該服務(wù)設(shè)在單獨(dú)進(jìn)程中,進(jìn)程名以[冒號(hào):]開頭屹篓,是本應(yīng)用的私有進(jìn)程疙渣,其它應(yīng)用無(wú)法訪問(wèn)
android:permission(必需)添加權(quán)限以確保只有系統(tǒng)可以綁定到該服務(wù)
-->
<service
android:name=".MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<!--在xml文件配置輔助功能,也可在onServiceConnected()中使用setServiceInfo()動(dòng)態(tài)配置-->
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config"/>
</service>
2.在你的@xml/accessibility_service_config
中:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_doc"
android:notificationTimeout="100"
tools:ignore="UnusedAttribute" />
<!--
android:canPerformGestures="true"允許發(fā)送手勢(shì)android7.0以上
android:description輔助功能描述
android:packageNames指定輔助功能監(jiān)聽的包名,不指定表示監(jiān)聽所有應(yīng)用
android:accessibilityEventTypes事件類型堆巧,typeAllMask表示接收所有事件
android:accessibilityFlags查找截點(diǎn)方式妄荔,一般配置為flagDefault默認(rèn)方式
android:accessibilityFeedbackType操作按鈕以后給用戶的反饋類型,包括聲音谍肤,震動(dòng)等
android:notificationTimeout響應(yīng)超時(shí)
android:canRetrieveWindowContent是否允許提取窗口的節(jié)點(diǎn)信息
-->
3.創(chuàng)建MyAccessibilityService
繼承自 AccessibilityService
@SuppressLint("Registered")
public class MyAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
三.工具類封裝
1.工具類AccessibilityServiceUtil
封裝
abstract public class AccessibilityUtil {
//開關(guān)
public static boolean isService = false;
//判斷服務(wù)是否開啟
public static boolean isService(Context context, Class service) {
int enable = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0);
if (enable != 1) return false;
String services = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (!TextUtils.isEmpty(services)) {
TextUtils.SimpleStringSplitter split = new TextUtils.SimpleStringSplitter(':');
split.setString(services);
while (split.hasNext()) {
if (split.next().equalsIgnoreCase(context.getPackageName() + "/" + service.getName()))
return true;
}
}
return false;
}
//通過(guò)文字點(diǎn)擊指定項(xiàng)
public static boolean clickText(AccessibilityService service, String string, int location) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> nodeInfoByText = accessibilityNodeInfo.findAccessibilityNodeInfosByText(string);
List<AccessibilityNodeInfo> nodeInfoList = new ArrayList<>();
if (nodeInfoByText != null && !nodeInfoByText.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoByText) {
if (nodeInfo != null && (nodeInfo.getText().equals(string) || nodeInfo.getContentDescription().equals(string))) {
nodeInfoList.add(nodeInfo);
}
}
}
if (nodeInfoList.isEmpty()) return false;
if (nodeInfoList.size() > location) {
return click(nodeInfoList.get(location));
} else {
return click(nodeInfoList.get(0));
}
}
//通過(guò)文字點(diǎn)擊第一項(xiàng)
public static boolean clickText(AccessibilityService service, String string) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> nodeInfoByText = accessibilityNodeInfo.findAccessibilityNodeInfosByText(string);
if (nodeInfoByText != null && !nodeInfoByText.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoByText) {
if (nodeInfo != null && (nodeInfo.getText().equals(string) || nodeInfo.getContentDescription().equals(string))) {
return click(nodeInfo);
}
}
}
return false;
}
//通過(guò)文字長(zhǎng)按指定項(xiàng)
public static boolean longClickText(AccessibilityService service, String string, int location) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> nodeInfoByText = accessibilityNodeInfo.findAccessibilityNodeInfosByText(string);
List<AccessibilityNodeInfo> nodeInfoList = new ArrayList<>();
if (nodeInfoByText != null && !nodeInfoByText.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoByText) {
if (nodeInfo != null && (nodeInfo.getText().equals(string) || nodeInfo.getContentDescription().equals(string))) {
nodeInfoList.add(nodeInfo);
}
}
}
if (nodeInfoList.isEmpty()) return false;
if (nodeInfoList.size() > location) {
return longClick(nodeInfoList.get(location));
} else {
return longClick(nodeInfoList.get(0));
}
}
//通過(guò)文字長(zhǎng)按第一項(xiàng)
public static boolean longClickText(AccessibilityService service, String string) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> nodeInfoByText = accessibilityNodeInfo.findAccessibilityNodeInfosByText(string);
if (nodeInfoByText != null && !nodeInfoByText.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoByText) {
if (nodeInfo != null && (nodeInfo.getText().equals(string) || nodeInfo.getContentDescription().equals(string))) {
return longClick(nodeInfo);
}
}
}
return false;
}
//通過(guò)ID點(diǎn)擊指定項(xiàng)
public static boolean clickId(AccessibilityService service, String id, int location) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> infoByViewId = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
List<AccessibilityNodeInfo> nodeInfoList = new ArrayList<>();
if (infoByViewId != null && !infoByViewId.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : infoByViewId) {
if (nodeInfo != null) {
nodeInfoList.add(nodeInfo);
}
}
}
if (nodeInfoList.isEmpty()) return false;
if (nodeInfoList.size() > location) {
return click(nodeInfoList.get(location));
} else {
return click(nodeInfoList.get(0));
}
}
//通過(guò)ID點(diǎn)擊第一項(xiàng)
public static boolean clickId(AccessibilityService service, String id) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> infoByViewId = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (infoByViewId != null && !infoByViewId.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : infoByViewId) {
if (nodeInfo != null) {
return click(nodeInfo);
}
}
}
return false;
}
//通過(guò)ID長(zhǎng)按指定項(xiàng)
public static boolean longClickId(AccessibilityService service, String id, int location) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> infoByViewId = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
List<AccessibilityNodeInfo> nodeInfoList = new ArrayList<>();
if (infoByViewId != null && !infoByViewId.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : infoByViewId) {
if (nodeInfo != null) {
nodeInfoList.add(nodeInfo);
}
}
}
if (nodeInfoList.isEmpty()) return false;
if (nodeInfoList.size() > location) {
return longClick(nodeInfoList.get(location));
} else {
return longClick(nodeInfoList.get(0));
}
}
//通過(guò)ID長(zhǎng)按第一項(xiàng)
public static boolean longClickid(AccessibilityService service, String id) {
AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow();
if (accessibilityNodeInfo == null) return false;
List<AccessibilityNodeInfo> infoByViewId = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (infoByViewId != null && !infoByViewId.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : infoByViewId) {
if (nodeInfo != null) {
return longClick(nodeInfo);
}
}
}
return false;
}
//通過(guò)坐標(biāo)點(diǎn)擊
public static boolean coordinatesClick(AccessibilityService service, int x, int y) {
Path path = new Path();
path.moveTo(x, y);
GestureDescription.Builder builder = new GestureDescription.Builder();
GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build();
return service.dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
}, null);
}
//通過(guò)坐標(biāo)長(zhǎng)按
public static boolean coordinatesLongClick(AccessibilityService service, int x, int y) {
Path path = new Path();
path.moveTo(x, y);
GestureDescription.Builder builder = new GestureDescription.Builder();
GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 1000)).build();
return service.dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
}, null);
}
//自定義滑動(dòng)
public static boolean coordinateSliding(AccessibilityService service, int x0, int y0, int x1, int y1, int ms) {
Path path = new Path();
path.moveTo(x0, y0);
path.lineTo(x1, y1);
GestureDescription.Builder builder = new GestureDescription.Builder();
GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, ms)).build();
return service.dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
}, null);
}
//點(diǎn)擊
private static boolean click(AccessibilityNodeInfo node) {
if (node == null) return false;
if (node.isClickable()) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
} else {
click(node.getParent());
}
return false;
}
//長(zhǎng)按
private static boolean longClick(AccessibilityNodeInfo node) {
if (node == null) return false;
if (node.isLongClickable()) {
return node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
} else {
longClick(node.getParent());
}
return false;
}
//輸入
@SuppressLint("ObsoleteSdkInt")
public static boolean InputText(AccessibilityService service, AccessibilityNodeInfo nodeInfo, String text) {
if (service == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
return nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
ClipboardManager clipboard = (ClipboardManager) service.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("label", text);
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
}
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
}
return false;
}
//返回
@SuppressLint("ObsoleteSdkInt")
public static boolean Back(AccessibilityService service) {
if (service == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
} else {
return false;
}
}
//主頁(yè)
@SuppressLint("ObsoleteSdkInt")
public static boolean Home(AccessibilityService service) {
if (service == null) return false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
} else {
return false;
}
}
//上滑
public static boolean ScrollForward(AccessibilityNodeInfo node) {
if (node == null || !node.isScrollable()) return false;
return node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
//下滑
public static boolean ScrollBackward(AccessibilityNodeInfo node) {
if (node == null || !node.isScrollable()) return false;
return node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
//暫停
public static void Sleep(long millis) {
try {
Thread.sleep(millis);
} catch (Exception e) {
System.exit(0);
}
}
}
2.使用AccessibilityServiceUtil
啦租,例如:
public class MyAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//在服務(wù)正常的情況控制服務(wù),isService默認(rèn)為false,可在Activity中設(shè)置為true即可
if (AccessibilityUtil.isService) {
//通過(guò)坐標(biāo)點(diǎn)擊屏幕中的(500,500)位置直到成功
while (!AccessibilityUtil.coordinatesClick(this, 500, 500)) ;
//關(guān)閉服務(wù)荒揣,設(shè)置中仍然開啟
AccessibilityUtil.isService = false;
}
}
@Override
public void onInterrupt() {
System.out.print("已停止服務(wù)");
}
}