From 96c0210eb57e60f03c23a8e504782a8ba4b9edfe Mon Sep 17 00:00:00 2001 From: Alexander Lucas Date: Thu, 27 Oct 2011 15:23:41 -0700 Subject: [PATCH] Adding ICS Accessibility Features to ApiDemos app. Change-Id: I37485821a77fed483308434e7b1d407642379d8c --- samples/ApiDemos/AndroidManifest.xml | 25 ++- samples/ApiDemos/_index.html | 3 + samples/ApiDemos/res/layout/tasklist_main.xml | 28 +++ samples/ApiDemos/res/layout/tasklist_row.xml | 34 ++++ samples/ApiDemos/res/values/strings.xml | 16 +- samples/ApiDemos/res/xml/taskbackconfig.xml | 25 +++ .../apis/accessibility/TaskBackService.java | 160 ++++++++++++++++++ .../apis/accessibility/TaskListActivity.java | 42 +++++ .../apis/accessibility/TaskListView.java | 119 +++++++++++++ .../android/apis/accessibility/_index.html | 9 + 10 files changed, 457 insertions(+), 4 deletions(-) create mode 100644 samples/ApiDemos/res/layout/tasklist_main.xml create mode 100644 samples/ApiDemos/res/layout/tasklist_row.xml create mode 100644 samples/ApiDemos/res/xml/taskbackconfig.xml create mode 100644 samples/ApiDemos/src/com/example/android/apis/accessibility/TaskBackService.java create mode 100644 samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListActivity.java create mode 100644 samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListView.java create mode 100644 samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index fd591079c..064d05887 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -36,7 +36,7 @@ - + @@ -242,7 +242,7 @@ - + + + + + + + + + + + + + + + + @@ -2237,7 +2256,7 @@ - +
  • Stylus and hover support
  • Switch widget
  • +
  • Window + Querying Accessibility Service
  • diff --git a/samples/ApiDemos/res/layout/tasklist_main.xml b/samples/ApiDemos/res/layout/tasklist_main.xml new file mode 100644 index 000000000..61a17e3ef --- /dev/null +++ b/samples/ApiDemos/res/layout/tasklist_main.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/samples/ApiDemos/res/layout/tasklist_row.xml b/samples/ApiDemos/res/layout/tasklist_row.xml new file mode 100644 index 000000000..51bb31eea --- /dev/null +++ b/samples/ApiDemos/res/layout/tasklist_row.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index 5a7183c6d..1dfc1bb48 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -1286,4 +1286,18 @@ Dismiss Share - + + + + + + + Accessibility/Accessibility Node Querying + Task App Accessibility Service + TaskBack + + Task + Task %1$s %2$s + is complete + is not complete + diff --git a/samples/ApiDemos/res/xml/taskbackconfig.xml b/samples/ApiDemos/res/xml/taskbackconfig.xml new file mode 100644 index 000000000..02ff11c70 --- /dev/null +++ b/samples/ApiDemos/res/xml/taskbackconfig.xml @@ -0,0 +1,25 @@ + + + + diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskBackService.java b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskBackService.java new file mode 100644 index 000000000..dbbfe3a1b --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskBackService.java @@ -0,0 +1,160 @@ +/* + * 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.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; + +/** The TaskBackService listens for AccessibilityEvents, and turns them into information it can + * communicate to the user with speech. + */ +public class TaskBackService extends AccessibilityService implements OnInitListener { + + private final String LOG_TAG = "TaskBackService/onAccessibilityEvent"; + private boolean mTextToSpeechInitialized = false; + private TextToSpeech mTts = null; + private static final String SEPARATOR = ", "; + + + + /** Initializes the Text-To-Speech engine as soon as the service is connected. */ + @Override + public void onServiceConnected() { + 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; + } + + int eventType = event.getEventType(); + if (eventType != AccessibilityEvent.TYPE_VIEW_CLICKED) { + 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. + */ + AccessibilityNodeInfo entryNode = event.getSource(); + + /* 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." + */ + if (entryNode == null) { + return; + } + // Grab the parent of the view that fired the event. + AccessibilityNodeInfo rowNode = entryNode.getParent(); + + if (rowNode == null) { + return; + } + + /* Using this parent, get references to both child nodes, + * the label and the checkbox. + */ + AccessibilityNodeInfo labelNode = rowNode.getChild(0); + AccessibilityNodeInfo completeNode = rowNode.getChild(1); + + if (labelNode == null || completeNode == null) { + return; + } + + /* Using these to determine what the task is and whether or not + * it's complete, based on the text inside the label, and the state + * of the checkbox. + */ + + // Quick check to make sure we're not in the ApiDemos nav. + if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) { + return; + } + + CharSequence taskLabel = labelNode.getText(); + 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 forSpeech = 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. + */ + int records = event.getRecordCount(); + + for (int i = 0; i < records; i++) { + AccessibilityRecord record = event.getRecord(i); + CharSequence contentDescription = record.getContentDescription(); + if (contentDescription != null) { + forSpeech.append(SEPARATOR).append(contentDescription); + } + } + + /* Speak the forSpeech string to the user. QUEUE_ADD adds the string to the end of the + * queue, QUEUE_FLUSH would interrupt whatever was currently being said. + */ + mTts.speak(forSpeech.toString() , TextToSpeech.QUEUE_ADD, null); + Log.d(LOG_TAG, forSpeech.toString()); + } + + @Override + public void onInterrupt() { + /* do nothing */ + } + + /** Sets a flag so that the TaskBackService knows that the Text-To-Speech engine has been + * initialized, and can now handle speaking requests. + */ + @Override + public void onInit (int status) { + if (status == TextToSpeech.SUCCESS) { + mTts.setLanguage(Locale.US); + mTextToSpeechInitialized = true; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mTextToSpeechInitialized) { + mTts.shutdown(); + } + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListActivity.java b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListActivity.java new file mode 100644 index 000000000..bbc1403b8 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListActivity.java @@ -0,0 +1,42 @@ +/* + * 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.app.ListActivity; +import android.os.Bundle; +import android.widget.ArrayAdapter; + +/** Starts up the task list that will interact with the AccessibilityService sample. */ +public class TaskListActivity extends ListActivity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.tasklist_main); + + // Hardcoded hand-waving here. + boolean[] checkboxes = {true, true, false, true, false, false, false}; + String[] labels = {"Take out Trash", "Do Laundry", + "Conquer World", "Nap", "Do Taxes", + "Abolish IRS", "Tea with Aunt Sharon" }; + + TaskAdapter myAdapter = new TaskAdapter(this, labels, checkboxes); + this.setListAdapter(myAdapter); + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListView.java b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListView.java new file mode 100644 index 000000000..a4b17cb19 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/TaskListView.java @@ -0,0 +1,119 @@ +/* + * 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.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.TextView; + + +/** Acts as a go-between for all AccessibilityEvents sent from items in the ListView, providing the + * option of sending more context to an AccessibilityService by adding more AccessiblityRecords to + * an event. + */ +public class TaskListView extends ListView { + + public TaskListView(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + /** + * This method will fire whenever a child event wants to send an AccessibilityEvent. As a + * result, it's a great place to add more AccessibilityRecords, if you want. In this case, + * the code is grabbing the position of the item in the list, and assuming that to be the + * priority for the task. + */ + @Override + public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { + // Add a record for ourselves as well. + AccessibilityEvent record = AccessibilityEvent.obtain(); + super.onInitializeAccessibilityEvent(record); + + int priority = (Integer) child.getTag(); + String priorityStr = "Priority: " + priority; + record.setContentDescription(priorityStr); + + event.appendRecord(record); + return true; + } +} + +/** Adds Accessibility information to individual child views of rows in the list. */ +final class TaskAdapter extends BaseAdapter{ + + private String[] mLabels = null; + private boolean[] mCheckboxes = null; + private Context mContext = null; + + public TaskAdapter(Context context, String[] labels, boolean[] checkboxes) { + super(); + mContext = context; + mLabels = labels; + mCheckboxes = checkboxes; + } + + @Override + public int getCount() { + return mLabels.length; + } + + /** Expands the views for individual list entries, and sets content descriptions for use by the + * TaskBackAccessibilityService. + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if(convertView == null) { + LayoutInflater inflater = LayoutInflater.from(mContext); + convertView = inflater.inflate(R.layout.tasklist_row, parent, false); + } + + CheckBox checkbox = (CheckBox) convertView.findViewById(R.id.tasklist_finished); + checkbox.setChecked(mCheckboxes[position]); + + TextView label = (TextView)(convertView.findViewById(R.id.tasklist_label)); + label.setText(mLabels[position]); + + String contentDescription = new StringBuilder() + .append(mContext.getString(R.string.task_name)) + .append(' ') + .append(mLabels[position]).toString(); + label.setContentDescription(contentDescription); + + convertView.setTag(position); + + return convertView; + } + + @Override + public Object getItem(int position) { + return mLabels[position]; + } + + @Override + public long getItemId(int position) { + return position; + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html new file mode 100644 index 000000000..eacefa10a --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html @@ -0,0 +1,9 @@ +
    +
    Window Querying Accessibility Service
    +
    Demonstrates several new accessibility features in Ice Cream Sandwich, + including the ability for an AccessibilityService to traverse the view + hierarchy using AccessibilityNodeInfo objects, service configuration via + xml files, and adding additional information to AccessibilityEvents using + AccessibilityRecords. +
    +