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