Android辅助功能AccessibilityService控制第三方app

最近调研车机旋钮控制操作第三方应用(高德,百度等;很多中高端汽车中控屏采用旋钮+按键控制,屏幕不能触控)需要用到辅助功能,百度查了下,然后就直接开干了。

不要跟我说什么底层原理,框架内核,老夫敲代码就是一把梭,复制,粘贴,拿起键盘就是干!

第一步,建个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的滑动问题一直没有找到解决方式;

你可能感兴趣的:(Android辅助功能AccessibilityService控制第三方app)