• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2page.title=Developing an Accessibility Service
3parent.title=Implementing Accessibility
4parent.link=index.html
5
6trainingnavtop=true
7previous.title=Developing Accessible Applications
8previous.link=accessible-app.html
9
10@jd:body
11
12<div id="tb-wrapper">
13<div id="tb">
14
15<h2>This lesson teaches you to</h2>
16<ol>
17  <li><a href="#create">Create Your Accessibility Service</a></li>
18  <li><a href="#configure">Configure Your Accessibility Service</a></li>
19  <li><a href="#events">Respond to AccessibilityEvents</a></li>
20  <li><a href="#query">Query the View Hierarchy for More Context</a></li>
21</ol>
22
23<h2>You should also read</h2>
24<ul>
25  <li><a href="{@docRoot}guide/topics/ui/accessibility/services.html">Building
26  Accessibility Services</a></li>
27</ul>
28
29</div>
30</div>
31
32
33<p>Accessibility services are a feature of the Android framework designed to
34provide alternative navigation feedback to the user on behalf of applications
35installed on Android devices.  An accessibility service can communicate to the
36user on the application's behalf, such as converting text to speech, or haptic
37feedback when a user is hovering on an important area of the screen.  This
38lesson covers how to create an accessibility service, process information
39received from the application, and report that information back to the
40user.</p>
41
42
43<h2 id="create">Create Your Accessibility Service</h2>
44<p>An accessibility service can be bundled with a normal application, or created
45as a standalone Android project.  The steps to creating the service are the same
46in either situation.  Within your project, create a class that extends {@link
47android.accessibilityservice.AccessibilityService}.</p>
48
49<pre>
50package com.example.android.apis.accessibility;
51
52import android.accessibilityservice.AccessibilityService;
53
54public class MyAccessibilityService extends AccessibilityService {
55...
56    &#64;Override
57    public void onAccessibilityEvent(AccessibilityEvent event) {
58    }
59
60    &#64;Override
61    public void onInterrupt() {
62    }
63
64...
65}
66</pre>
67
68<p>Like any other service, you also declare it in the manifest file.
69Remember to specify that it handles the {@code android.accessibilityservice} intent,
70so that the service is called when applications fire an
71{@link android.view.accessibility.AccessibilityEvent}.</p>
72
73<pre>
74&lt;application ...&gt;
75...
76&lt;service android:name=".MyAccessibilityService"&gt;
77     &lt;intent-filter&gt;
78         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
79     &lt;/intent-filter&gt;
80     . . .
81&lt;/service&gt;
82...
83&lt;/application&gt;
84</pre>
85
86<p>If you created a new project for this service, and don't plan on having an
87application, you can remove the starter Activity class (usually called MainActivity.java) from your source.  Remember to
88also remove the corresponding activity element from your manifest.</p>
89
90<h2 id="configure">Configure Your Accessibility Service</h2>
91<p>Setting the configuration variables for your accessibility service tells the
92system how and when you want it to run.  Which event types would you like to
93respond to?  Should the service be active for all applications, or only specific
94package names?  What different feedback types does it use?</p>
95
96<p>You have two options for how to set these variables.  The
97backwards-compatible option is to set them in code, using {@link
98android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}.
99To do that, override the {@link
100android.accessibilityservice.AccessibilityService#onServiceConnected()} method
101and configure your service in there.</p>
102
103<pre>
104&#64;Override
105public void onServiceConnected() {
106    // Set the type of events that this service wants to listen to.  Others
107    // won't be passed to this service.
108    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
109            AccessibilityEvent.TYPE_VIEW_FOCUSED;
110
111    // If you only want this service to work with specific applications, set their
112    // package names here.  Otherwise, when the service is activated, it will listen
113    // to events from all applications.
114    info.packageNames = new String[]
115            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};
116
117    // Set the type of feedback your service will provide.
118    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
119
120    // Default services are invoked only if no package-specific ones are present
121    // for the type of AccessibilityEvent generated.  This service *is*
122    // application-specific, so the flag isn't necessary.  If this was a
123    // general-purpose service, it would be worth considering setting the
124    // DEFAULT flag.
125
126    // info.flags = AccessibilityServiceInfo.DEFAULT;
127
128    info.notificationTimeout = 100;
129
130    this.setServiceInfo(info);
131
132}
133</pre>
134
135<p>The second option is to configure the
136service using an XML file.  Certain configuration options like
137{@link android.R.attr#canRetrieveWindowContent} are only available if you
138configure your service using XML.  The same configuration options above, defined
139using XML, would look like this:</p>
140
141<pre>
142&lt;accessibility-service
143     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
144     android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"
145     android:accessibilityFeedbackType="feedbackSpoken"
146     android:notificationTimeout="100"
147     android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"
148     android:canRetrieveWindowContent="true"
149/&gt;
150</pre>
151
152<p>If you go the XML route, be sure to reference it in your manifest, by adding
153a <a
154href="{@docRoot}guide/topics/manifest/meta-data-element.html">&lt;meta-data&gt;</a> tag to
155your service declaration, pointing at the XML file.  If you stored your XML file
156in {@code res/xml/serviceconfig.xml}, the new tag would look like this:</p>
157
158<pre>
159&lt;service android:name=".MyAccessibilityService"&gt;
160     &lt;intent-filter&gt;
161         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
162     &lt;/intent-filter&gt;
163     &lt;meta-data android:name="android.accessibilityservice"
164     android:resource="@xml/serviceconfig" /&gt;
165&lt;/service&gt;
166</pre>
167
168<h2 id="events">Respond to AccessibilityEvents</h2>
169<p>Now that your service is set up to run and listen for events, write some code
170so it knows what to do when an {@link
171android.view.accessibility.AccessibilityEvent} actually arrives!  Start by
172overriding the {@link
173android.accessibilityservice.AccessibilityService#onAccessibilityEvent} method.
174In that method, use {@link
175android.view.accessibility.AccessibilityEvent#getEventType} to determine the
176type of event, and {@link
177android.view.accessibility.AccessibilityEvent#getContentDescription} to extract
178any label text associated with the view that fired the event.</pre>
179
180<pre>
181&#64;Override
182public void onAccessibilityEvent(AccessibilityEvent event) {
183    final int eventType = event.getEventType();
184    String eventText = null;
185    switch(eventType) {
186        case AccessibilityEvent.TYPE_VIEW_CLICKED:
187            eventText = "Focused: ";
188            break;
189        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
190            eventText = "Focused: ";
191            break;
192    }
193
194    eventText = eventText + event.getContentDescription();
195
196    // Do something nifty with this text, like speak the composed string
197    // back to the user.
198    speakToUser(eventText);
199    ...
200}
201</pre>
202
203<h2 id="query">Query the View Hierarchy for More Context</h2>
204<p>This step is optional, but highly useful. The Android platform provides the ability for an
205{@link android.accessibilityservice.AccessibilityService} to query the view
206hierarchy, collecting information about the UI component that generated an event, and
207its parent and children.  In order to do this, make sure that you set the
208following line in your XML configuration:</p>
209<pre>
210android:canRetrieveWindowContent="true"
211</pre>
212<p>Once that's done, get an {@link
213android.view.accessibility.AccessibilityNodeInfo} object using {@link
214android.view.accessibility.AccessibilityEvent#getSource}.  This call only
215returns an object if the window where the event originated is still the active
216window.  If not, it will return null, so <em>behave accordingly</em>.  The
217following example is a snippet of code that, when it receives an event, does
218the following:
219<ol>
220  <li>Immediately grab the parent of the view where the event originated</li>
221  <li>In that view, look for a label and a check box as children views</li>
222  <li>If it finds them, create a string to report to the user, indicating
223  the label and whether it was checked or not.</li>
224  <li>If at any point a null value is returned while traversing the view
225  hierarchy, the method quietly gives up.</li>
226</ol>
227
228<pre>
229
230// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo
231
232&#64;Override
233public void onAccessibilityEvent(AccessibilityEvent event) {
234
235    AccessibilityNodeInfo source = event.getSource();
236    if (source == null) {
237        return;
238    }
239
240    // Grab the parent of the view that fired the event.
241    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
242    if (rowNode == null) {
243        return;
244    }
245
246    // Using this parent, get references to both child nodes, the label and the checkbox.
247    AccessibilityNodeInfo labelNode = rowNode.getChild(0);
248    if (labelNode == null) {
249        rowNode.recycle();
250        return;
251    }
252
253    AccessibilityNodeInfo completeNode = rowNode.getChild(1);
254    if (completeNode == null) {
255        rowNode.recycle();
256        return;
257    }
258
259    // Determine what the task is and whether or not it's complete, based on
260    // the text inside the label, and the state of the check-box.
261    if (rowNode.getChildCount() &lt; 2 || !rowNode.getChild(1).isCheckable()) {
262        rowNode.recycle();
263        return;
264    }
265
266    CharSequence taskLabel = labelNode.getText();
267    final boolean isComplete = completeNode.isChecked();
268    String completeStr = null;
269
270    if (isComplete) {
271        completeStr = getString(R.string.checked);
272    } else {
273        completeStr = getString(R.string.not_checked);
274    }
275    String reportStr = taskLabel + completeStr;
276    speakToUser(reportStr);
277}
278
279</pre>
280
281<p>Now you have a complete, functioning accessibility service.  Try configuring
282how it interacts with the user, by adding Android's <a
283  href="http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html">text-to-speech
284  engine</a>, or using a {@link android.os.Vibrator} to provide haptic
285feedback!</p>
286