287 lines
10 KiB
Plaintext
287 lines
10 KiB
Plaintext
|
|
page.title=Developing an Accessibility Service
|
|
parent.title=Implementing Accessibility
|
|
parent.link=index.html
|
|
|
|
trainingnavtop=true
|
|
previous.title=Developing Accessible Applications
|
|
previous.link=accessible-app.html
|
|
|
|
@jd:body
|
|
|
|
<div id="tb-wrapper">
|
|
<div id="tb">
|
|
|
|
<h2>This lesson teaches you to</h2>
|
|
<ol>
|
|
<li><a href="#create">Create Your Accessibility Service</a></li>
|
|
<li><a href="#configure">Configure Your Accessibility Service</a></li>
|
|
<li><a href="#events">Respond to AccessibilityEvents</a></li>
|
|
<li><a href="#query">Query the View Heirarchy for More Context</a></li>
|
|
</ol>
|
|
|
|
<h2>You should also read</h2>
|
|
<ul>
|
|
<li><a href="{@docRoot}guide/topics/ui/accessibility/services.html">Building
|
|
Accessibility Services</a></li>
|
|
</ul>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<p>Accessibility services are a feature of the Android framework designed to
|
|
provide alternative navigation feedback to the user on behalf of applications
|
|
installed on Android devices. An accessibility service can communicate to the
|
|
user on the application's behalf, such as converting text to speech, or haptic
|
|
feedback when a user is hovering on an important area of the screen. This
|
|
lesson covers how to create an accessibility service, process information
|
|
received from the application, and report that information back to the
|
|
user.</p>
|
|
|
|
|
|
<h2 id="create">Create Your Accessibility Service</h2>
|
|
<p>An accessibility service can be bundled with a normal application, or created
|
|
as a standalone Android project. The steps to creating the service are the same
|
|
in either situation. Within your project, create a class that extends {@link
|
|
android.accessibilityservice.AccessibilityService}.</p>
|
|
|
|
<pre>
|
|
package com.example.android.apis.accessibility;
|
|
|
|
import android.accessibilityservice.AccessibilityService;
|
|
|
|
public class MyAccessibilityService extends AccessibilityService {
|
|
...
|
|
@Override
|
|
public void onAccessibilityEvent(AccessibilityEvent event) {
|
|
}
|
|
|
|
@Override
|
|
public void onInterrupt() {
|
|
}
|
|
|
|
...
|
|
}
|
|
</pre>
|
|
|
|
<p>Like any other service, you also declare it in the manifest file.
|
|
Remember to specify that it handles the {@code android.accessibilityservice} intent,
|
|
so that the service is called when applications fire an
|
|
{@link android.view.accessibility.AccessibilityEvent}.</p>
|
|
|
|
<pre>
|
|
<application ...>
|
|
...
|
|
<service android:name=".MyAccessibilityService">
|
|
<intent-filter>
|
|
<action android:name="android.accessibilityservice.AccessibilityService" />
|
|
</intent-filter>
|
|
. . .
|
|
</service>
|
|
...
|
|
</application>
|
|
</pre>
|
|
|
|
<p>If you created a new project for this service, and don't plan on having an
|
|
application, you can remove the starter Activity class (usually called MainActivity.java) from your source. Remember to
|
|
also remove the corresponding activity element from your manifest.</p>
|
|
|
|
<h2 id="configure">Configure Your Accessibility Service</h2>
|
|
<p>Setting the configuration variables for your accessibility service tells the
|
|
system how and when you want it to run. Which event types would you like to
|
|
respond to? Should the service be active for all applications, or only specific
|
|
package names? What different feedback types does it use?</p>
|
|
|
|
<p>You have two options for how to set these variables. The
|
|
backwards-compatible option is to set them in code, using {@link
|
|
android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}.
|
|
To do that, override the {@link
|
|
android.accessibilityservice.AccessibilityService#onServiceConnected()} method
|
|
and configure your service in there.</p>
|
|
|
|
<pre>
|
|
@Override
|
|
public 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);
|
|
|
|
}
|
|
</pre>
|
|
|
|
<p>Starting with Android 4.0, there is a second option available: configure the
|
|
service using an XML file. Certain configuration options like
|
|
{@link android.R.attr#canRetrieveWindowContent} are only available if you
|
|
configure your service using XML. The same configuration options above, defined
|
|
using XML, would look like this:</p>
|
|
|
|
<pre>
|
|
<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"
|
|
/>
|
|
</pre>
|
|
|
|
<p>If you go the XML route, be sure to reference it in your manifest, by adding
|
|
a <a
|
|
href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a> tag to
|
|
your service declaration, pointing at the XML file. If you stored your XML file
|
|
in {@code res/xml/serviceconfig.xml}, the new tag would look like this:</p>
|
|
|
|
<pre>
|
|
<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>
|
|
</pre>
|
|
|
|
<h2 id="events">Respond to AccessibilityEvents</h2>
|
|
<p>Now that your service is set up to run and listen for events, write some code
|
|
so it knows what to do when an {@link
|
|
android.view.accessibility.AccessibilityEvent} actually arrives! Start by
|
|
overriding the {@link
|
|
android.accessibilityservice.AccessibilityService#onAccessibilityEvent} method.
|
|
In that method, use {@link
|
|
android.view.accessibility.AccessibilityEvent#getEventType} to determine the
|
|
type of event, and {@link
|
|
android.view.accessibility.AccessibilityEvent#getContentDescription} to extract
|
|
any label text associated with the view that fired the event.</pre>
|
|
|
|
<pre>
|
|
@Override
|
|
public void onAccessibilityEvent(AccessibilityEvent event) {
|
|
final int eventType = event.getEventType();
|
|
String eventText = null;
|
|
switch(eventType) {
|
|
case AccessibilityEvent.TYPE_VIEW_CLICKED:
|
|
eventText = "Focused: ";
|
|
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);
|
|
...
|
|
}
|
|
</pre>
|
|
|
|
<h2 id="query">Query the View Heirarchy for More Context</h2>
|
|
<p>This step is optional, but highly useful. One of the new features in Android
|
|
4.0 (API Level 14) is the ability for an
|
|
{@link android.accessibilityservice.AccessibilityService} to query the view
|
|
hierarchy, collecting information about the UI component that generated an event, and
|
|
its parent and children. In order to do this, make sure that you set the
|
|
following line in your XML configuration:</p>
|
|
<pre>
|
|
android:canRetrieveWindowContent="true"
|
|
</pre>
|
|
<p>Once that's done, get an {@link
|
|
android.view.accessibility.AccessibilityNodeInfo} object using {@link
|
|
android.view.accessibility.AccessibilityEvent#getSource}. This call only
|
|
returns an object if the window where the event originated is still the active
|
|
window. If not, it will return null, so <em>behave accordingly</em>. The
|
|
following example is a snippet of code that, when it receives an event, does
|
|
the following:
|
|
<ol>
|
|
<li>Immediately grab the parent of the view where the event originated</li>
|
|
<li>In that view, look for a label and a check box as children views</li>
|
|
<li>If it finds them, create a string to report to the user, indicating
|
|
the label and whether it was checked or not.</li>
|
|
<li>If at any point a null value is returned while traversing the view
|
|
hierarchy, the method quietly gives up.</li>
|
|
</ol>
|
|
|
|
<pre>
|
|
|
|
// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo
|
|
|
|
@Override
|
|
public 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);
|
|
}
|
|
|
|
</pre>
|
|
|
|
<p>Now you have a complete, functioning accessibility service. Try configuring
|
|
how it interacts with the user, by adding Android's <a
|
|
href="http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html">text-to-speech
|
|
engine</a>, or using a {@link android.os.Vibrator} to provide haptic
|
|
feedback!</p>
|