Android 浅析 辅助功能

前言

Linus Benedict Torvalds : RTFSC – Read The Funning Source Code

概述

许多Android用户有不同的需求,需要他们以不同的方式与他们的Android设备进行交互。这些包括具有视觉,身体或年龄相关限制的用户,这些限制阻止他们完全看到或使用触摸屏,以及听力损失的用户可能无法感知到可听信息和警报。 Android提供了辅助功能和服务,可帮助这些用户更轻松地浏览其设备,包括文字转语音,触觉反馈,手势导航,轨迹球和方向键盘导航。 Android应用程序开发人员可以利用这些服务来使他们的应用程序更容易访问。 Android开发人员还可以构建自己的辅助功能服务,可以提供增强的可用性功能,如音频提示,物理反馈和其他导航模式。辅助功能服务可以为所有应用程序,一组应用程序或单个应用程序提供这些增强功能。

使用

Step 1 : AccessibilityService类

自定义一个Service然后继承AccessibilityService类。

1
2
3
4
5
6
7
8
9
10
11
12
13
<service
android:name=".permission.PermissionAccessibilityService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:process=":DefendService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config"/>
</service>

Step 2 : accessibility_service_config.xml

创建一个作为辅助功能开启说明类

1
2
3
4
5
6
7
8
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags=""
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_perm_tutorial_message"
android:notificationTimeout="100"/>

几个属性说明:

  1. android:accessibilityEventTypes=”typeAllMask”
    这个是用来设置响应事件的类型,typeAllMask 是响应所有类型的事件了。还有单击、长按、滑动等。
  2. android:accessibilityFeedbackType=”feedbackSpoken”
    设置回馈给用户的方式,有语音播出和振动。可以配置一些TTS引擎,让它实现发音。
  3. android:notificationTimeout=”100”
    响应时间的设置。
  4. android:packageNames=”com.example.android.apis”
    可以指定响应某个应用的事件,这里因为要响应所有应用的事件,所以不填,默认就是响应所有应用的事件。比如我们写一个微信抢红包的辅助程序,就可以在这里填写微信的包名,便可以监听微信产生的事件了。

Step 3 : onAccessibilityEvent(AccessibilityEvent event)

1
2
3
4
5
6
7
8
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
int eventType = event.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
//.......
}
}

通过在event.getEventType()方法里可以筛选出不同的事件:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@Deprecated
public static final int MAX_TEXT_LENGTH = 500;
/**
* Represents the event of clicking on a {@link android.view.View} like
* {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
*/
public static final int TYPE_VIEW_CLICKED = 0x00000001;
/**
* Represents the event of long clicking on a {@link android.view.View} like
* {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
*/
public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
/**
* Represents the event of selecting an item usually in the context of an
* {@link android.widget.AdapterView}.
*/
public static final int TYPE_VIEW_SELECTED = 0x00000004;
/**
* Represents the event of setting input focus of a {@link android.view.View}.
*/
public static final int TYPE_VIEW_FOCUSED = 0x00000008;
/**
* Represents the event of changing the text of an {@link android.widget.EditText}.
*/
public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
/**
* Represents the event of opening a {@link android.widget.PopupWindow},
* {@link android.view.Menu}, {@link android.app.Dialog}, etc.
*/
public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
/**
* Represents the event showing a {@link android.app.Notification}.
*/
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
/**
* Represents the event of a hover enter over a {@link android.view.View}.
*/
public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;
/**
* Represents the event of a hover exit over a {@link android.view.View}.
*/
public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;
/**
* Represents the event of starting a touch exploration gesture.
*/
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;
/**
* Represents the event of ending a touch exploration gesture.
*/
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
/**
* Represents the event of changing the content of a window and more
* specifically the sub-tree rooted at the event's source.
*/
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
/**
* Represents the event of scrolling a view.
*/
public static final int TYPE_VIEW_SCROLLED = 0x00001000;
/**
* Represents the event of changing the selection in an {@link android.widget.EditText}.
*/
public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;
/**
* Represents the event of an application making an announcement.
*/
public static final int TYPE_ANNOUNCEMENT = 0x00004000;
/**
* Represents the event of gaining accessibility focus.
*/
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
/**
* Represents the event of clearing accessibility focus.
*/
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
/**
* Represents the event of traversing the text of a view at a given movement granularity.
*/
public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
/**
* Represents the event of beginning gesture detection.
*/
public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;
/**
* Represents the event of ending gesture detection.
*/
public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;
/**
* Represents the event of the user starting to touch the screen.
*/
public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;
/**
* Represents the event of the user ending to touch the screen.
*/
public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
* The type of change is not defined.
*/
public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
* A node in the subtree rooted at the source node was added or removed.
*/
public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
* The node's text changed.
*/
public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
* The node's content description changed.
*/
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;

通过这个事件我们可以区分不同的事件做区分了。

Step 4 : 查找元素

这里有两种方法可以查找到相应元素:

  1. 通过节点View的Text内容来查找findAccessibilityNodeInfosByText("查找内容"),这种方式查找,就是像TextView、Button等View有文本内容的,可以使用这种方式快速的找到。
  2. 通过节点View在xml布局中的id名称findAccessibilityNodeInfosByViewId("@id/xxx"),这个一般很难知道,但是在查找系统控件的时候还是可以做的,因为系统的控件的id是可以知道的,而且是统一的。

Step 5 : 点击元素

找到想要的View节点,调用方法模拟事件:

  1. 点击事件:performAction(AccessibilityNodeInfo.ACTION_CLICK)
  2. 滑动时间:performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
  3. 物理键:AccessibilityService.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);

判断是否开启辅助功能

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
public static boolean isAccessibilitySettingsOn(Context context) {
if (context == null) {
return false;
}
int accessibilityEnabled = 0;
try {
accessibilityEnabled = Settings.Secure.getInt(context.getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
String packageName = context.getPackageName();
// 获取注册服务的那个类的全包名
String accessibilityServicePath = ".permission.PermissionAccessibilityService";
String serviceStr = packageName + "/" + accessibilityServicePath;
if (accessibilityEnabled != ACCESSIBILITY_ENABLED) {
return false;
}
String settingValue = Settings.Secure.getString(context.getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue == null) {
return false;
}
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
splitter.setString(settingValue);
while (splitter.hasNext()) {
String accessabilityService = splitter.next();
if (accessabilityService.equalsIgnoreCase(serviceStr)) {
return true;
}
}
return false;
}

附录 跳转到系统界面activity

String 作用
ACTION_ACCESSIBILITY_SETTINGS 辅助功能模块的显示设置。
ACTION_ADD_ACCOUNT 显示屏幕上创建一个新帐户添加帐户。
ACTION_AIRPLANE_MODE_SETTINGS 显示设置,以允许进入/退出飞行模式。
ACTION_APN_SETTINGS 显示设置,以允许配 置的APN。
ACTION_APPLICATION_DETAILS_SETTINGS 有关特定应用程序的详细信息的显示屏幕。
ACTION_APPLICATION_DEVELOPMENT_SETTINGS 显示设置,以允许应用程序开发相关的设置配置
ACTION_APPLICATION_SETTINGS 显示设置,以允许应用程序相关的设置配置
ACTION_BLUETOOTH_SETTINGS 显示设置,以允许蓝牙配置
ACTION_DATA_ROAMING_SETTINGS 选择of2G/3G显示设置
ACTION_DATE_SETTINGS 显示日期和时间设置,以允许配 置
ACTION_DEVICE_INFO_SETTINGS 显示一般的设备信息设置(序列号,软件版本,电话号码,等)
ACTION_DISPLAY_SETTINGS 显示设置,以允许配 置显示
ACTION_INPUT_METHOD_SETTINGS 特别配置的输入方法,允许用户启用输入法的显示设置
ACTION_INPUT_METHOD_SUBTYPE_SETTINGS 显示设置来启用/禁用输入法亚型
ACTION_INTERNAL_STORAGE_SETTINGS 内部存储的显示设置
ACTION_LOCALE_SETTINGS 显示设置,以允许配 置的语言环境
ACTION_LOCATION_SOURCE_SETTINGS 显示设置,以允许当前位置源的配置
ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS 显示设置来管理所有的应用程序
ACTION_MANAGE_APPLICATIONS_SETTINGS 显示设置来管理安装的应用程序
ACTION_MEMORY_CARD_SETTINGS 显示设置为存储卡存储
ACTION_NETWORK_OPERATOR_SETTINGS 选择网络运营商的显示设置
ACTION_PRIVACY_SETTINGS 显示设置,以允许配 置隐私选项
ACTION_QUICK_LAUNCH_SETTINGS 显示设置,以允许快速启动快捷键的配置
ACTION_SEARCH_SETTINGS 全局搜索显示设置
ACTION_SECURITY_SETTINGS 显示设置,以允许配 置的安全性和位置隐私
ACTION_SETTINGS 显示系统设置
ACTION_SOUND_SETTINGS 显示设置,以允许配 置声音和音量
ACTION_SYNC_SETTINGS 显示设置,以允许配 置同步设置
ACTION_USER_DICTIONARY_SETTINGS 显示设置来管理用户输入字典
ACTION_WIFI_IP_SETTINGS 显示设置,以允许配 置一个静态IP地址的Wi – Fi
ACTION_WIFI_SETTINGS 显示设置,以允许Wi – Fi配置
ACTION_WIRELESS_SETTINGS 显示设置,以允许配 置,如Wi – Fi,蓝牙和移动网络的无线控制
AUTHORITY
EXTRA_AUTHORITIES 在推出活动的基础上给予的权力限制可选项。
EXTRA_INPUT_METHOD_ID

彩蛋

  1. 如果遇到action无效的情况,例如上滑无效,可以将里面的所有listview元素都滑动一遍,因为有可能是做了fragment,第一次的滑动滑到了下面的层级去了。