概述 AccessibilityService用于提供辅助功能服务,其在后台运行,并在触发AccessibilityEvents时由系统接收回调。此类事件表示用户界面中的某些状态转换,例如,焦点更改,按钮被单击等。此类服务可以可选地请求查询活动窗口内容的功能。
AccessibilityServiceInfo描述一个AccessibilityService,系统根据封装在此类中的信息将AccessibilityEvent通知给AccessibilityService。
AccessibilityService的生命周期仅由系统管理,并遵循Service的生命周期,用户只能通过在设备设置中显式打开服务来触发启动无障碍服务。系统绑定到服务后,它将调用AccessibilityService#onServiceConnected()。当用户在设备设置中将其关闭或调用AccessibilityService#disableSelf()时,AccessibilityService即会停止。
每个AccessibilityService在都是由AccessibilityManagerService注册的,当用户开启辅助服务后,系统会发送广播到AccessibilityManagerService,AccessibilityManagerService会注册AccessibilityService。当受到监控的App某个View发生了改变时,其内部会调用AccessibilityManager来发送Event。
声明 在Manifest文件中配置:
1 2 3 4 5 6 <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等。有两种配置的方法:
在Manifest中配置:
1 2 3 4 5 6 <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如下:
1 2 3 4 5 6 7 8 9 <?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
调用AccessibilityService#setServiceInfo(AccessibilityServiceInfo)进行配置,该方法任意时候都可以调用,用来动态改变该Service的配置。此方法仅允许设置动态可配置属性:
AccessibilityServiceInfo#eventTypes
AccessibilityServiceInfo#feedbackType
AccessibilityServiceInfo#flags
AccessibilityServiceInfo#notificationTimeout
AccessibilityServiceInfo#packageNames
属性
描述
android:description
Descriptive text for the associated data
android:summary
The summary for the item
android:settingsActivity
Activity的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:nonInteractiveUiTimeout
AccessibilityManager.getRecommendedTimeoutMillis(int,int)中使用的建议超时(以毫秒为单位),为不包含交互式控件的UI返回合适的值
android:interactiveUiTimeout
AccessibilityManager.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。
getFingerprintGestureController
getServiceInfo
public final AccessibilityServiceInfo getServiceInfo()
getRootInActiveWindow
public AccessibilityNodeInfo getRootInActiveWindow()
获取当前活动窗口中的根节点。
AccessibilityNodeInfo 概述 此类表示Window Content中的一个Node。
addAction
public void addAction(AccessibilityNodeInfo.AccessibilityAction action)
添加可以在节点上执行的操作。
见AccessibilityAction
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
AccessibilityRecord 概述 表示AccessibilityEvent中的一条记录,并包含有关其Source View的状态更改的信息。
原理解析 概述 AccessibilityService相关类图如下:
AccessibilityService 在AccessibilityService的onBind方法中返回了一个IAccessibilityServiceClientWrapper对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Override public 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; final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE); wm.setDefaultToken(windowToken); } }); }
Callbacks 该接口定义的方法与IAccessibilityServiceClient中是对应的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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接口,其方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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相关。其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 public 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 ); } 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 ) { AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); if (serviceWantsEvent && (mConnectionId != AccessibilityInteractionClient.NO_ID)) { mCallback.onAccessibilityEvent(event); } try { event.recycle(); } catch (IllegalStateException ise) { } } } 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 ; } } } public interface Callback { public void executeMessage (Message msg) ; }
AccessibilityInteractionClient AccessibilityService用来进程跨进程通信,最终执行findAccessibilityNodeInfosByXXX和performAction的是AccessibilityInteractionClient。
其执行流程总结如下:
AccessibilityInteractionClient没做什么操作,直接通过Binder调用了AccessibilityManagerService对应的方法。
AccessibilityManagerService最终还是通过Binder调用了ViewRootImpl对应的方法。
ViewRootImpl仅作为Binder中的服务端接收调用,真正的操作交给AccessibilityInteractionController来做。
AccessibilityInteractionController对应的方法被调用之后,并没有直接进行操作,而是通过Handler做了一次转发,以便从Binder线程转到UI线程。
以performAction(ACTION_CLICK)点击事件为例,最终调用的实际是View的mOnClickListener。
以findAccessibilityNodeInfosByText为例,最终调用的实际是View的findViewsWithText方法,其方法内部实际对比的值是mContentDescription。需要特别说明的是TextView重写了该方法,其内部实际对比的值是mText。
流程总结 当View发生改变时,会发出一个AccessibilityEvent出来,这个Event会通过Binder驱动发送给IAccessibilityServiceClientWrapper,调用他的onAccessibilityEvent(AccessibilityEvent)
方法,这个方法通过Handler发送了一个Message给自己,目的是为了从Binder线程转回主线程。然后调用了mCallback.onAccessibilityEvent(event)
,间接的调用了AccessibilityService.this.onAccessibilityEvent(event)
,即我们自己实现的方法。
其外部调用的流程如下:
用户在设置页面启动了某个辅助模式服务;
系统发送了一条广播到AccessibilityManagerService,收到广播后,AccessibilityManagerService绑定了我们写的AccessibilityService,就这样调用了onBind方法。AIDL的Server端准备好了,AccessibilityManagerService是一个系统服务,由SystemService启动。
受到监控的App某个View发生了改变,其内部都会调用AccessibilityManager来发送event,其具体发送的对象是ViewRootImpl类来做的。
发出event后会通过Binder驱动调用到AccessibilityService,最终调用了我们复写的onAccessibilityEvent方法。
每一个View在AccessibilityService中都会被映射为一个AccessibilityNodeInfo对象,我们通过这个对象去查找具体View、触发事件,其本质是调用了AccessibilityInteractionClient类的对应方法。
AccessibilityInteractionClient是一个可以执行accessibility交互的单例对象,它查询远程视图层次结构,查看视图的快照,以及来自这些层次结构的请求,以便在视图上执行某些操作。
如果利用AccessibilityInteractionClient操作正在被监控的App,比如点击按钮,那么View发生变化,又发送出一个Event,这样便形成一个循环。
防御措施 检测辅助模式的开启 可以通过AccessibilityManagerService提供的方法获取所有的辅助模式应用:
1 2 3 4 5 6 public class AccessibilityManagerService extends IAccessibilityManager .Stub { @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList (int userId) { } }
AccessibilityManagerService是com.android.server.accessibility包下的类,没有办法直接使用,可以通过AccessibilityManager来间接的操作AccessibilityManagerService,其内部利用Binder间接的调用了AccessibilityManagerService。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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出来,干扰其允许。代码如下:
1 textView.sendAccessibilityEvent(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
屏蔽AccessibilityServices文案检查 在了解AccessibilityServices源码之后,我们知道其内部核心原理就是调用TextView的findViewsWithText方法,因此可以复写这个方法:
1 2 3 4 5 6 7 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。
使用实例 开启辅助权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <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 >
配置 1 2 3 4 5 6 7 8 9 <?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" />