前言
此篇博客主要记录如何开启无障碍服务与功能使用。google的设计这个功能是用来帮助残障人士使用设备。 也能帮助我们开发者进行各种各样的全局事件监听(按键、触控手势、UI变化)这样可以免于修改framework插入事件监听。当然启动条件比较苛刻,需要用户手动打开,所以在正常的应用上应该用不上此功能。但是系统级别的应用上我们可以通过反射直接开启。 还有一些人还会使用此服务进行自动抢微信红包的无语行为。个人是测试转开发,我体验后无障碍服务更像是自动化uiautomator2测试的里的翻版。
添加无障碍服务
第一步 创建AccessibilityService服务类
import android.accessibilityservice.AccessibilityService
import android.content.Intent
import android.content.ServiceConnection
import android.util.Log
import android.view.KeyEvent
import android.view.accessibility.AccessibilityEvent
class AccessibilityService : AccessibilityService() {
override fun bindService(service: Intent?, conn: ServiceConnection, flags: Int): Boolean {
return super.bindService(service, conn, flags)
}
override fun onCreate() {
super.onCreate()
}
override fun onDestroy() {
super.onDestroy()
}
/**
* 无障碍服务的生命周期,表明服务已经连接成功
*/
override fun onServiceConnected() {
super.onServiceConnected()
}
/**
*当系统想要中断您的服务正在提供的反馈(通常是为了响应将焦点移到其他
*控件等用户操作)时,就会调用此方法。此方法可能会在您的服务的整个生命
*周期内被调用多次。
*/
override fun onInterrupt() {
}
/**
* 当用户在触摸屏上执行特定手势时由系统调用。注意:为了接收手势,
* 辅助服务必须通过设置AccessibilityServiceInfo请求设备处于触摸探索模式FLAG_REQUEST_TOUCH_EXPLORATION_MOD
*/
override fun onGesture(gestureId: Int): Boolean {
Log.e("zh", "onGesture: ${gestureId}")
return super.onGesture(gestureId)
}
/**
*当系统检测到与无障碍服务指定的事件过滤参数匹配的 AccessibilityEvent
*时,就会回调此方法。例如,当用户点击按钮,或者聚焦于某个应用(无障碍
*服务正在为该应用提供反馈)中的界面控件时。出现这种情况时,系统会调用
*此方法,并传递关联的 AccessibilityEvent,然后服务会对该类进行解释并
*使用它来向用户提供反馈。此方法可能会在您的服务的整个生命周期内被调用多次。
*/
override fun onAccessibilityEvent(event: AccessibilityEvent) {
Log.e("zh", "无障碍服务 onAccessibilityEvent:${event}")
when(event.eventType){
AccessibilityEvent.TYPE_ANNOUNCEMENT-> Log.e("zh", "应用程序发布公告的事件")
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> Log.e("zh", "View的焦点")
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED -> Log.e("zh", "View的焦点清除")
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> Log.e("zh", "通知栏状态更新")
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER -> Log.e("zh", "View的鼠标悬停选中")
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT -> Log.e("zh", "View的鼠标悬停离开")
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START -> Log.e("zh", "开始触摸探索手势的事件")
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END -> Log.e("zh", "结束触摸探索手势的事件")
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> Log.e("zh", "窗口内容更新")
AccessibilityEvent.TYPE_VIEW_SCROLLED -> Log.e("zh", "滚动类View")
AccessibilityEvent.TYPE_VIEW_SELECTED -> Log.e("zh", "表示通常在android.widget.AdapterView的上下文中选择项的事件")
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> Log.e("zh", "EditText视图选中内容改变")
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> Log.e("zh", "EditText视图内容改变")
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> Log.e("zh", "表示以给定的移动粒度遍历视图文本的事件")
AccessibilityEvent.TYPE_VIEW_CLICKED -> Log.e("zh", "点击事件")
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED -> Log.e("zh", "长按点击事件")
AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED -> Log.e("zh", "表示在android.view.View上的上下文单击事件")
AccessibilityEvent.TYPE_GESTURE_DETECTION_START -> Log.e("zh", "开始手势检测")
AccessibilityEvent.TYPE_GESTURE_DETECTION_END -> Log.e("zh", "结束手势检测")
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START -> Log.e("zh", "表示用户开始触摸屏幕的事件")
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END -> Log.e("zh", "表示用户结束触摸屏幕的事件")
AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT -> Log.e("zh", "表示助手当前正在读取用户屏幕上下文的事件。")
}
}
/**
* 按键事件
*/
override fun onKeyEvent(event: KeyEvent): Boolean {
Log.e("zh", "onKeyEvent: ${event}")
return super.onKeyEvent(event)
}
}
第二步 在xml资源目录下添加配置xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_name"
android:packageNames="com.zh.XXX,com.android.systemui"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews"
android:notificationTimeout="100"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true" />
android:description 此属性是在无障碍服务启用页面的描述
android:packageNames 此属性代表你需要那些应用支持无障碍服务,如果什么都不填删除此属性则代表你想监听设备的全部应用
android:accessibilityEventTypes 事件类型AccessibilityService服务响应的事件类型,只有声明了的类型,系统才会调用该服务的onAccessibilityEvent,有以下几个事件类型提供选择:
typeViewClicked 点击事件 | typeViewSelected View被选择 | typeViewScrolled 滑动事件 | typeWindowContentChanged 窗口内容该表 | typeAllMask 所有事件
android:accessibilityFeedbackType 反馈类型
feedbackSpoken 语音反馈 | feedbackHaptic 触觉(震动)反馈 | feedbackAudible 音频反馈 | feedbackVisual 视频反馈 | feedbackGeneric 通用反馈 | feedbackAllMask 以上都具有
android:accessibilityFlags 额外声明
flagDefault 默认
flagIncludeNotImportantViews
flagRequestTouchExplorationMode 允许获得触控信息,另外你还需要将android:canRequestTouchExplorationMode 属性设置为true。 请注意!此属性有一定的危险,添加此属性后有可能导致触控失效(触发条件可能是需要插入鼠标或者其他外置设备)
flagRequestEnhancedWebAccessibility 允许获取Web地址信息,另外你还需要将 android:canRequestEnhancedWebAccessibility 属性设置为true
flagReportViewIds 允许获得view id,需要获取viewid的时候需要该参数,开始没声明导致nodeInfo. getViewIdResourceName()返回的为null
flagRequestFilterKeyEvents 此事件添加后才能在服务的onKeyEvent方法里输出当前按键键值,另外你还需要将 android:canRequestFilterKeyEvents 属性设置为true
flagRetrieveInteractiveWindows 允许获得windows,使用getWindows时需要该参数,否则会返回空列表
android:canRetrieveWindowContent 设置为“true”表示允许获取屏幕信息,使用getWindows、getRootInActiveWindow等函数时需要为“true”
android:canRequestTouchExplorationMode 设置为“true”表示允许获取触摸信息
android:canRequestEnhancedWebAccessibility 设置为“true”表示允许获取Web地址访问信息
android:canRequestFilterKeyEvents 设置为“true”表示允许获取按键信息
android:canRequestFingerprintGestures 设置为“true”表示允许获取手势信息
android:canControlMagnification 设置为“true”表示允许获取缩放信息
android:notificationTimeout 同一种事件类型触发的最短时间间隔(毫秒)
第三步 在AndroidManifest.xml里注册服务
注意在android:resource 属性里添加了上面的配置xml
<application>
<service
android:name=".ScreenSaverAccessibilityService"
android:enabled="true"
android:exported="true"
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>
</application>
第四步 启动服务
如果你不是系统级别应用,你需要手动去设置-无障碍中启动服务,如下图
如果你是系统级别应用,这可以使用下面的工具类,实现自动开启无障碍服务:
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import java.util.List;
public class AccessibilityUtil {
/**
* 关闭无障碍服务
* @param context
*/
public static void autoCloseAccessibilityService(Context context){
if (isStartAccessibilityServiceEnable(context)) {
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
ComponentName selfComponentName = new ComponentName(context.getPackageName(), ScreenSaverAccessibilityService.class.getCanonicalName());
String flattenToString = selfComponentName.flattenToString();
enabledServicesSetting=enabledServicesSetting.replace(":"+flattenToString , "");
Settings.Secure.putString(context.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,enabledServicesSetting);
//Settings.Secure.putInt(context.getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED, 0);
Log.d("zh", "autoCloseAccessibilityService: SETTING ACCESSIBILITY SUCCESS!");
}
return;
}
/**
* 开启无障碍服务
* @param context
*/
public static void autoOpenAccessibilityService(Context context){
if (!isStartAccessibilityServiceEnable(context)) {
String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
ComponentName selfComponentName = new ComponentName(context.getPackageName(), ScreenSaverAccessibilityService.class.getCanonicalName());
String flattenToString = selfComponentName.flattenToString();
if (enabledServicesSetting==null||
!enabledServicesSetting.contains(flattenToString)) {
enabledServicesSetting += ":"+flattenToString;
}
Settings.Secure.putString(context.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,enabledServicesSetting);
Settings.Secure.putInt(context.getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED, 1);
Log.d("zh", "autoOpenAccessibilityService: SETTING ACCESSIBILITY SUCCESS!");
}
return;
}
/**
* 判断无障碍服务是否开启
*
* @param context
* @return
*/
public static boolean isStartAccessibilityServiceEnable(Context context) {
AccessibilityManager accessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
assert accessibilityManager != null;
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().contains(context.getPackageName())) {
return true;
}
}
return false;
}
}
第五步 如果无障碍服务无法连接或者创建
这可能是google的一些设计,可能是不允许debug安装或者内置系统应用,直接开启无障碍。 你需要重启一下设备就能恢复正常。
模拟操作
首先在配置xml里一定要添加,否则会出现调用getRootInActiveWindow()始终返回为null的问题
android:canRetrieveWindowContent="true"
单击操作
这里举例一个单击功能,其他操作其实都是一样可以举一反三的。如果你写过uiautomator2自动化简直是信手拈来。
以文本内容查找View
/**
* 根据文本查找点击设置
*/
fun byTextClickSettings(){
val nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText("设置")
//点击
if (nodeInfoList.isNotEmpty()){
nodeInfoList[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
以Id查询View
首先需要知道View的id,路径如下,点击monitor.bat:
代码:
/**
* 根据id查找点击设置
*/
fun byIdClickSettings(){
val nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.xxx.xxx:id/settings")
//点击
if (nodeInfoList.isNotEmpty()){
nodeInfoList[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
焦点操作
选中焦点
public static final int ACTION_FOCUS = 0x00000001;
清除焦点
public static final int ACTION_CLEAR_FOCUS = 0x00000002;
选中操作
选中
public static final int ACTION_SELECT = 0x00000004;
清除选中
public static final int ACTION_CLEAR_SELECTION = 0x00000008;
多选
public static final int ACTION_SET_SELECTION = 0x00020000;
Bundle arguments = new Bundle();
arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
滚动操作
/**
* 操作向前滚动节点内容。
*/
public static final int ACTION_SCROLL_FORWARD = 0x00001000;
/**
* 操作向后滚动节点内容。
*/
public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
展开/收起操作
/**
* 展开可展开节点的操作。
*/
public static final int ACTION_EXPAND = 0x00040000;
/**
* 折叠可展开节点的操作。
*/
public static final int ACTION_COLLAPSE = 0x00080000;
撤销操作
/**
* 撤销可撤销节点的操作。
*/
public static final int ACTION_DISMISS = 0x00100000;
进度条操作
/**
* Argument for specifying the progress value to set.
* <p>
* <strong>Type:</strong> float<br>
* <strong>Actions:</strong>
* <ul>
* <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS}</li>
* </ul>
*
* @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS
*/
public static final String ACTION_ARGUMENT_PROGRESS_VALUE =
"android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
移动View操作
/**
* Argument for specifying the x coordinate to which to move a window.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong>
* <ul>
* <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}</li>
* </ul>
*
* @see AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW
*/
public static final String ACTION_ARGUMENT_MOVE_WINDOW_X =
"ACTION_ARGUMENT_MOVE_WINDOW_X";
/**
* Argument for specifying the y coordinate to which to move a window.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong>
* <ul>
* <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}</li>
* </ul>
*
* @see AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW
*/
public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y =
"ACTION_ARGUMENT_MOVE_WINDOW_Y";
复制黏贴操作
/**
* 操作将当前选定内容复制到剪贴板。
*/
public static final int ACTION_COPY = 0x00004000;
/**
* 操作粘贴当前剪贴板内容。
*/
public static final int ACTION_PASTE = 0x00008000;
/**
* 操作以剪切当前选定内容并将其放置到剪贴板。
*/
public static final int ACTION_CUT = 0x00010000;
文本操作
添加文本
/**
* 添加文本内容,如果传入是是null这可以视为清空文本。并且光标会跳转到末尾
* <p>
* <strong>Arguments:</strong>
* {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
* <strong>Example:</strong>
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
* "android");
* info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
* </code></pre></p>
*/
public static final int ACTION_SET_TEXT = 0x00200000;
向前选中文本位置
/**
请求以给定的移动粒度转到此节点文本中的前一个实体的操作。例如,移动到下一个字符、单词等。
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
* {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
* <strong>Example:</strong> Move to the next character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
* arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
* false);
* info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
* arguments);
* </code></pre></p>
* </p>
*
* @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
* @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
*
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
* @see #MOVEMENT_GRANULARITY_CHARACTER
* @see #MOVEMENT_GRANULARITY_WORD
* @see #MOVEMENT_GRANULARITY_LINE
* @see #MOVEMENT_GRANULARITY_PARAGRAPH
* @see #MOVEMENT_GRANULARITY_PAGE
*/
public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
向后选中文本位置
/**
* 请求以给定的移动粒度转到此节点文本中的下一个实体的操作。例如,移动到下一个字符、单词等。
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
* {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
* <strong>Example:</strong> Move to the previous character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
* arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
* false);
* info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
* </code></pre></p>
* </p>
*
* @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
* @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
*
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
* @see #MOVEMENT_GRANULARITY_CHARACTER
* @see #MOVEMENT_GRANULARITY_WORD
* @see #MOVEMENT_GRANULARITY_LINE
* @see #MOVEMENT_GRANULARITY_PARAGRAPH
* @see #MOVEMENT_GRANULARITY_PAGE
*/
public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
HTML操作
移动HTML元素
/**
* 动作移动到给定类型的下一个HTML元素。例如,移动到按钮,输入,表等。
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br>
* <strong>Example:</strong>
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON");
* info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments);
* </code></pre></p>
* </p>
*/
public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17031369.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
来源:https://www.cnblogs.com/guanxinjing/p/17031369.html |