1 2 page.title=Developing an Accessibility Service 3 parent.title=Implementing Accessibility 4 parent.link=index.html 5 6 trainingnavtop=true 7 previous.title=Developing Accessible Applications 8 previous.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 34 provide alternative navigation feedback to the user on behalf of applications 35 installed on Android devices. An accessibility service can communicate to the 36 user on the application's behalf, such as converting text to speech, or haptic 37 feedback when a user is hovering on an important area of the screen. This 38 lesson covers how to create an accessibility service, process information 39 received from the application, and report that information back to the 40 user.</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 45 as a standalone Android project. The steps to creating the service are the same 46 in either situation. Within your project, create a class that extends {@link 47 android.accessibilityservice.AccessibilityService}.</p> 48 49 <pre> 50 package com.example.android.apis.accessibility; 51 52 import android.accessibilityservice.AccessibilityService; 53 54 public 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. 69 Remember to specify that it handles the {@code android.accessibilityservice} intent, 70 so 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 87 application, you can remove the starter Activity class (usually called MainActivity.java) from your source. Remember to 88 also 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 92 system how and when you want it to run. Which event types would you like to 93 respond to? Should the service be active for all applications, or only specific 94 package names? What different feedback types does it use?</p> 95 96 <p>You have two options for how to set these variables. The 97 backwards-compatible option is to set them in code, using {@link 98 android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. 99 To do that, override the {@link 100 android.accessibilityservice.AccessibilityService#onServiceConnected()} method 101 and configure your service in there.</p> 102 103 <pre> 104 @Override 105 public 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 136 service using an XML file. Certain configuration options like 137 {@link android.R.attr#canRetrieveWindowContent} are only available if you 138 configure your service using XML. The same configuration options above, defined 139 using 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 153 a <a 154 href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a> tag to 155 your service declaration, pointing at the XML file. If you stored your XML file 156 in {@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 170 so it knows what to do when an {@link 171 android.view.accessibility.AccessibilityEvent} actually arrives! Start by 172 overriding the {@link 173 android.accessibilityservice.AccessibilityService#onAccessibilityEvent} method. 174 In that method, use {@link 175 android.view.accessibility.AccessibilityEvent#getEventType} to determine the 176 type of event, and {@link 177 android.view.accessibility.AccessibilityRecord#getContentDescription} to extract 178 any label text associated with the view that fired the event.</pre> 179 180 <pre> 181 @Override 182 public 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. The Android platform provides the ability for an 205 {@link android.accessibilityservice.AccessibilityService} to query the view 206 hierarchy, collecting information about the UI component that generated an event, and 207 its parent and children. In order to do this, make sure that you set the 208 following line in your XML configuration:</p> 209 <pre> 210 android:canRetrieveWindowContent="true" 211 </pre> 212 <p>Once that's done, get an {@link 213 android.view.accessibility.AccessibilityNodeInfo} object using {@link 214 android.view.accessibility.AccessibilityRecord#getSource}. This call only 215 returns an object if the window where the event originated is still the active 216 window. If not, it will return null, so <em>behave accordingly</em>. The 217 following example is a snippet of code that, when it receives an event, does 218 the 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 233 public 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 282 how 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 285 feedback!</p> 286