Accessibility Overview

Accessible design allows users of all abilities to navigate, understand, and use your UI successfully.Android Accessibility的目的在于让所有的用户都能更方便的使用Android设备,不仅为残障人士提供了便利,更是方便了all users,比如你在开车,在做饭的时候。
说个题外话,看Google I/O 2017或2018开发者大会视频,讲解Accessibility in Android的Product Manager(Patrick Clary)and Technical Program Manager(Victor Tsaran)是两位残障人士,由衷的敬佩。

Impact of Accessibility

  • Increase your app’s reach.
  • Improve your app’s versatility.

全世界约有15%的残障人士,如果能使你的APP more Accessibility,那么会有更多的人使用;另外Accessibility不仅仅方便了残障人士,也能方便所有人,比如你在开车的时候也能用语音代替手势或点击行为。也有可以做一些外挂,比如微信抢红包什么的。

Build accessibility services

在分析内部的AccessibilityService之前,我们先来看看构建accessibility service基础知识,先有个宏观上的认识,再去分析内部实现。

Manifest declarations and permissions

Accessibility service declaration

Accessibility Service也是一个服务,需要在AndroidManifest.xml中声明,注意这里必须有BIND_ACCESSIBILITY_SERVICE permission,让Android System知道可以绑定。

<application>    <service android:name=".MyAccessibilityService"        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"        android:label="@string/accessibility_service_label">      <intent-filter>        <action android:name="android.accessibilityservice.AccessibilityService" />      intent-filter>    service>  application>

Accessibility service configuration

Accessibility Service还需要配置信息,指定服务处理时的accessibility events类型和关于服务的附加信息。配置信息可以通过AccessibilityServiceInfo类的setServiceInfo()方法配置。Android4.0后,可以在Manifest中包含 < meta-data> 元素。

For example:

AndroidManifest.xml

<service android:name=".MyAccessibilityService">  ...  <meta-data    android:name="android.accessibilityservice"    android:resource="@xml/accessibility_service_config" />service>

< project_dir>/res/xml/accessibility_service_config.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"    android:description="@string/accessibility_service_description"    android:packageNames="com.example.android.apis"    android:accessibilityEventTypes="typeAllMask"    android:accessibilityFlags="flagDefault"    android:accessibilityFeedbackType="feedbackSpoken"    android:notificationTimeout="100"    android:canRetrieveWindowContent="true"    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"/>

属性的解释

Accessibility service methods

自己写的Accessibility Service必须从AccessibilityService继承,然后再重写里面的方法,这些方法都是被Android System调用。

  • start service:onServiceConnected()
  • running service:onAccessibilityEvent(), onInterrupt()
  • close service:onUnbind()

Register for accessibility events

Accessibility service configuration一个很重要的功能是告诉指定Accessibility Service可以处理哪些Accessibility Event,这些event可以靠两种方式判别:

  • Package Names
  • Event Types

Android Framework可能会把AccessibilityEvent分发给多个Accessibility Service,前提是这些Accessibility Service有不同的Feedback Types。如果多个Accessibility Service的Feedback Types相同,只有第一个注册的Accessibility Service会接收到这个Accessibility Event。

Accessibility volume

Android 8.0 (API level 26) 及以上,包含了STREAM_ACCESSIBILITYvolume category,可以独立于设备上其他的声音,只控制accessibility service的声音输出。通过调用AudioManager实例的 adjustStreamVolume()方法调节accessibility service的音量。

For example:

import static android.media.AudioManager.*;public class MyAccessibilityService extends AccessibilityService {    private AudioManager mAudioManager =            (AudioManager) getSystemService(AUDIO_SERVICE);    @Override    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {        AccessibilityNodeInfo interactedNodeInfo =                accessibilityEvent.getSource();        if (interactedNodeInfo.getText().equals("Increase volume")) {            mAudioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY,                ADJUST_RAISE, 0);        }    }}

Accessibility shortcut

用户可以通过长按两个音量键来启用和禁用他们喜欢的Accessibility Service。

Accessibility button

导航栏的右侧包括一个Accessibility button。当用户按下这个按钮时,可以根据屏幕上显示的内容调用几个启用的Accessibility Service。Accessibility Service需要add FLAG_REQUEST_ACCESSIBILITY_BUTTONflag(android:accessibilityFlags),然后调用registerAccessibilityButtonCallback()

For example:

private AccessibilityButtonController mAccessibilityButtonController;private AccessibilityButtonController        .AccessibilityButtonCallback mAccessibilityButtonCallback;private boolean mIsAccessibilityButtonAvailable;@Overrideprotected void onServiceConnected() {    mAccessibilityButtonController = getAccessibilityButtonController();    mIsAccessibilityButtonAvailable =            mAccessibilityButtonController.isAccessibilityButtonAvailable();    if (!mIsAccessibilityButtonAvailable) {        return;    }    AccessibilityServiceInfo serviceInfo = getServiceInfo();    serviceInfo.flags            |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;    setServiceInfo(serviceInfo);    mAccessibilityButtonCallback =        new AccessibilityButtonController.AccessibilityButtonCallback() {            @Override            public void onClicked(AccessibilityButtonController controller) {                Log.d("MY_APP_TAG", "Accessibility button pressed!");                // Add custom logic for a service to react to the                // accessibility button being pressed.            }            @Override            public void onAvailabilityChanged(              AccessibilityButtonController controller, boolean available) {                if (controller.equals(mAccessibilityButtonController)) {                    mIsAccessibilityButtonAvailable = available;                }            }        };    if (mAccessibilityButtonCallback != null) {        mAccessibilityButtonController.registerAccessibilityButtonCallback(                mAccessibilityButtonCallback, null);    }}

Fingerprint gestures

Accessibility Service可以响应另一种输入机制,即在设备的指纹传感器上进行定向滑动(向上、向下、左、右)。配置一个Service来接收这些交互的回调,需要完成以下步骤:

  1. 声明USE_FINGERPRINTpermission 和 CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTUREScapability.
  2. 设置 FLAG_REQUEST_FINGERPRINT_GESTURESflag(android:accessibilityFlags).
  3. 注册函数registerFingerprintGestureCallback().

For example:
AndroidManifest.xml

<manifest ... >    <uses-permission android:name="android.permission.USE_FINGERPRINT" />    ...    <application>        <service android:name="com.example.MyFingerprintGestureService" ... >            <meta-data                android:name="android.accessibilityservice"                android:resource="@xml/myfingerprintgestureservice" />        service>    application>manifest>

myfingerprintgestureservice.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"    ...    android:accessibilityFlags=" ... |flagRequestFingerprintGestures"    android:canRequestFingerprintGestures="true"    ... />

MyFingerprintGestureService.java

import static android.accessibilityservice.FingerprintGestureController.*;public class MyFingerprintGestureService extends AccessibilityService {    private FingerprintGestureController mGestureController;    private FingerprintGestureController            .FingerprintGestureCallback mFingerprintGestureCallback;    private boolean mIsGestureDetectionAvailable;    @Override    public void onCreate() {        mGestureController = getFingerprintGestureController();        mIsGestureDetectionAvailable =                mGestureController.isGestureDetectionAvailable();    }    @Override    protected void onServiceConnected() {        if (mFingerprintGestureCallback != null                || !mIsGestureDetectionAvailable) {            return;        }        mFingerprintGestureCallback =               new FingerprintGestureController.FingerprintGestureCallback() {            @Override            public void onGestureDetected(int gesture) {                switch (gesture) {                    case FINGERPRINT_GESTURE_SWIPE_DOWN:                        moveGameCursorDown();                        break;                    case FINGERPRINT_GESTURE_SWIPE_LEFT:                        moveGameCursorLeft();                        break;                    case FINGERPRINT_GESTURE_SWIPE_RIGHT:                        moveGameCursorRight();                        break;                    case FINGERPRINT_GESTURE_SWIPE_UP:                        moveGameCursorUp();                        break;                    default:                        Log.e(MY_APP_TAG,                                  "Error: Unknown gesture type detected!");                        break;                }            }            @Override            public void onGestureDetectionAvailabilityChanged(boolean available) {                mIsGestureDetectionAvailable = available;            }        };        if (mFingerprintGestureCallback != null) {            mGestureController.registerFingerprintGestureCallback(                    mFingerprintGestureCallback, null);        }    }}

Multilingual text to speech

text-to-speech (TTS) service可以在一个文本块识别和使用多种语言,要启用这种功能,需要将LocaleSpan对象中的所有字符串封装起来

For example:

TextView localeWrappedTextView = findViewById(R.id.my_french_greeting_text);localeWrappedTextView.setText(wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE));private SpannableStringBuilder wrapTextInLocaleSpan(        CharSequence originalText, Locale loc) {    SpannableStringBuilder myLocaleBuilder =            new SpannableStringBuilder(originalText);    myLocaleBuilder.setSpan(new LocaleSpan(loc), 0,            originalText.length() - 1, 0);    return myLocaleBuilder;}

Take action for users

从Android 4.0起,Accessibility Service可以代替用户做出Action,比如改变焦点(焦点就是当前正在处理事件的位置,比如有多个text输入框,同一时间内只能有一个输入框可以输入),模拟点击,模拟手势等等。

Listen for gestures

Accessibility Service可以监听特定的手势,然后代替用户做出反应。需要设置flags FLAG_REQUEST_TOUCH_EXPLORATION_MODE

public class MyAccessibilityService extends AccessibilityService {    @Override    public void onCreate() {        getServiceInfo().flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;    }    ...}

Continued gestures

可以通过Path表示手势的路径,然后用GestureDescription.StrokeDescription构造手势。

For example:

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.private void doRightThenDownDrag() {    Path dragRightPath = new Path();    dragRightPath.moveTo(200, 200);    dragRightPath.lineTo(400, 200);    long dragRightDuration = 500L; // 0.5 second    // The starting point of the second path must match    // the ending point of the first path.    Path dragDownPath = new Path();    dragDownPath.moveTo(400, 200);    dragDownPath.lineTo(400, 400);    long dragDownDuration = 500L;    GestureDescription.StrokeDescription rightThenDownDrag =            new GestureDescription.StrokeDescription(dragRightPath, 0L,            dragRightDuration, true);    rightThenDownDrag.continueStroke(dragDownPath, dragRightDuration,            dragDownDuration, false);}

Use accessibility actions

可以通过getSource()得到node,然后调用performAction做出Action。

public class MyAccessibilityService extends AccessibilityService {    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {        // get the source node of the event        AccessibilityNodeInfo nodeInfo = event.getSource();        // Use the event and node information to determine        // what action to take        // take action on behalf of the user        nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);        // recycle the nodeInfo object        nodeInfo.recycle();    }    ...}

Use focus types

可以用AccessibilityNodeInfo.findFocus()查找node有哪些元素具有Input Focus or Accessibility Focus,还可以使用focusSearch()选择Input Focus。最后使用performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS)设置Accessibility Focus。

Gather information

Accessibility services also have standard methods of gathering and representing key units of user-provided information, such as event details, text, and numbers.

Get event details

  • AccessibilityEvent.getRecordCount()getRecord(int)
  • AccessibilityEvent.getSource()- 返回一个AccessibilityNodeInfo对象

Process text

Hint text

  • isShowingHintText()and setShowingHintText()
  • getHintText()

Locations of on-screen text characters

  • refreshWithExtraData()

Standardized one-sided range values

一些AccessibilityNodeInfo对象用AccessibilityNodeInfo.RangeInfo的实例表示UI元素的范围值。

  • For ranges with no minimum, Float.NEGATIVE_INFINITYrepresents the minimum value.
  • For ranges with no maximum, Float.POSITIVE_INFINITYrepresents the maximum value.

Build an accessibility service

这一部分会介绍构建一个accessibility service的flow,从应用程序接收到的信息,然后该信息反馈给用户。

Create your accessibility service

create class

package com.example.android.apis.accessibility;import android.accessibilityservice.AccessibilityService;import android.view.accessibility.AccessibilityEvent;public class MyAccessibilityService extends AccessibilityService {...    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {    }    @Override    public void onInterrupt() {    }...}

AndroidManifest.xml

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

Configure your accessibility service

告诉Android System,你想怎么运行,何时运行,你想对何种AccessibilityEvent做出回应,Service是否要监听所有Application,还是特定的Application,使用哪种feedback types。
有两种方法,一种是setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo),然后重写onServiceConnected()
For example:

@Overridepublic void onServiceConnected() {    // Set the type of events that this service wants to listen to.  Others    // won't be passed to this service.    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |            AccessibilityEvent.TYPE_VIEW_FOCUSED;    // If you only want this service to work with specific applications, set their    // package names here.  Otherwise, when the service is activated, it will listen    // to events from all applications.    info.packageNames = new String[]            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};    // Set the type of feedback your service will provide.    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;    // Default services are invoked only if no package-specific ones are present    // for the type of AccessibilityEvent generated.  This service *is*    // application-specific, so the flag isn't necessary.  If this was a    // general-purpose service, it would be worth considering setting the    // DEFAULT flag.    // info.flags = AccessibilityServiceInfo.DEFAULT;    info.notificationTimeout = 100;    this.setServiceInfo(info);}

另一种方法是使用xml方法

For example:

<accessibility-service     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"     android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"     android:accessibilityFeedbackType="feedbackSpoken"     android:notificationTimeout="100"     android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"     android:canRetrieveWindowContent="true"/>

同时在AndroidManifest.xml中添加< meta-data>,假设XML file 在res/xml/serviceconfig.xml

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

Respond to accessibility events

当监听有AccessibilityEvent时,使用onAccessibilityEvent(AccessibilityEvent)方法做出回应。用getEventType()获取AccessibilityEvent Type,getContentDescription()提取与触发事件的视图有关的标签文本。
For example:

@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {    final int eventType = event.getEventType();    String eventText = null;    switch(eventType) {        case AccessibilityEvent.TYPE_VIEW_CLICKED:            eventText = "Clicked: ";            break;        case AccessibilityEvent.TYPE_VIEW_FOCUSED:            eventText = "Focused: ";            break;    }    eventText = eventText + event.getContentDescription();    // Do something nifty with this text, like speak the composed string    // back to the user.    speakToUser(eventText);    ...}

Query the view hierarchy for more context

有时候,需要得到视图的相关信息,可以查看视图的层次关系,为了做到这一点,需要现在xml中配置。

android:canRetrieveWindowContent="true"

使用getSource()获得AccessibilityNodeInfo对象,当接收到一个AccessibilityEvent时,它会做以下事情:

1.抓住该事件视图的父节点。
2.在该视图(父节点)寻找label and check box作为子视图。
3.如果它找到了它们,就创建一个字符串来向用户报告,指示标签,以及是否检查了它。
4.如果在遍历视图层级时返回null(不管什么时候),那么该方法就会放弃。

For example:

// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {    AccessibilityNodeInfo source = event.getSource();    if (source == null) {        return;    }    // Grab the parent of the view that fired the event.    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);    if (rowNode == null) {        return;    }    // Using this parent, get references to both child nodes, the label and the checkbox.    AccessibilityNodeInfo labelNode = rowNode.getChild(0);    if (labelNode == null) {        rowNode.recycle();        return;    }    AccessibilityNodeInfo completeNode = rowNode.getChild(1);    if (completeNode == null) {        rowNode.recycle();        return;    }    // Determine what the task is and whether or not it's complete, based on    // the text inside the label, and the state of the check-box.    if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {        rowNode.recycle();        return;    }    CharSequence taskLabel = labelNode.getText();    final boolean isComplete = completeNode.isChecked();    String completeStr = null;    if (isComplete) {        completeStr = getString(R.string.checked);    } else {        completeStr = getString(R.string.not_checked);    }    String reportStr = taskLabel + completeStr;    speakToUser(reportStr);}

以上基本是Android官网的学习资料,下一篇会学习AccessibilityService的源码,看看内部是怎么实现的。
请看Android 9.0源码学习-AccessibilityManager

更多相关文章

  1. Android中后台显示悬浮窗口的方法
  2. Android(安卓)四大组件之 Service(二)
  3. Android(安卓)四大组件之一 :BroadCastReceiver 广播接收器详解
  4. 【学习Android(安卓)NDK开发】native code通过JNI调用Java方法
  5. Android适配器之-----SimpleAdapter
  6. Java和Javascript互调的例子 ---------(Android(安卓)WebView 中)
  7. Android(安卓)AsyncTask
  8. android 中添加一个服务
  9. Android(安卓)之WebView

随机推荐

  1. Cocos2d-x3.1下 Android,APK自动升级
  2. 前端h5与 android/ios 交互传参
  3. android 关机 流程分析
  4. 修改eclipse android 默认debug 签名
  5. 10个android开源项目
  6. 【原创】Android锁定横竖屏、splash,全屏
  7. [笔记]2012年移动大趋势(上)
  8. Android(安卓)-- 图像处理(信息量超大)
  9. Android(安卓)Camera 使用小结
  10. 十大Android(安卓)IDE工具和应用