Android無(wú)障礙輔助功能(AccessibilityService)學(xué)習(xí)

一.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ù)");
    }
}
3.十七歲辣雞少年正在學(xué)習(xí)...篷角,大佬輕拍,有興趣加微互相交流…下方評(píng)論吐槽一下吧系任,轉(zhuǎn)載請(qǐng)標(biāo)記出處
AceCode
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末内地,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赋除,更是在濱河造成了極大的恐慌阱缓,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,949評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件举农,死亡現(xiàn)場(chǎng)離奇詭異荆针,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)颁糟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,772評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門航背,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人棱貌,你說(shuō)我怎么就攤上這事玖媚。” “怎么了婚脱?”我有些...
    開封第一講書人閱讀 158,419評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵今魔,是天一觀的道長(zhǎng)勺像。 經(jīng)常有香客問(wèn)我,道長(zhǎng)错森,這世上最難降的妖魔是什么吟宦? 我笑而不...
    開封第一講書人閱讀 56,812評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮涩维,結(jié)果婚禮上殃姓,老公的妹妹穿的比我還像新娘。我一直安慰自己瓦阐,他們只是感情好蜗侈,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,927評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著睡蟋,像睡著了一般踏幻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上薄湿,一...
    開封第一講書人閱讀 50,102評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音偷卧,去河邊找鬼豺瘤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛听诸,可吹牛的內(nèi)容都是我干的坐求。 我是一名探鬼主播,決...
    沈念sama閱讀 39,171評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼晌梨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桥嗤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起仔蝌,我...
    開封第一講書人閱讀 37,921評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泛领,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后敛惊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渊鞋,經(jīng)...
    沈念sama閱讀 44,366評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,675評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瞧挤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锡宋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,820評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡特恬,死狀恐怖执俩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癌刽,我是刑警寧澤役首,帶...
    沈念sama閱讀 34,523評(píng)論 4 335
  • 正文 年R本政府宣布尝丐,位于F島的核電站,受9級(jí)特大地震影響宋税,放射性物質(zhì)發(fā)生泄漏摊崭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,162評(píng)論 3 317
  • 文/蒙蒙 一杰赛、第九天 我趴在偏房一處隱蔽的房頂上張望呢簸。 院中可真熱鬧,春花似錦乏屯、人聲如沸根时。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,885評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛤迎。三九已至,卻和暖如春含友,著一層夾襖步出監(jiān)牢的瞬間替裆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,126評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工窘问, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆童,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,647評(píng)論 2 362
  • 正文 我出身青樓惠赫,卻偏偏與公主長(zhǎng)得像把鉴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子儿咱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,732評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容