Android-AccessibilityService

概述

AccessibilityService用于提供辅助功能服务,其在后台运行,并在触发AccessibilityEvents时由系统接收回调。此类事件表示用户界面中的某些状态转换,例如,焦点更改,按钮被单击等。此类服务可以可选地请求查询活动窗口内容的功能。

AccessibilityServiceInfo描述一个AccessibilityService,系统根据封装在此类中的信息将AccessibilityEvent通知给AccessibilityService。

AccessibilityService的生命周期仅由系统管理,并遵循Service的生命周期,用户只能通过在设备设置中显式打开服务来触发启动无障碍服务。系统绑定到服务后,它将调用AccessibilityService#onServiceConnected()。当用户在设备设置中将其关闭或调用AccessibilityService#disableSelf()时,AccessibilityService即会停止。

每个AccessibilityService在都是由AccessibilityManagerService注册的,当用户开启辅助服务后,系统会发送广播到AccessibilityManagerService,AccessibilityManagerService会注册AccessibilityService。当受到监控的App某个View发生了改变时,其内部会调用AccessibilityManager来发送Event。

声明

在Manifest文件中配置:

<service android:name=".MyAccessibilityService"        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">    <intent-filter>        <action android:name="android.accessibilityservice.AccessibilityService" />    </intent-filter></service>

配置

可以将AccessibilityService配置为接收特定类型的事件,仅侦听特定的程序包,在给定的时间范围内仅从每种类型获取事件一次,检索Window内容,指定设置Activity等。有两种配置的方法:

  1. 在Manifest中配置:

    <service android:name=".MyAccessibilityService"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /></service>

    在res目录下新建xml目录,配置accessibilityservice.xml如下:

    <?xml version="1.0" encoding="utf-8"?><accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds|flagRequestTouchExplorationMode" android:canRetrieveWindowContent="true" android:description="@string/description" android:notificationTimeout="100" android:packageNames="com.tencent.mm,com.eg.android.AlipayGphone" />

    与配置相关的可以参考官网:AccessibilityService

  2. 调用AccessibilityService#setServiceInfo(AccessibilityServiceInfo)进行配置,该方法任意时候都可以调用,用来动态改变该Service的配置。此方法仅允许设置动态可配置属性:

    • AccessibilityServiceInfo#eventTypes
    • AccessibilityServiceInfo#feedbackType
    • AccessibilityServiceInfo#flags
    • AccessibilityServiceInfo#notificationTimeout
    • AccessibilityServiceInfo#packageNames

Service Meta Data

属性描述
android:descriptionDescriptive text for the associated data
android:summaryThe summary for the item
android:settingsActivityActivity的Component name,允许用户修改此Service的设置
android:accessibilityEventTypes监视的动作,见AccessibilityEvent
android:packageNames监控的软件包名,使用逗号隔开
android:accessibilityFeedbackType提供反馈类型,语音震动等等,见AccessibilityServiceInfo
android:notificationTimeout两次相同类型的事件之间的间隔(以毫秒为单位)
android:accessibilityFlags监视的view的状态,见AccessibilityServiceInfo
android:canRetrieveWindowContent是否要能够检索活动窗口的内容,此设置不能在运行时改变
android:canRequestTouchExplorationMode请求触摸模式的属性,在这种模式下,可以通过手势浏览UI
android:canRequestEnhancedWebAccessibility请求增强的Web可访问性增强功能的属性
android:canRequestFilterKeyEvents请求过滤关键事件的属性
android:canControlMagnification控制显示倍率的属性
android:canPerformGestures能否执行手势的属性
android:canRequestFingerprintGestures能否从指纹传感器捕获手势的属性
android:nonInteractiveUiTimeoutAccessibilityManager.getRecommendedTimeoutMillis(int,int)中使用的建议超时(以毫秒为单位),为不包含交互式控件的UI返回合适的值
android:interactiveUiTimeoutAccessibilityManager.getRecommendedTimeoutMillis(int,int)中使用的建议超时(以毫秒为单位),为交互式控件的UI返回合适的值

Event types

  • AccessibilityEvent#TYPES_ALL_MASK
  • AccessibilityEvent#TYPE_VIEW_CLICKED
  • AccessibilityEvent#TYPE_VIEW_LONG_CLICKED
  • AccessibilityEvent#TYPE_VIEW_FOCUSED
  • AccessibilityEvent#TYPE_VIEW_SELECTED
  • AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
  • AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
  • AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
  • AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START
  • AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END
  • AccessibilityEvent#TYPE_VIEW_HOVER_ENTER
  • AccessibilityEvent#TYPE_VIEW_HOVER_EXIT
  • AccessibilityEvent#TYPE_VIEW_SCROLLED
  • AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
  • AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
  • AccessibilityEvent#TYPE_ANNOUNCEMENT
  • AccessibilityEvent#TYPE_GESTURE_DETECTION_START
  • AccessibilityEvent#TYPE_GESTURE_DETECTION_END
  • AccessibilityEvent#TYPE_TOUCH_INTERACTION_START
  • AccessibilityEvent#TYPE_TOUCH_INTERACTION_END
  • AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED
  • AccessibilityEvent#TYPE_WINDOWS_CHANGED
  • AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED

Feedback types

  • AccessibilityServiceInfo#FEEDBACK_ALL_MASK
  • AccessibilityServiceInfo#FEEDBACK_AUDIBLE:表示可听(非语音)反馈
  • AccessibilityServiceInfo#FEEDBACK_HAPTIC:表示触觉反馈
  • AccessibilityServiceInfo#FEEDBACK_SPOKEN:表示语音反馈
  • AccessibilityServiceInfo#FEEDBACK_VISUAL:表示视觉反馈
  • AccessibilityServiceInfo#FEEDBACK_GENERIC:表示一般反馈
  • AccessibilityServiceInfo#FEEDBACK_BRAILLE:表示盲文反馈

Flags

  • AccessibilityServiceInfo#FLAG_ENABLE_ACCESSIBILITY_VOLUME:此标志请求由AudioManager.STREAM_ACCESSIBILITY控制系统范围内所有具有AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY的音频轨道。
  • AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS:如果设置了此标志,通过View#IMPORTANT_FOR_ACCESSIBILITY_NO或View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS标记为对accessibility不重要的View,以及通过View#IMPORTANT_FOR_ACCESSIBILITY_AUTO标记为对accessibility潜在重要的View,在查询窗口内容时被报告,并且AccessibilityService也将从中接收事件。对于Android 4.1(API级别16)或更高版本的AccessibilityService,必须显式设置相关标志。
  • AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS:该标志请求AccessibilityService获得的包含源视图ID的AccessibilityNodeInfos。ID格式为"package:id/name"的标准资源名称,默认情况下未设置此标志。
  • AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON:系统的导航区域中将显示一个辅助功能按钮。
  • AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS:该标志要求系统过滤关键事件。
  • AccessibilityServiceInfo#FLAG_REQUEST_FINGERPRINT_GESTURES:将所有指纹手势发送到AccessibilityService。想要设置此标志的服务必须声明具有检索窗口内容的功能,在meta-data中配置R.attr.canRequestFingerprintGestures,见SERVICE_META_DATA
  • AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK。
  • AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE:该标志请求系统进入触摸浏览模式,系统将检测在触摸屏上执行的某些手势并通知此Service。在Android 4.3以上的设备必须声明canRequestTouchExplorationMode,见SERVICE_META_DATA
  • AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS:访问所有交互式窗口的内容。如果未设置此标志,服务将不会收到AccessibilityEvent.TYPE_WINDOWS_CHANGED事件,调用AccessibilityServiceAccessibilityService#getWindows()将返回一个空列表,而AccessibilityNodeInfo#getWindow()将返回null。必须声明canRetrieveWindowContent,见SERVICE_META_DATA

AccessibilityService

disableSelf

  • public final void disableSelf()
  • 关闭AccessibilityService服务。

findFocus

  • public AccessibilityNodeInfo findFocus(int focus)
  • 查找具有指定焦点类型的视图。
  • focus取值:AccessibilityNodeInfo#FOCUS_INPUT/AccessibilityNodeInfo#FOCUS_ACCESSIBILITY。

getAccessibilityButtonController

  • public final AccessibilityButtonController getAccessibilityButtonController()
  • 返回系统导航区域中的辅助功能按钮的控制器。
  • AccessibilityButtonController

getFingerprintGestureController

getServiceInfo

  • public final AccessibilityServiceInfo getServiceInfo()

getRootInActiveWindow

  • public AccessibilityNodeInfo getRootInActiveWindow()
  • 获取当前活动窗口中的根节点。

AccessibilityNodeInfo

概述

此类表示Window Content中的一个Node。

addAction

  • public void addAction(AccessibilityNodeInfo.AccessibilityAction action)
  • 添加可以在节点上执行的操作。
  • AccessibilityAction

performAction

  • public boolean performAction(int action)
  • public boolean performAction(int action, Bundle arguments)

findAccessibilityNodeInfosByText

  • public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text)
  • 客户有责任调用AccessibilityNodeInfo#recycle()来回收接收到的信息,以避免创建多个实例。
  • 这里的text不单单是TextView的Text,还包括一些组件的ContentDescription。

findAccessibilityNodeInfosByViewId

  • public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId)
  • 客户有责任调用AccessibilityNodeInfo#recycle()来回收接收到的信息,以避免创建多个实例。
  • 组件的id获取可以通过Android Studio内置的工具Layout Inspector查看。
  • viewId:pkg:id/name,例如com.miui.securitycenter:id/am_detail_perm

AccessibilityEvent

概述

继承自AccessibilityRecord,表示当用户界面中发生了关注事件时系统发送的事件。

obtain

  • public static AccessibilityEvent obtain()
  • public static AccessibilityEvent obtain(int eventType)
  • public static AccessibilityEvent obtain(AccessibilityEvent event)
  • 返回一个缓存的实例(如果有)或实例化一个新实例。

recycle

  • public void recycle()

AccessibilityRecord

概述

表示AccessibilityEvent中的一条记录,并包含有关其Source View的状态更改的信息。

原理解析

概述

AccessibilityService相关类图如下:

AccessibilityService类图

AccessibilityService

在AccessibilityService的onBind方法中返回了一个IAccessibilityServiceClientWrapper对象。

@Overridepublic final IBinder onBind(Intent intent) {    return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {        @Override        public void onServiceConnected() {            AccessibilityService.this.dispatchServiceConnected();        }        @Override        public void onInterrupt() {            AccessibilityService.this.onInterrupt();        }        @Override        public void onAccessibilityEvent(AccessibilityEvent event) {            AccessibilityService.this.onAccessibilityEvent(event);        }        @Override        public void init(int connectionId, IBinder windowToken) {            mConnectionId = connectionId;            mWindowToken = windowToken;            // The client may have already obtained the window manager, so            // update the default token on whatever manager we gave them.            final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);            wm.setDefaultToken(windowToken);        }        // ...    });}

Callbacks

该接口定义的方法与IAccessibilityServiceClient中是对应的:

public interface Callbacks {    void onAccessibilityEvent(AccessibilityEvent event);    void onInterrupt();    void onServiceConnected();    void init(int connectionId, IBinder windowToken);    boolean onGesture(int gestureId);    boolean onKeyEvent(KeyEvent event);    void onMagnificationChanged(@NonNull Region region,            float scale, float centerX, float centerY);    void onSoftKeyboardShowModeChanged(int showMode);    void onPerformGestureResult(int sequence, boolean completedSuccessfully);    void onFingerprintCapturingGesturesChanged(boolean active);    void onFingerprintGesture(int gesture);    void onAccessibilityButtonClicked();    void onAccessibilityButtonAvailabilityChanged(boolean available);}

IAccessibilityServiceClient

IAccessibilityServiceClient是一个aidl接口,其方法如下:

oneway interface IAccessibilityServiceClient {    void init(in IAccessibilityServiceConnection connection, int connectionId, IBinder windowToken);    void onAccessibilityEvent(in AccessibilityEvent event, in boolean serviceWantsEvent);    void onInterrupt();    void onGesture(int gesture);    void clearAccessibilityCache();    void onKeyEvent(in KeyEvent event, int sequence);    void onMagnificationChanged(int displayId, in Region region, float scale, float centerX, float centerY);    void onSoftKeyboardShowModeChanged(int showMode);    void onPerformGestureResult(int sequence, boolean completedSuccessfully);    void onFingerprintCapturingGesturesChanged(boolean capturing);    void onFingerprintGesture(int gesture);    void onAccessibilityButtonClicked();    void onAccessibilityButtonAvailabilityChanged(boolean available);}

IAccessibilityServiceClientWrapper

IAccessibilityServiceClientWrapper继承自IAccessibilityServiceClient.Stub,且实现了HandlerCaller.Callback接口,很显然与Binder IPC相关。其代码如下:

// IAccessibilityServiceClientWrapperpublic static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub implements HandlerCaller.Callback {    public IAccessibilityServiceClientWrapper(Context context, Looper looper,            Callbacks callback) {        mCallback = callback;        mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);    }    public void init(IAccessibilityServiceConnection connection, int connectionId,            IBinder windowToken) {        Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,                connection, windowToken);        mCaller.sendMessage(message);    }    public void onInterrupt() {        Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);        mCaller.sendMessage(message);    }    public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {        Message message = mCaller.obtainMessageBO(                DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event);        mCaller.sendMessage(message);    }    @Override    public void executeMessage(Message message) {        switch (message.what) {            case DO_ON_ACCESSIBILITY_EVENT: {                AccessibilityEvent event = (AccessibilityEvent) message.obj;                boolean serviceWantsEvent = message.arg1 != 0;                if (event != null) {                    // Send the event to AccessibilityCache via AccessibilityInteractionClient                    AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);                    if (serviceWantsEvent                            && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {                        // Send the event to AccessibilityService                        mCallback.onAccessibilityEvent(event);                    }                    // Make sure the event is recycled.                    try {                        event.recycle();                    } catch (IllegalStateException ise) {                        /* ignore - best effort */                    }                }            } return;            case DO_ON_INTERRUPT: {                if (mConnectionId != AccessibilityInteractionClient.NO_ID) {                    mCallback.onInterrupt();                }            } return;            case DO_INIT: {                mConnectionId = message.arg1;                SomeArgs args = (SomeArgs) message.obj;                IAccessibilityServiceConnection connection =                        (IAccessibilityServiceConnection) args.arg1;                IBinder windowToken = (IBinder) args.arg2;                args.recycle();                if (connection != null) {                    AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,                            connection);                    mCallback.init(mConnectionId, windowToken);                    mCallback.onServiceConnected();                } else {                    AccessibilityInteractionClient.getInstance().removeConnection(                            mConnectionId);                    mConnectionId = AccessibilityInteractionClient.NO_ID;                    AccessibilityInteractionClient.getInstance().clearCache();                    mCallback.init(AccessibilityInteractionClient.NO_ID, null);                }            } return;            // ...        }    }}// HandlerCaller.Callbackpublic interface Callback {    public void executeMessage(Message msg);}

AccessibilityInteractionClient

AccessibilityService用来进程跨进程通信,最终执行findAccessibilityNodeInfosByXXX和performAction的是AccessibilityInteractionClient。

其执行流程总结如下:

  1. AccessibilityInteractionClient没做什么操作,直接通过Binder调用了AccessibilityManagerService对应的方法。
  2. AccessibilityManagerService最终还是通过Binder调用了ViewRootImpl对应的方法。
  3. ViewRootImpl仅作为Binder中的服务端接收调用,真正的操作交给AccessibilityInteractionController来做。
  4. AccessibilityInteractionController对应的方法被调用之后,并没有直接进行操作,而是通过Handler做了一次转发,以便从Binder线程转到UI线程。
  5. 以performAction(ACTION_CLICK)点击事件为例,最终调用的实际是View的mOnClickListener。
  6. 以findAccessibilityNodeInfosByText为例,最终调用的实际是View的findViewsWithText方法,其方法内部实际对比的值是mContentDescription。需要特别说明的是TextView重写了该方法,其内部实际对比的值是mText。

AccessibilityInteractionClient流程图

流程总结

当View发生改变时,会发出一个AccessibilityEvent出来,这个Event会通过Binder驱动发送给IAccessibilityServiceClientWrapper,调用他的onAccessibilityEvent(AccessibilityEvent)方法,这个方法通过Handler发送了一个Message给自己,目的是为了从Binder线程转回主线程。然后调用了mCallback.onAccessibilityEvent(event),间接的调用了AccessibilityService.this.onAccessibilityEvent(event),即我们自己实现的方法。

AccessibilityService流程图

其外部调用的流程如下:

  1. 用户在设置页面启动了某个辅助模式服务;
  2. 系统发送了一条广播到AccessibilityManagerService,收到广播后,AccessibilityManagerService绑定了我们写的AccessibilityService,就这样调用了onBind方法。AIDL的Server端准备好了,AccessibilityManagerService是一个系统服务,由SystemService启动。
  3. 受到监控的App某个View发生了改变,其内部都会调用AccessibilityManager来发送event,其具体发送的对象是ViewRootImpl类来做的。
  4. 发出event后会通过Binder驱动调用到AccessibilityService,最终调用了我们复写的onAccessibilityEvent方法。
  5. 每一个View在AccessibilityService中都会被映射为一个AccessibilityNodeInfo对象,我们通过这个对象去查找具体View、触发事件,其本质是调用了AccessibilityInteractionClient类的对应方法。
  6. AccessibilityInteractionClient是一个可以执行accessibility交互的单例对象,它查询远程视图层次结构,查看视图的快照,以及来自这些层次结构的请求,以便在视图上执行某些操作。
  7. 如果利用AccessibilityInteractionClient操作正在被监控的App,比如点击按钮,那么View发生变化,又发送出一个Event,这样便形成一个循环。

AccessibilityService外部流程图

防御措施

检测辅助模式的开启

可以通过AccessibilityManagerService提供的方法获取所有的辅助模式应用:

public class AccessibilityManagerService extends IAccessibilityManager.Stub {    @Override    public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {        // ...    }}

AccessibilityManagerService是com.android.server.accessibility包下的类,没有办法直接使用,可以通过AccessibilityManager来间接的操作AccessibilityManagerService,其内部利用Binder间接的调用了AccessibilityManagerService。代码如下:

/** * 取得正在监控目标包名的AccessibilityService */private List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(String targetPackage) {    List<AccessibilityServiceInfo> result = new ArrayList<>();    AccessibilityManager accessibilityManager = (AccessibilityManager) getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);    if (accessibilityManager == null) {        return result;    }    List<AccessibilityServiceInfo> infoList = accessibilityManager.getInstalledAccessibilityServiceList();    if (infoList == null || infoList.size() == 0) {        return result;    }    for (AccessibilityServiceInfo info : infoList) {        if (info.packageNames == null) {            result.add(info);        } else {            for (String packageName : info.packageNames) {                if (targetPackage.equals(packageName)) {                    result.add(info);                }            }        }    }    return result;}

当info.packageNames为null时,表示监控所有包名。外挂有可能蒙混其中,但如果一刀切,也有可能误杀正常软件。

Event干扰

随机发送外挂感兴趣的Event出来,干扰其允许。代码如下:

textView.sendAccessibilityEvent(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);

屏蔽AccessibilityServices文案检查

在了解AccessibilityServices源码之后,我们知道其内部核心原理就是调用TextView的findViewsWithText方法,因此可以复写这个方法:

public class DefensiveTextView extends android.support.v7.widget.AppCompatTextView {    @Override    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {        outViews.remove(this);    }}

屏蔽AccessibilityServices点击事件

像上面一样,通过源码了解原理之后,我们知道AccessibilityServices执行点击事件最终在调用View的mOnClickListener。因此可以利用onTouch代替onClick。

使用实例

开启辅助权限

public class AccessibilityHelper {    private static boolean hasAccessibility(Context context, Class serviceClass) {        if (context == null || serviceClass == null) {            return false;        }        int ok = 0;        try {            ok = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);        } catch (Settings.SettingNotFoundException e) {        }        TextUtils.SimpleStringSplitter ms = new TextUtils.SimpleStringSplitter(':');        if (ok == 1) {            String settingValue = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);            if (settingValue != null) {                ms.setString(settingValue);                while (ms.hasNext()) {                    String accessibilityService = ms.next();                    if (accessibilityService.contains(serviceClass.getSimpleName())) {                        return true;                    }                }            }        }        return false;    }    public static void openAccessibility(Context context, Class service) {        if (context == null || service == null) {            return;        }        if (hasAccessibility(context, service)) {            return;        }        Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);        context.startActivity(intent);    }}

实现AccessibilityService

public class CallAccessibilityService extends AccessibilityService {    private static final String TAG = "CallAccessibility";    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        Log.d(TAG, "onAccessibilityEvent: " + event.toString());        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {            AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();            if (nodeInfo != null) {                List<AccessibilityNodeInfo> infos = nodeInfo.findAccessibilityNodeInfosByText("选择来电秀视频");                for (AccessibilityNodeInfo info : infos) {                    info.performAction(AccessibilityNodeInfo.ACTION_CLICK);                }            }        }    }    @Override    public void onInterrupt() {        Log.d(TAG, "onInterrupt");    }}

声明AccessibilityService

<service    android:name=".accessibility.CallAccessibilityService"    android:enabled="true"    android:exported="true"    android:label="@string/app_name"    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/accessibility" /></service>

配置

<?xml version="1.0" encoding="utf-8"?><accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"    android:accessibilityEventTypes="typeViewClicked"    android:accessibilityFeedbackType="feedbackGeneric"    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds|flagRequestTouchExplorationMode"    android:canRetrieveWindowContent="true"    android:description="@string/description"    android:notificationTimeout="100"    android:packageNames="com.hearing.calltest" />

最后分享一套由阿里高级架构师编写的《Android八大模块进阶资料》,帮助大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要《Android八大模块进阶资料》完整文档的可以加微信 即可免费领取!

PS:(文末还有使用ChatGPT机器人小福利哦!!大家不要错过)

《Android八大模块进阶笔记》

在这里插入图片描述

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、源码解析合集

在这里插入图片描述

二、开源框架合集

在这里插入图片描述

同时这里还搭建了一个基于chatGPT的微信群聊机器人,24小时为大家解答疑难技术问题

图片

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值