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>Starting with Android 4.0, there is a second option available: 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.AccessibilityEvent#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. One of the new features in Android 205 4.0 (API Level 14) is the ability for an 206 {@link android.accessibilityservice.AccessibilityService} to query the view 207 hierarchy, collecting information about the UI component that generated an event, and 208 its parent and children. In order to do this, make sure that you set the 209 following line in your XML configuration:</p> 210 <pre> 211 android:canRetrieveWindowContent="true" 212 </pre> 213 <p>Once that's done, get an {@link 214 android.view.accessibility.AccessibilityNodeInfo} object using {@link 215 android.view.accessibility.AccessibilityEvent#getSource}. This call only 216 returns an object if the window where the event originated is still the active 217 window. If not, it will return null, so <em>behave accordingly</em>. The 218 following example is a snippet of code that, when it receives an event, does 219 the 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 @Override 234 public 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() < 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 283 how 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 286 feedback!</p> 287