Alexander Lucas | df6c827 | 2012-03-06 18:13:07 -0800 | [diff] [blame] | 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 |
Scott Main | cb0b2df | 2012-08-09 11:49:50 -0700 | [diff] [blame] | 178 | any label text associated with the view that fired the event.</pre> |
Alexander Lucas | df6c827 | 2012-03-06 18:13:07 -0800 | [diff] [blame] | 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 |
kmccormick | 76dfc02 | 2013-04-03 12:41:12 -0700 | [diff] [blame] | 207 | hierarchy, collecting information about the UI component that generated an event, and |
Alexander Lucas | df6c827 | 2012-03-06 18:13:07 -0800 | [diff] [blame] | 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 |
Scott Main | f284d49 | 2012-07-31 09:46:52 -0700 | [diff] [blame] | 284 | href="http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html">text-to-speech |
Alexander Lucas | df6c827 | 2012-03-06 18:13:07 -0800 | [diff] [blame] | 285 | engine</a>, or using a {@link android.os.Vibrator} to provide haptic |
| 286 | feedback!</p> |