1# AccessibilityExtensionAbility
2
3Accessibility Kit(无障碍开发服务)通过基于ExtensionAbility框架的AccessibilityExtensionAbility提供无障碍扩展服务,开发者可以基于AccessibilityExtensionAbility模板开发自己的扩展服务,协助用户完成一些快捷的交互过程。
4
5## 如何创建一个无障碍扩展服务
6
7开发者在创建一个无障碍扩展服务时,如工程满足环境要求,开发者可自主选择是否跳过创建工程步骤,在已有工程中新增无障碍扩展服务。一个工程仅支持创建一个无障碍扩展服务。
8本指南以实现以下功能为案例,讲述如何创建无障碍扩展服务,如何调用API接口实现该功能:
9启动辅助功能后,在设备屏幕上绘画“右划后再下划”(rightThenDown)的手势,获取当前界面的全部节点;之后再绘画“左划后再下划”(leftThenDown)的手势,打印所有节点。
10
11### 创建工程
12
13如需新增独立的无障碍扩展服务应用,在DevEco Studio中新建一个API 9以上的Stage工程。
14
15### 新建无障碍扩展服务ets文件
16
17在已创建工程的ets文件夹下创建AccessibilityExtAbility文件夹,在该文件夹下创建AccessibilityExtAbility.ets文件,可在该文件中实现一些回调函数,并加入业务处理逻辑的调用:
18
19```ts
20import { AccessibilityExtensionAbility, AccessibilityEvent } from '@kit.AccessibilityKit';
21import AccessibilityManager from './AccessibilityManager';
22
23class AccessibilityExtAbility extends AccessibilityExtensionAbility {
24    onConnect() {
25        console.info(`AccessibilityExtAbility onConnect`);
26        // 执行初始化业务逻辑的操作
27        AccessibilityManager.getInstance().onStart(this.context);
28    }
29
30    onDisconnect() {
31        console.info(`AccessibilityExtAbility onDisconnect`);
32        // 执行资源回收退出业务逻辑的操作
33        AccessibilityManager.getInstance().onStop();
34    }
35
36    onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
37        console.info(`AccessibilityExtAbility onAccessibilityEvent: ${JSON.stringify(accessibilityEvent)}`);
38        // 根据事件信息进行业务逻辑处理
39        AccessibilityManager.getInstance().onEvent(accessibilityEvent);
40    }
41}
42
43export default AccessibilityExtAbility;
44```
45
46其中,主要定义了以下接口:
47
48| 接口 | 描述 |
49| ---- | ---- |
50| onConnect(): void | 当扩展服务连接时回调。 |
51| onDisconnect(): void | 当扩展服务断开时回调。 |
52| onAccessibilityEvent(event: AccessibilityEvent): void | 当无障碍事件发生时回调。 |
53
54创建AccessibilityManager.ets文件,用于存放业务逻辑代码:
55```ts
56import {
57  AccessibilityElement,
58  AccessibilityEvent,
59  AccessibilityExtensionContext,
60  ElementAttributeKeys
61} from '@kit.AccessibilityKit';
62
63interface Rect {
64  left: number,
65  top: number,
66  width: number,
67  height: number,
68}
69
70// 想要查询的属性信息
71let wantedAttribute: ElementAttributeKeys[] = ['bundleName', 'text', 'description', 'windowId'];
72type attributeValues = string | number | boolean | AccessibilityElement | AccessibilityElement[] | string[] | Rect;
73
74export default class AccessibilityManager {
75  private static instance: AccessibilityManager;
76  accessibleContext?: AccessibilityExtensionContext;
77  currentPageElementArray: Array<AccessibilityElement> | null = null;
78
79  static getInstance(): AccessibilityManager {
80    if (!AccessibilityManager.instance) {
81      AccessibilityManager.instance = new AccessibilityManager();
82    }
83    return AccessibilityManager.instance;
84  }
85
86  onStart(context: AccessibilityExtensionContext) {
87    console.info(`AccessibilityManager onStart`);
88    this.accessibleContext = context;
89  }
90
91  onStop() {
92    console.info(`AccessibilityManager onStop`);
93    this.accessibleContext = undefined;
94  }
95
96  onEvent(accessibilityEvent: AccessibilityEvent): void {
97    console.info(`AccessibilityManager onEvent`);
98    switch (accessibilityEvent.eventType) {
99      case 'rightThenDown':
100      // 获取当前页面的所有节点
101        this.getCurrentPageAllElement();
102        break;
103      case 'leftThenDown':
104      // 打印所有节点
105        this.printAllElementInfo();
106        break;
107      default:
108        break;
109    }
110  }
111
112  async getCurrentPageAllElement(): Promise<void> {
113    console.info(`AccessibilityManager getCurrentPageAllElement`);
114    let rootElement: AccessibilityElement;
115    if(!this.accessibleContext){
116      console.error(`AccessibilityManager accessibleContext undefined`);
117      return;
118    }
119    try {
120      rootElement = await this.accessibleContext?.getWindowRootElement();
121      this.currentPageElementArray = await this.getAttributeValue(rootElement, 'children') as AccessibilityElement[];
122    } catch (error) {
123      console.error(`AccessibilityExtAbility Failed to getWindowRootElement. Cause:${JSON.stringify(error)}`);
124    }
125  }
126
127  async getElementWantedInfo(accessibilityElement: AccessibilityElement, wantedAttribute: ElementAttributeKeys[]):
128    Promise<string | null> {
129    console.info(`AccessibilityUtils getElementAllInfo`);
130    if (accessibilityElement === null) {
131      console.error(`AccessibilityUtils accessibilityElement is null`);
132      return null;
133    }
134
135    let info = '';
136    let value: attributeValues | null;
137    for (let name of wantedAttribute) {
138      value = await this.getAttributeValue(accessibilityElement, name);
139      info = info.concat(name + ': ' + value + ' ');
140    }
141    return info;
142  }
143
144  async getAttributeValue(accessibilityElement: AccessibilityElement, key: ElementAttributeKeys):
145    Promise<attributeValues | null> {
146    console.info(`AccessibilityUtils getAttributeValue`);
147    let value: attributeValues;
148    let keys = await accessibilityElement.attributeNames();
149    let isExit = false;
150    for (let keyString of keys) {
151      if (key == keyString) {
152        isExit = true;
153      }
154    }
155    if (isExit) {
156      try {
157        value = await accessibilityElement.attributeValue(key);
158        return value;
159      } catch (error) {
160        console.error(`AccessibilityUtils Failed to get attributeValue of ${key} . Cause:  ${JSON.stringify(error)}`);
161      }
162    }
163    return null;
164  }
165
166  async printAllElementInfo(): Promise<void> {
167    console.info(`AccessibilityManager printAllElementInfo`);
168    if (this.currentPageElementArray === null || this.currentPageElementArray.length === 0) {
169      console.error(`AccessibilityManager currentPageElementArray is null`);
170      return;
171    }
172    let info: string | null = null;
173    for (let index = 0; index < this.currentPageElementArray.length; index++) {
174      info = await this.getElementWantedInfo(this.currentPageElementArray[index], wantedAttribute);
175      console.info(`AccessibilityManager element information: ${info}`);
176    }
177  }
178}
179```
180
181## 如何处理一个无障碍事件
182
183相关无障碍事件可以在`onAccessibilityEvent()`方法中进行业务逻辑处理,具体事件可参考[AccessibilityEvent](../reference/apis-accessibility-kit/js-apis-application-accessibilityExtensionAbility.md#accessibilityevent)。此处以手势事件`rightThenDown`为例:
184
185```ts
186onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
187    console.info('AccessibilityExtAbility onAccessibilityEvent: ' + JSON.stringify(accessibilityEvent));
188    if (accessibilityEvent.eventType === 'rightThenDown') {
189        console.info('AccessibilityExtAbility onAccessibilityEvent: rightThenDown');
190        // TODO: 自定义相关逻辑开发
191    }
192}
193```
194在相应的无障碍事件中,可以使用[辅助功能扩展上下文(AccessibilityExtensionContext)](../reference/apis-accessibility-kit/js-apis-inner-application-accessibilityExtensionContext.md)提供的接口进行扩展开发,包括允许配置辅助应用关注信息类型、查询节点信息、手势注入等。
195
196此外,还可在无障碍扩展服务中对物理按键事件进行处理,具体可参考[onKeyEvent](../reference/apis-accessibility-kit/js-apis-application-accessibilityExtensionAbility.md#accessibilityextensionabilityonkeyevent)。
197
198## 如何声明无障碍扩展服务具备的能力
199
200在完成自定义无障碍扩展服务的逻辑开发后,还需要在工程中Module对应的module.json5文件中加入新增扩展服务的配置信息。
201
202`srcEntry`标签为`extensionAbility`对应的路径。
203`label`标签为`extensionAbility`在已安装扩展服务列表中显示的名称。
204`description`标签为`extensionAbility`在已安装扩展服务详情页的帮助信息。
205`type`标签要按照与无障碍子系统的约定进行配置,需要注意的是该值固定为`accessibility`,否则将无法正常连接。
206
207```json
208"extensionAbilities": [
209  {
210    "name": "AccessibilityExtAbility",
211    "srcEntry": "./ets/AccessibilityExtAbility/AccessibilityExtAbility.ets",
212    "label": "$string:MainAbility_label",
213    "description": "$string:MainAbility_desc",
214    "type": "accessibility",
215    "metadata": [
216      {
217        "name": "ohos.accessibleability",
218        "resource": "$profile:accessibility_config"
219      }
220    ]
221  }
222]
223```
224另外,配置信息中的`accessibility_config`为无障碍扩展服务的具体配置,需要在`resources/base/profile/`下新建`accessibility_config.json`文件,在该文件中声明此无障碍扩展服务具备的[能力类型](../reference/apis-accessibility-kit/js-apis-accessibility.md#capability),根据业务功能合理声明能力类型,本案例中,需要如下声明:
225```json
226{
227  "accessibilityCapabilities": [
228    "retrieve",
229    "gesture",
230    "touchGuide"
231  ]
232}
233```
234## 如何开启自定义的无障碍扩展服务
235
236当前提供设备-设置中的扩展服务管理页的开关按钮来开启/关闭选择的无障碍扩展服务:
237
2381、打开设备设置页面,进入“辅助功能”,“扩展服务”小标题下的“已安装的服务”显示当前安装的扩展服务个数,点击进入,展示安装的扩展服务列表;未安装扩展服务时,“已安装的扩展服务”不可点击,并显示“无服务”。
239
2402、选择需要开启/关闭的扩展服务,通过开关按钮进行扩展服务的开启/关闭。
241
2423、开启时,弹出安全提醒,在倒计时结束后,勾选“我已知晓如上风险,并自愿承担可能导致的后果。”后,可选择“开启”/“不开启”按钮;  关闭时,将开启的开关关闭,即可关闭已开启的扩展服务。
243
244## 相关实例
245
246针对AccessibilityExtensionAbility开发,有以下相关实例可供参考:
247
248- [AccessibilityExtensionAbility示例](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/ApplicationModels/AccessibilityExtAbility)
249