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 (至于里面的参数具体作用--百度)

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

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值