Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.example.android.apis.accessibility;
     18 
     19 import com.example.android.apis.R;
     20 
     21 import android.accessibilityservice.AccessibilityService;
     22 import android.text.TextUtils;
     23 import android.util.Log;
     24 import android.view.accessibility.AccessibilityEvent;
     25 import android.view.accessibility.AccessibilityNodeInfo;
     26 import android.view.accessibility.AccessibilityRecord;
     27 import android.speech.tts.TextToSpeech;
     28 import android.speech.tts.TextToSpeech.OnInitListener;
     29 
     30 import java.util.Locale;
     31 
     32 /**
     33  * This class demonstrates how an accessibility service can query
     34  * window content to improve the feedback given to the user.
     35  */
     36 public class TaskBackService extends AccessibilityService implements OnInitListener {
     37 
     38     /** Tag for logging. */
     39     private static final String LOG_TAG = "TaskBackService/onAccessibilityEvent";
     40 
     41     /** Comma separator. */
     42     private static final String SEPARATOR = ", ";
     43 
     44     /** The class name of TaskListView - for simplicity we speak only its items. */
     45     private static final String TASK_LIST_VIEW_CLASS_NAME =
     46         "com.example.android.apis.accessibility.TaskListView";
     47 
     48     /** Flag whether Text-To-Speech is initialized. */
     49     private boolean mTextToSpeechInitialized;
     50 
     51     /** Handle to the Text-To-Speech engine. */
     52     private TextToSpeech mTts;
     53 
     54     /**
     55      * {@inheritDoc}
     56      */
     57     @Override
     58     public void onServiceConnected() {
     59         // Initializes the Text-To-Speech engine as soon as the service is connected.
     60         mTts = new TextToSpeech(getApplicationContext(), this);
     61     }
     62 
     63     /**
     64      * Processes an AccessibilityEvent, by traversing the View's tree and
     65      * putting together a message to speak to the user.
     66      */
     67     @Override
     68     public void onAccessibilityEvent(AccessibilityEvent event) {
     69         if (!mTextToSpeechInitialized) {
     70             Log.e(LOG_TAG, "Text-To-Speech engine not ready.  Bailing out.");
     71             return;
     72         }
     73 
     74         // This AccessibilityNodeInfo represents the view that fired the
     75         // AccessibilityEvent. The following code will use it to traverse the
     76         // view hierarchy, using this node as a starting point.
     77         //
     78         // NOTE: Every method that returns an AccessibilityNodeInfo may return null,
     79         // because the explored window is in another process and the
     80         // corresponding View might be gone by the time your request reaches the
     81         // view hierarchy.
     82         AccessibilityNodeInfo source = event.getSource();
     83         if (source == null) {
     84             return;
     85         }
     86 
     87         // Grab the parent of the view that fired the event.
     88         AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
     89         if (rowNode == null) {
     90             return;
     91         }
     92 
     93         // Using this parent, get references to both child nodes, the label and the checkbox.
     94         AccessibilityNodeInfo labelNode = rowNode.getChild(0);
     95         if (labelNode == null) {
     96             rowNode.recycle();
     97             return;
     98         }
     99 
    100         AccessibilityNodeInfo completeNode = rowNode.getChild(1);
    101         if (completeNode == null) {
    102             rowNode.recycle();
    103             return;
    104         }
    105 
    106         // Determine what the task is and whether or not it's complete, based on
    107         // the text inside the label, and the state of the check-box.
    108         if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
    109             rowNode.recycle();
    110             return;
    111         }
    112 
    113         CharSequence taskLabel = labelNode.getText();
    114         final boolean isComplete = completeNode.isChecked();
    115 
    116         String completeStr = null;
    117         if (isComplete) {
    118             completeStr = getString(R.string.task_complete);
    119         } else {
    120             completeStr = getString(R.string.task_not_complete);
    121         }
    122 
    123         String taskStr = getString(R.string.task_complete_template, taskLabel, completeStr);
    124         StringBuilder utterance = new StringBuilder(taskStr);
    125 
    126         // The custom ListView added extra context to the event by adding an
    127         // AccessibilityRecord to it. Extract that from the event and read it.
    128         final int records = event.getRecordCount();
    129         for (int i = 0; i < records; i++) {
    130             AccessibilityRecord record = event.getRecord(i);
    131             CharSequence contentDescription = record.getContentDescription();
    132             if (!TextUtils.isEmpty(contentDescription )) {
    133                 utterance.append(SEPARATOR);
    134                 utterance.append(contentDescription);
    135             }
    136         }
    137 
    138         // Announce the utterance.
    139         mTts.speak(utterance.toString(), TextToSpeech.QUEUE_FLUSH, null);
    140         Log.d(LOG_TAG, utterance.toString());
    141     }
    142 
    143     private AccessibilityNodeInfo getListItemNodeInfo(AccessibilityNodeInfo source) {
    144         AccessibilityNodeInfo current = source;
    145         while (true) {
    146             AccessibilityNodeInfo parent = current.getParent();
    147             if (parent == null) {
    148                 return null;
    149             }
    150             if (TASK_LIST_VIEW_CLASS_NAME.equals(parent.getClassName())) {
    151                 return current;
    152             }
    153             // NOTE: Recycle the infos.
    154             AccessibilityNodeInfo oldCurrent = current;
    155             current = parent;
    156             oldCurrent.recycle();
    157         }
    158     }
    159 
    160     /**
    161      * {@inheritDoc}
    162      */
    163     @Override
    164     public void onInterrupt() {
    165         /* do nothing */
    166     }
    167 
    168     /**
    169      * {@inheritDoc}
    170      */
    171     @Override
    172     public void onInit(int status) {
    173         // Set a flag so that the TaskBackService knows that the Text-To-Speech
    174         // engine has been initialized, and can now handle speaking requests.
    175         if (status == TextToSpeech.SUCCESS) {
    176             mTts.setLanguage(Locale.US);
    177             mTextToSpeechInitialized = true;
    178         }
    179     }
    180 
    181     /**
    182      * {@inheritDoc}
    183      */
    184     @Override
    185     public void onDestroy() {
    186         super.onDestroy();
    187         if (mTextToSpeechInitialized) {
    188             mTts.shutdown();
    189         }
    190     }
    191 }
    192