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 @Override 57 public void onAccessibilityEvent(AccessibilityEvent event) { 58 } 59 60 @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<application ...> 75... 76<service android:name=".MyAccessibilityService"> 77 <intent-filter> 78 <action android:name="android.accessibilityservice.AccessibilityService" /> 79 </intent-filter> 80 . . . 81</service> 82... 83</application> 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@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<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/> 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"><meta-data></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<service android:name=".MyAccessibilityService"> 160 <intent-filter> 161 <action android:name="android.accessibilityservice.AccessibilityService" /> 162 </intent-filter> 163 <meta-data android:name="android.accessibilityservice" 164 android:resource="@xml/serviceconfig" /> 165</service> 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@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@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() < 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