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>