/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.example.android.apis.accessibility; import com.example.android.apis.R; import android.accessibilityservice.AccessibilityService; import android.text.TextUtils; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityRecord; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; import java.util.Locale; /** * This class demonstrates how an accessibility service can query * window content to improve the feedback given to the user. */ public class TaskBackService extends AccessibilityService implements OnInitListener { /** Tag for logging. */ private static final String LOG_TAG = "TaskBackService/onAccessibilityEvent"; /** Comma separator. */ private static final String SEPARATOR = ", "; /** The class name of TaskListView - for simplicity we speak only its items. */ private static final String TASK_LIST_VIEW_CLASS_NAME = "com.example.android.apis.accessibility.TaskListView"; /** Flag whether Text-To-Speech is initialized. */ private boolean mTextToSpeechInitialized; /** Handle to the Text-To-Speech engine. */ private TextToSpeech mTts; /** * {@inheritDoc} */ @Override public void onServiceConnected() { // Initializes the Text-To-Speech engine as soon as the service is connected. mTts = new TextToSpeech(getApplicationContext(), this); } /** * Processes an AccessibilityEvent, by traversing the View's tree and * putting together a message to speak to the user. */ @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (!mTextToSpeechInitialized) { Log.e(LOG_TAG, "Text-To-Speech engine not ready. Bailing out."); return; } // This AccessibilityNodeInfo represents the view that fired the // AccessibilityEvent. The following code will use it to traverse the // view hierarchy, using this node as a starting point. // // NOTE: Every method that returns an AccessibilityNodeInfo may return null, // because the explored window is in another process and the // corresponding View might be gone by the time your request reaches the // view hierarchy. 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.task_complete); } else { completeStr = getString(R.string.task_not_complete); } String taskStr = getString(R.string.task_complete_template, taskLabel, completeStr); StringBuilder utterance = new StringBuilder(taskStr); // The custom ListView added extra context to the event by adding an // AccessibilityRecord to it. Extract that from the event and read it. final int records = event.getRecordCount(); for (int i = 0; i < records; i++) { AccessibilityRecord record = event.getRecord(i); CharSequence contentDescription = record.getContentDescription(); if (!TextUtils.isEmpty(contentDescription )) { utterance.append(SEPARATOR); utterance.append(contentDescription); } } // Announce the utterance. mTts.speak(utterance.toString(), TextToSpeech.QUEUE_FLUSH, null); Log.d(LOG_TAG, utterance.toString()); } private AccessibilityNodeInfo getListItemNodeInfo(AccessibilityNodeInfo source) { AccessibilityNodeInfo current = source; while (true) { AccessibilityNodeInfo parent = current.getParent(); if (parent == null) { return null; } if (TASK_LIST_VIEW_CLASS_NAME.equals(parent.getClassName())) { return current; } // NOTE: Recycle the infos. AccessibilityNodeInfo oldCurrent = current; current = parent; oldCurrent.recycle(); } } /** * {@inheritDoc} */ @Override public void onInterrupt() { /* do nothing */ } /** * {@inheritDoc} */ @Override public void onInit(int status) { // Set a flag so that the TaskBackService knows that the Text-To-Speech // engine has been initialized, and can now handle speaking requests. if (status == TextToSpeech.SUCCESS) { mTts.setLanguage(Locale.US); mTextToSpeechInitialized = true; } } /** * {@inheritDoc} */ @Override public void onDestroy() { super.onDestroy(); if (mTextToSpeechInitialized) { mTts.shutdown(); } } }