| |
| page.title=Developing an Accessibility Service |
| parent.title=Implementing Accessibility |
| parent.link=index.html |
| |
| trainingnavtop=true |
| previous.title=Developing Accessible Applications |
| previous.link=accessible-app.html |
| |
| @jd:body |
| |
| <div id="tb-wrapper"> |
| <div id="tb"> |
| |
| <h2>This lesson teaches you to</h2> |
| <ol> |
| <li><a href="#create">Create Your Accessibility Service</a></li> |
| <li><a href="#configure">Configure Your Accessibility Service</a></li> |
| <li><a href="#events">Respond to AccessibilityEvents</a></li> |
| <li><a href="#query">Query the View Heirarchy for More Context</a></li> |
| </ol> |
| |
| <h2>You should also read</h2> |
| <ul> |
| <li><a href="{@docRoot}guide/topics/ui/accessibility/services.html">Building |
| Accessibility Services</a></li> |
| </ul> |
| |
| </div> |
| </div> |
| |
| |
| <p>Accessibility services are a feature of the Android framework designed to |
| provide alternative navigation feedback to the user on behalf of applications |
| installed on Android devices. An accessibility service can communicate to the |
| user on the application's behalf, such as converting text to speech, or haptic |
| feedback when a user is hovering on an important area of the screen. This |
| lesson covers how to create an accessibility service, process information |
| received from the application, and report that information back to the |
| user.</p> |
| |
| |
| <h2 id="create">Create Your Accessibility Service</h2> |
| <p>An accessibility service can be bundled with a normal application, or created |
| as a standalone Android project. The steps to creating the service are the same |
| in either situation. Within your project, create a class that extends {@link |
| android.accessibilityservice.AccessibilityService}.</p> |
| |
| <pre> |
| package com.example.android.apis.accessibility; |
| |
| import android.accessibilityservice.AccessibilityService; |
| |
| public class MyAccessibilityService extends AccessibilityService { |
| ... |
| @Override |
| public void onAccessibilityEvent(AccessibilityEvent event) { |
| } |
| |
| @Override |
| public void onInterrupt() { |
| } |
| |
| ... |
| } |
| </pre> |
| |
| <p>Like any other service, you also declare it in the manifest file. |
| Remember to specify that it handles the {@code android.accessibilityservice} intent, |
| so that the service is called when applications fire an |
| {@link android.view.accessibility.AccessibilityEvent}.</p> |
| |
| <pre> |
| <application ...> |
| ... |
| <service android:name=".MyAccessibilityService"> |
| <intent-filter> |
| <action android:name="android.accessibilityservice.AccessibilityService" /> |
| </intent-filter> |
| . . . |
| </service> |
| ... |
| </application> |
| </pre> |
| |
| <p>If you created a new project for this service, and don't plan on having an |
| application, you can remove the starter Activity class (usually called MainActivity.java) from your source. Remember to |
| also remove the corresponding activity element from your manifest.</p> |
| |
| <h2 id="configure">Configure Your Accessibility Service</h2> |
| <p>Setting the configuration variables for your accessibility service tells the |
| system how and when you want it to run. Which event types would you like to |
| respond to? Should the service be active for all applications, or only specific |
| package names? What different feedback types does it use?</p> |
| |
| <p>You have two options for how to set these variables. The |
| backwards-compatible option is to set them in code, using {@link |
| android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. |
| To do that, override the {@link |
| android.accessibilityservice.AccessibilityService#onServiceConnected()} method |
| and configure your service in there.</p> |
| |
| <pre> |
| @Override |
| public void onServiceConnected() { |
| // Set the type of events that this service wants to listen to. Others |
| // won't be passed to this service. |
| info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED | |
| AccessibilityEvent.TYPE_VIEW_FOCUSED; |
| |
| // If you only want this service to work with specific applications, set their |
| // package names here. Otherwise, when the service is activated, it will listen |
| // to events from all applications. |
| info.packageNames = new String[] |
| {"com.example.android.myFirstApp", "com.example.android.mySecondApp"}; |
| |
| // Set the type of feedback your service will provide. |
| info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN; |
| |
| // Default services are invoked only if no package-specific ones are present |
| // for the type of AccessibilityEvent generated. This service *is* |
| // application-specific, so the flag isn't necessary. If this was a |
| // general-purpose service, it would be worth considering setting the |
| // DEFAULT flag. |
| |
| // info.flags = AccessibilityServiceInfo.DEFAULT; |
| |
| info.notificationTimeout = 100; |
| |
| this.setServiceInfo(info); |
| |
| } |
| </pre> |
| |
| <p>Starting with Android 4.0, there is a second option available: configure the |
| service using an XML file. Certain configuration options like |
| {@link android.R.attr#canRetrieveWindowContent} are only available if you |
| configure your service using XML. The same configuration options above, defined |
| using XML, would look like this:</p> |
| |
| <pre> |
| <accessibility-service |
| android:accessibilityEventTypes="typeViewClicked|typeViewFocused" |
| android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp" |
| android:accessibilityFeedbackType="feedbackSpoken" |
| android:notificationTimeout="100" |
| android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity" |
| android:canRetrieveWindowContent="true" |
| /> |
| </pre> |
| |
| <p>If you go the XML route, be sure to reference it in your manifest, by adding |
| a <a |
| href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a> tag to |
| your service declaration, pointing at the XML file. If you stored your XML file |
| in {@code res/xml/serviceconfig.xml}, the new tag would look like this:</p> |
| |
| <pre> |
| <service android:name=".MyAccessibilityService"> |
| <intent-filter> |
| <action android:name="android.accessibilityservice.AccessibilityService" /> |
| </intent-filter> |
| <meta-data android:name="android.accessibilityservice" |
| android:resource="@xml/serviceconfig" /> |
| </service> |
| </pre> |
| |
| <h2 id="events">Respond to AccessibilityEvents</h2> |
| <p>Now that your service is set up to run and listen for events, write some code |
| so it knows what to do when an {@link |
| android.view.accessibility.AccessibilityEvent} actually arrives! Start by |
| overriding the {@link |
| android.accessibilityservice.AccessibilityService#onAccessibilityEvent} method. |
| In that method, use {@link |
| android.view.accessibility.AccessibilityEvent#getEventType} to determine the |
| type of event, and {@link |
| android.view.accessibility.AccessibilityEvent#getContentDescription} to extract |
| any label text associated with the view that fired the event.</pre> |
| |
| <pre> |
| @Override |
| public void onAccessibilityEvent(AccessibilityEvent event) { |
| final int eventType = event.getEventType(); |
| String eventText = null; |
| switch(eventType) { |
| case AccessibilityEvent.TYPE_VIEW_CLICKED: |
| eventText = "Focused: "; |
| break; |
| case AccessibilityEvent.TYPE_VIEW_FOCUSED: |
| eventText = "Focused: "; |
| break; |
| } |
| |
| eventText = eventText + event.getContentDescription(); |
| |
| // Do something nifty with this text, like speak the composed string |
| // back to the user. |
| speakToUser(eventText); |
| ... |
| } |
| </pre> |
| |
| <h2 id="query">Query the View Heirarchy for More Context</h2> |
| <p>This step is optional, but highly useful. One of the new features in Android |
| 4.0 (API Level 14) is the ability for an |
| {@link android.accessibilityservice.AccessibilityService} to query the view |
| hierarchy, collecting information about the UI component that generated an event, and |
| its parent and children. In order to do this, make sure that you set the |
| following line in your XML configuration:</p> |
| <pre> |
| android:canRetrieveWindowContent="true" |
| </pre> |
| <p>Once that's done, get an {@link |
| android.view.accessibility.AccessibilityNodeInfo} object using {@link |
| android.view.accessibility.AccessibilityEvent#getSource}. This call only |
| returns an object if the window where the event originated is still the active |
| window. If not, it will return null, so <em>behave accordingly</em>. The |
| following example is a snippet of code that, when it receives an event, does |
| the following: |
| <ol> |
| <li>Immediately grab the parent of the view where the event originated</li> |
| <li>In that view, look for a label and a check box as children views</li> |
| <li>If it finds them, create a string to report to the user, indicating |
| the label and whether it was checked or not.</li> |
| <li>If at any point a null value is returned while traversing the view |
| hierarchy, the method quietly gives up.</li> |
| </ol> |
| |
| <pre> |
| |
| // Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo |
| |
| @Override |
| public void onAccessibilityEvent(AccessibilityEvent event) { |
| |
| AccessibilityNodeInfo source = event.getSource(); |
| if (source == null) { |
| return; |
| } |
| |
| // Grab the parent of the view that fired the event. |
| AccessibilityNodeInfo rowNode = getListItemNodeInfo(source); |
| if (rowNode == null) { |
| return; |
| } |
| |
| // Using this parent, get references to both child nodes, the label and the checkbox. |
| AccessibilityNodeInfo labelNode = rowNode.getChild(0); |
| if (labelNode == null) { |
| rowNode.recycle(); |
| return; |
| } |
| |
| AccessibilityNodeInfo completeNode = rowNode.getChild(1); |
| if (completeNode == null) { |
| rowNode.recycle(); |
| return; |
| } |
| |
| // Determine what the task is and whether or not it's complete, based on |
| // the text inside the label, and the state of the check-box. |
| if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) { |
| rowNode.recycle(); |
| return; |
| } |
| |
| CharSequence taskLabel = labelNode.getText(); |
| final boolean isComplete = completeNode.isChecked(); |
| String completeStr = null; |
| |
| if (isComplete) { |
| completeStr = getString(R.string.checked); |
| } else { |
| completeStr = getString(R.string.not_checked); |
| } |
| String reportStr = taskLabel + completeStr; |
| speakToUser(reportStr); |
| } |
| |
| </pre> |
| |
| <p>Now you have a complete, functioning accessibility service. Try configuring |
| how it interacts with the user, by adding Android's <a |
| href="http://android-developers.blogspot.com/2009/09/introduction-to-text-to-speech-in.html">text-to-speech |
| engine</a>, or using a {@link android.os.Vibrator} to provide haptic |
| feedback!</p> |