最近調研車機旋鈕控制操作第三方應用(高德,百度等;很多中高端汽車中控屏採用旋鈕+按鍵控制,屏幕不能觸控)需要用到輔助功能,百度查了下,然後就直接開幹了。
不要跟我說什麼底層原理,框架內核,老夫敲代碼就是一把梭,複製,粘貼,拿起鍵盤就是幹!
第一步,建個demo工程,建一個繼承AccessibilityService的類;
public class MyService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
boolean openAccessibility = AccessbilityTool.isOpenAccessibility(this);
if (!openAccessibility) {
String action = Settings.ACTION_ACCESSIBILITY_SETTINGS;
startActivity(new Intent(action));
}
}
@Override
public void onInterrupt() {
}
}
2,創建配置文件、res/xml/service_conf.xml (至於裏面的參數具體作用--百度)
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask|typeViewClicked|typeViewFocused|typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric|feedbackSpoken"
android:accessibilityFlags="flagDefault|flagReportViewIds|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true"
android:description="@string/app_name"
android:notificationTimeout="10"
android:packageNames="com.autonavi.amapauto"> //需要監聽(模擬控制)的app的包名
</accessibility-service>
3.清單文件配置 AndroidManifest.xml:
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- android:theme="@android:style/Theme.Translucent"-->
<service
android:name=".TestService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/service_conf" />
</service>
</application>
5,判斷app是否開啓輔助功能:
public static boolean isOpenAccessibility(Context context) {
try {
String service = context.getPackageName() + "/" + TestService.class.getCanonicalName();
int accessibility = Settings.Secure.getInt(context.getApplicationContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
TextUtils.SimpleStringSplitter stringSplitter = new TextUtils.SimpleStringSplitter(':');
Log.e(TAG, "accessibility: " + accessibility);
if (accessibility == 1) {
String value = Settings.Secure.getString(context.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (!TextUtils.isEmpty(value)) {
stringSplitter.setString(value);
while (stringSplitter.hasNext()) {
String next = stringSplitter.next();
if (next.equalsIgnoreCase(service)) {
return true;
}
}
}
}
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
return false;
}
6.接下來就是在自己的AccessibilityService中遍歷查找相關的組件節點了:
//id格式
String viewId = nodeInfo.getViewIdResourceName();
String viewId = "com.autonavi.amapauto:id/sftv_off";
//遍歷當前窗口界面的節點信息--對於自定義控件幾乎讀取不到
//對於控制別人的app這種方式幾乎無用,我這個案例就是反編譯高德車機地圖的res文件,通過id和文字反推節點信息
AccessibilityNodeInfo root = getRootInActiveWindow();
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo child = root.getChild(i);
String resName = child.getViewIdResourceName();
Log.e(TAG, "resName: " + resName);
}
/**
* 對指定組件節點觸發點擊事件;比如TextView,Button,LinearLayout等
*
* @param viewId
*/
private void setViewClick(String viewId) {
Log.e(TAG, "點擊指定組件View>>>>>>>");
AccessibilityNodeInfo root = getRootInActiveWindow();
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByViewId(viewId);
if (infoList != null && infoList.size() > 0) {
for (AccessibilityNodeInfo nodeInfo : infoList) {
if (nodeInfo != null) {
performClick(nodeInfo);
}
}
} else {
Log.e(TAG, viewId + " = is null");
}
}
/**
* 設置ListView列表逐行往下滾動(GridView也類似)
*
* @param viewId
*/
private void setListScrollDown(String viewId) {
Log.e(TAG, "列表往下滾動>>>>>");
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
return;
}
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByViewId(viewId);
Log.e(TAG, "list.size: " + infoList.size());
if (infoList != null && infoList.size() > 0) {
AccessibilityNodeInfo nodeInfo = infoList.get(0);
if (nodeInfo != null && getNodeClass(nodeInfo).contains("ListView")) {
Log.e(TAG, "item 數量: " + nodeInfo.getChildCount());
for (int k = 0; k < nodeInfo.getChildCount(); k++) {
AccessibilityNodeInfo child = nodeInfo.getChild(k);
if (child != null) {
//逐行滾動。
child.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
child.performAction(AccessibilityNodeInfo.ACTION_SELECT);
// getItemInfoClick(child.getViewIdResourceName());
}
}
}
}
}
/**
* 設置選中列表指定item並觸發點擊事件,(GridView也類似)
* infoList的大小爲當前可見item數量,position的值爲當前列表item的位置
* @param viewId
* @param position
*/
private void setSelectedListItem(String viewId, int position) {
Log.e(TAG, "選中ListView列表item>>>>>");
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
return;
}
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByViewId(viewId);
Log.e(TAG, "list.size: " + infoList.size());
if (infoList != null && infoList.size() > 0) {
AccessibilityNodeInfo nodeInfo = infoList.get(0);
if (nodeInfo != null && getNodeClass(nodeInfo).contains("ListView")) {
int childCount = nodeInfo.getChildCount();
if (position >= 0 && position <= childCount-1) {
AccessibilityNodeInfo child = nodeInfo.getChild(position);
if (child != null) {
child.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
child.performAction(AccessibilityNodeInfo.ACTION_SELECT);
child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
} else {
AccessibilityNodeInfo child = nodeInfo.getChild(0);
child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
/**
* 設置ListView滾動到頂部(GridView也類似)
*
* @param viewId
*/
private void setListScrollTop(String viewId) {
Log.e(TAG, "列表往上滾動到頂部>>>>>");
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
return;
}
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByViewId(viewId);
Log.e(TAG, "list.size: " + infoList.size());
if (infoList != null && infoList.size() > 0) {
AccessibilityNodeInfo nodeInfo = infoList.get(0);
if (nodeInfo != null && getNodeClass(nodeInfo).contains("ListView")) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
}
}
/**
* 路線規劃列表設置選中路線
* 對於LinearLayout的子view以及RadioGroup等組件內的子控件點擊事件可用
* @param viewId
* @param position 需要選中的路線
*/
private void setSelectedRoute(String viewId, int position) {
Log.e(TAG, "setSelectedRoute");
AccessibilityNodeInfo root = getRootInActiveWindow();
if (root == null) {
return;
}
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByViewId(viewId);
if (infoList != null && infoList.size() == 1) {
int childCount = infoList.get(0).getChildCount();
if (position >= 0 && position <= childCount - 1) {
AccessibilityNodeInfo child = infoList.get(0).getChild(position);
child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
} else {
AccessibilityNodeInfo child = infoList.get(0).getChild(0);
child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
/**
* 獲取組件節點區域座標
*
* @param nodeInfo
*/
private void getNodeRect(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect); //相對於屏幕的位置
// nodeInfo.getBoundsInParent(rect);//相對於父控件
Log.e(TAG, "nodeInfo.rect: " + rect.left + "/" + rect.top + "/" + rect.right + "/" + rect.bottom);
}
/**
* 根據文字獲取組件id及事件處理
*
* @param text
*/
private void getNodeByText(String text) {
Log.e(TAG, "getNodeByText");
AccessibilityNodeInfo root = getRootInActiveWindow();
List<AccessibilityNodeInfo> infoList = root.findAccessibilityNodeInfosByText(text);
if (infoList != null && infoList.size() > 0) {
Log.e(TAG, "getNodeByText infoList.size=>" + infoList.size());
for (AccessibilityNodeInfo nodeInfo : infoList) {
if (nodeInfo != null) {
Log.e(TAG, "getNodeByText nodeInfo.name=>" + nodeInfo.getViewIdResourceName());
getNodeRect(nodeInfo);
performClick(nodeInfo);
}
}
} else {
Log.e(TAG, text + "view = is null");
}
}
7.問題:
對於高德導航頁面點擊地圖彈出退出導航,繼續導航菜單的模擬點擊目前無法實現,只能採用adb命令模擬點擊屏幕實現;
另外對於ScrollView的滑動問題一直沒有找到解決方式;