Adding ICS Accessibility Features to ApiDemos app.
Change-Id: I37485821a77fed483308434e7b1d407642379d8c
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index fd59107..064d058 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -36,7 +36,7 @@
<!-- For android.media.audiofx.Visualizer -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="13" />
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="14" />
<!-- We will request access to the camera, saying we require a camera
of some sort but not one with autofocus capability. -->
@@ -242,7 +242,7 @@
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
-
+
<!-- Fragment Samples -->
<activity android:name=".app.FragmentAlertDialog"
@@ -541,6 +541,25 @@
</intent-filter>
</activity>
+ <activity android:name=".accessibility.TaskListActivity"
+ android:label="@string/accessibility_taskapp_name"
+ android:enabled="@bool/atLeastIceCreamSandwich">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".accessibility.TaskBackService"
+ android:label="@string/accessibility_service_label"
+ android:enabled="@bool/atLeastIceCreamSandwich">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice" android:resource="@xml/taskbackconfig" />
+ </service>
+
+
<!-- Instrumentation Samples -->
<activity android:name=".app.LocalSample" android:label="@string/activity_local_sample">
@@ -2237,7 +2256,7 @@
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
-
+
<activity android:name=".graphics.TriangleActivity"
android:label="Graphics/OpenGL ES/Textured Triangle"
android:theme="@android:style/Theme.Holo.Dialog"
diff --git a/samples/ApiDemos/_index.html b/samples/ApiDemos/_index.html
index 9466c95..281364c 100644
--- a/samples/ApiDemos/_index.html
+++ b/samples/ApiDemos/_index.html
@@ -36,6 +36,9 @@
<li><a href="src/com/example/android/apis/graphics/TouchPaint.html">Stylus and hover
support</a></li>
<li><a href="src/com/example/android/apis/view/Switches.html">Switch widget</a></li>
+<li><a
+ href="src/com/example/android/apis/accessibility/TaskBackService.html">Window
+ Querying Accessibility Service</a></li>
</ul>
</div>
diff --git a/samples/ApiDemos/res/layout/tasklist_main.xml b/samples/ApiDemos/res/layout/tasklist_main.xml
new file mode 100644
index 0000000..61a17e3
--- /dev/null
+++ b/samples/ApiDemos/res/layout/tasklist_main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <com.example.android.apis.accessibility.TaskListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/layout/tasklist_row.xml b/samples/ApiDemos/res/layout/tasklist_row.xml
new file mode 100644
index 0000000..51bb31e
--- /dev/null
+++ b/samples/ApiDemos/res/layout/tasklist_row.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" android:weightSum="1" android:baselineAligned="false">
+ <TextView android:id="@+id/tasklist_label"
+ android:textSize="20dp"
+ android:textStyle="bold"
+ android:layout_height="48dp"
+ android:layout_width="300dip"
+ />
+
+ <CheckBox android:id="@+id/tasklist_finished"
+ android:checked="false" android:layout_height="48dp"
+ android:layout_weight=".25"
+ android:layout_width="wrap_content" />
+</LinearLayout>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index 5a7183c..1dfc1bb 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -1286,4 +1286,18 @@
<string name="dismiss">Dismiss</string>
<string name="share">Share</string>
-</resources>
+
+ <!-- ============================ -->
+ <!-- Accessibility examples strings -->
+ <!-- ============================ -->
+
+
+ <string name="accessibility_taskapp_name">Accessibility/Accessibility Node Querying</string>
+ <string name="accessibility_service_description">Task App Accessibility Service</string>
+ <string name="accessibility_service_label">TaskBack</string>
+
+ <string name="task_name">Task</string>
+ <string name="task_complete_template">Task %1$s %2$s</string>
+ <string name="task_complete">is complete</string>
+ <string name="task_not_complete">is not complete</string>
+ </resources>
diff --git a/samples/ApiDemos/res/xml/taskbackconfig.xml b/samples/ApiDemos/res/xml/taskbackconfig.xml
new file mode 100644
index 0000000..02ff11c
--- /dev/null
+++ b/samples/ApiDemos/res/xml/taskbackconfig.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+
+ <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+ android:packageNames="com.example.android.apis"
+ android:accessibilityFeedbackType="feedbackSpoken"
+ android:notificationTimeout="100"
+ android:accessibilityFlags="flagDefault"
+ android:canRetrieveWindowContent="true"
+ android:description="@string/accessibility_service_description" />
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 0000000..dbbfe3a
--- /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 0000000..bbc1403
--- /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 0000000..a4b17cb
--- /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 0000000..eacefa1
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html
@@ -0,0 +1,9 @@
+<dl>
+ <dt><a href="TaskBackService.html">Window Querying Accessibility Service</a></dt>
+ <dd>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.
+ </dd>
+</dl>