最近调研车机旋钮控制操作第三方应用(高德,百度等;很多中高端汽车中控屏采用旋钮+按键控制,屏幕不能触控)需要用到辅助功能,百度查了下,然后就直接开干了。
不要跟我说什么底层原理,框架内核,老夫敲代码就是一把梭,复制,粘贴,拿起键盘就是干!
第一步,建个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 (至于里面的参数具体作用--百度)
//需要监听(模拟控制)的app的包名
3.清单文件配置 AndroidManifest.xml:
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 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 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 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 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 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 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的滑动问题一直没有找到解决方式;