service.jd revision f284d49293aead609de5b83d601260cfd86b7978
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 Heirarchy 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>Starting with Android 4.0, there is a second option available: 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 fiew 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 Heirarchy for More Context</h2>
204<p>This step is optional, but highly useful.  One of the new features in Android
2054.0 (API Level 14) is the ability for an
206{@link android.accessibilityservice.AccessibilityService} to query the view
207hierarchy, collecting information about the the UI component that generated an event, and
208its parent and children.  In order to do this, make sure that you set the
209following line in your XML configuration:</p>
210<pre>
211android:canRetrieveWindowContent="true"
212</pre>
213<p>Once that's done, get an {@link
214android.view.accessibility.AccessibilityNodeInfo} object using {@link
215android.view.accessibility.AccessibilityEvent#getSource}.  This call only
216returns an object if the window where the event originated is still the active
217window.  If not, it will return null, so <em>behave accordingly</em>.  The
218following example is a snippet of code that, when it receives an event, does
219the following:
220<ol>
221  <li>Immediately grab the parent of the view where the event originated</li>
222  <li>In that view, look for a label and a check box as children views</li>
223  <li>If it finds them, create a string to report to the user, indicating
224  the label and whether it was checked or not.</li>
225  <li>If at any point a null value is returned while traversing the view
226  hierarchy, the method quietly gives up.</li>
227</ol>
228
229<pre>
230
231// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo
232
233&#64;Override
234public void onAccessibilityEvent(AccessibilityEvent event) {
235
236    AccessibilityNodeInfo source = event.getSource();
237    if (source == null) {
238        return;
239    }
240
241    // Grab the parent of the view that fired the event.
242    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
243    if (rowNode == null) {
244        return;
245    }
246
247    // Using this parent, get references to both child nodes, the label and the checkbox.
248    AccessibilityNodeInfo labelNode = rowNode.getChild(0);
249    if (labelNode == null) {
250        rowNode.recycle();
251        return;
252    }
253
254    AccessibilityNodeInfo completeNode = rowNode.getChild(1);
255    if (completeNode == null) {
256        rowNode.recycle();
257        return;
258    }
259
260    // Determine what the task is and whether or not it's complete, based on
261    // the text inside the label, and the state of the check-box.
262    if (rowNode.getChildCount() &lt; 2 || !rowNode.getChild(1).isCheckable()) {
263        rowNode.recycle();
264        return;
265    }
266
267    CharSequence taskLabel = labelNode.getText();
268    final boolean isComplete = completeNode.isChecked();
269    String completeStr = null;
270
271    if (isComplete) {
272        completeStr = getString(R.string.checked);
273    } else {
274        completeStr = getString(R.string.not_checked);
275    }
276    String reportStr = taskLabel + completeStr;
277    speakToUser(reportStr);
278}
279
280</pre>
281
282<p>Now you have a complete, functioning accessibility service.  Try configuring
283how it interacts with the user, by adding Android's <a
284  href="http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html">text-to-speech
285  engine</a>, or using a {@link android.os.Vibrator} to provide haptic
286feedback!</p>
287