Adding a code sample for implementing accessibility in a custom view.

bug:5683532

Change-Id: I79471c431ba3aeb4abe57f850e84a4da0bc1feba
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 8157e9f..4e63dde 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -542,7 +542,7 @@
         </activity>
 
         <!-- ============================ -->
-        <!--  Accessibility examples strings  -->
+        <!--  Accessibility examples      -->
         <!-- ============================ -->
 
         <activity android:name=".accessibility.ClockBackActivity"
@@ -580,6 +580,15 @@
                 android:resource="@xml/taskbackconfig" />
         </service>
 
+        <activity android:name=".accessibility.CustomViewAccessibilityActivity"
+                  android:label="@string/accessibility_custom_view"
+                  android:enabled="@bool/atLeastIceCreamSandwich">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <!-- Instrumentation Samples -->
 
         <activity android:name=".app.LocalSample" android:label="@string/activity_local_sample">
diff --git a/samples/ApiDemos/_index.html b/samples/ApiDemos/_index.html
index 281364c..3c45843 100644
--- a/samples/ApiDemos/_index.html
+++ b/samples/ApiDemos/_index.html
@@ -39,6 +39,10 @@
 <li><a
   href="src/com/example/android/apis/accessibility/TaskBackService.html">Window
   Querying Accessibility Service</a></li>
+<li><a
+  href="src/com/example/android/apis/accessibility/CustomViewAccessibilityActivity.html">Custom View
+  Accessibility</a></li>
+
 </ul>
 </div>
 
diff --git a/samples/ApiDemos/res/layout/custom_view_accessibility.xml b/samples/ApiDemos/res/layout/custom_view_accessibility.xml
new file mode 100644
index 0000000..ceb6b61
--- /dev/null
+++ b/samples/ApiDemos/res/layout/custom_view_accessibility.xml
@@ -0,0 +1,44 @@
+<?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:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="50dip"
+        android:text="@string/accessibility_custom_view_instructions">
+    </TextView>
+
+    <view
+        class="com.example.android.apis.accessibility.CustomViewAccessibilityActivity$AccessibleCompoundButtonInheritance"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="20dip"
+        android:clickable="true" >
+    </view>
+
+    <view
+        class="com.example.android.apis.accessibility.CustomViewAccessibilityActivity$AccessibleCompoundButtonComposition"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="20dip"
+        android:clickable="true" >
+    </view>
+
+</LinearLayout>
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index a31ad8a..2913844 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -1296,7 +1296,7 @@
     <string name="accessibility_service_label">ClockBack</string>
     <string name="accessibility_service_instructions">
         1. Enable TalkBack (Settings -> Accessibility -> TalkBack).
-        \n\n2. Enable Explore-byTouch (Settings -> Accessibility -> Explore by Touch).
+        \n\n2. Enable Explore-by-Touch (Settings -> Accessibility -> Explore by Touch).
         \n\n3. Touch explore the Clock application and the home screen.
         \n\n4. Go to the Clock application and change the time of an alarm.
         \n\n5. Enable ClockBack (Settings -> Accessibility -> ClockBack).
@@ -1316,10 +1316,19 @@
     <string name="accessibility_query_window_description">Task App Accessibility Service</string>
     <string name="accessibility_query_window_instructions">
         1. Enable QueryBack (Settings -> Accessibility -> QueryBack).
-        \n\n2. Enable Explore-byTouch (Settings -> Accessibility -> Explore by Touch).
+        \n\n2. Enable Explore-by-Touch (Settings -> Accessibility -> Explore by Touch).
         \n\n3. Touch explore the list.
     </string>
 
+    <string name="accessibility_custom_view">Accessibility/Custom View</string>
+    <string name="accessibility_custom_view_instructions">
+        1. Enable TalkBack (Settings -> Accessibility -> TalkBack).
+        \n\n2. Enable Explore-by-Touch (Settings -> Accessibility -> Explore by Touch).
+        \n\n3. Touch explore/poke the buttons.
+    </string>
+    <string name="accessibility_custom_on">On</string>
+    <string name="accessibility_custom_off">Off</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>
diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/CustomViewAccessibilityActivity.java b/samples/ApiDemos/src/com/example/android/apis/accessibility/CustomViewAccessibilityActivity.java
new file mode 100644
index 0000000..8facfba
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/CustomViewAccessibilityActivity.java
@@ -0,0 +1,280 @@
+/*
+ * 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 android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.example.android.apis.R;
+
+/**
+ * Demonstrates how to implement accessibility support of custom views. Custom view
+ * is a tailored widget developed by extending the base classes in the android.view
+ * package. This sample shows how to implement the accessibility behavior via both
+ * inheritance (non backwards compatible) and composition (backwards compatible).
+ * <p>
+ * While the Android framework has a diverse portfolio of views tailored for various
+ * use cases, sometimes a developer needs a specific functionality not implemented
+ * by the standard views. A solution is to write a custom view that extends one the
+ * base view classes. While implementing the desired functionality a developer should
+ * also implement accessibility support for that new functionality such that
+ * disabled users can leverage it.
+ * </p>
+ */
+public class CustomViewAccessibilityActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.custom_view_accessibility);
+    }
+
+    /**
+     * Demonstrates how to enhance the accessibility support via inheritance.
+     * <p>
+     * <strong>Note:</strong> Using inheritance may break your application's
+     * backwards compatibility. In particular, overriding a method that takes as
+     * an argument or returns a class not present on an older platform
+     * version will prevent your application from running on that platform.
+     * For example, {@link AccessibilityNodeInfo} was introduced in
+     * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}, thus overriding
+     * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+     *  View.onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}
+     * will prevent you application from running on a platform older than
+     * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}.
+     * </p>
+     */
+    public static class AccessibleCompoundButtonInheritance extends BaseToggleButton {
+
+        public AccessibleCompoundButtonInheritance(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(event);
+            // We called the super implementation to let super classes
+            // set appropriate event properties. Then we add the new property
+            // (checked) which is not supported by a super class.
+            event.setChecked(isChecked());
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(info);
+            // We called the super implementation to let super classes set
+            // appropriate info properties. Then we add our properties
+            // (checkable and checked) which are not supported by a super class.
+            info.setCheckable(true);
+            info.setChecked(isChecked());
+            // Very often you will need to add only the text on the custom view.
+            CharSequence text = getText();
+            if (!TextUtils.isEmpty(text)) {
+                info.setText(text);
+            }
+        }
+
+        @Override
+        public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+            super.onPopulateAccessibilityEvent(event);
+            // We called the super implementation to populate its text to the
+            // event. Then we add our text not present in a super class.
+            // Very often you will need to add only the text on the custom view.
+            CharSequence text = getText();
+            if (!TextUtils.isEmpty(text)) {
+                event.getText().add(text);
+            }
+        }
+    }
+
+    /**
+     * Demonstrates how to enhance the accessibility support via composition.
+     * <p>
+     * <strong>Note:</strong> Using composition ensures that your application is
+     * backwards compatible. The android-support-v4 library has API that allow
+     * using the accessibility APIs in a backwards compatible manner.
+     * </p>
+     */
+    public static class AccessibleCompoundButtonComposition extends BaseToggleButton {
+
+        public AccessibleCompoundButtonComposition(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            tryInstallAccessibilityDelegate();
+        }
+
+        public void tryInstallAccessibilityDelegate() {
+            // If the API version of the platform we are running is too old
+            // and does not support the AccessibilityDelegate APIs, do not
+            // call View.setAccessibilityDelegate(AccessibilityDelegate) or
+            // refer to AccessibilityDelegate, otherwise an exception will
+            // be thrown.
+            // NOTE: The android-support-v4 library contains APIs the enable
+            // using the accessibility APIs in a backwards compatible fashion.
+            if (Build.VERSION.SDK_INT < 14) {
+                return;
+            }
+            // AccessibilityDelegate allows clients to override its methods that
+            // correspond to the accessibility methods in View and register the
+            // delegate in the View essentially injecting the accessibility support.
+            setAccessibilityDelegate(new AccessibilityDelegate() {
+                @Override
+                public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+                    super.onInitializeAccessibilityEvent(host, event);
+                    // We called the super implementation to let super classes
+                    // set appropriate event properties. Then we add the new property
+                    // (checked) which is not supported by a super class.
+                    event.setChecked(isChecked());
+                }
+
+                @Override
+                public void onInitializeAccessibilityNodeInfo(View host,
+                        AccessibilityNodeInfo info) {
+                    super.onInitializeAccessibilityNodeInfo(host, info);
+                    // We called the super implementation to let super classes set
+                    // appropriate info properties. Then we add our properties
+                    // (checkable and checked) which are not supported by a super class.
+                    info.setCheckable(true);
+                    info.setChecked(isChecked());
+                    // Very often you will need to add only the text on the custom view.
+                    CharSequence text = getText();
+                    if (!TextUtils.isEmpty(text)) {
+                        info.setText(text);
+                    }
+                }
+
+                @Override
+                public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+                    super.onPopulateAccessibilityEvent(host, event);
+                    // We called the super implementation to populate its text to the
+                    // event. Then we add our text not present in a super class.
+                    // Very often you will need to add only the text on the custom view.
+                    CharSequence text = getText();
+                    if (!TextUtils.isEmpty(text)) {
+                        event.getText().add(text);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * This is a base toggle button class whose accessibility is not tailored
+     * to reflect the new functionality it implements.
+     * <p>
+     * <strong>Note:</strong> This is not a sample implementation of a toggle
+     * button, rather a simple class needed to demonstrate how to refine the
+     * accessibility support of a custom View.
+     * </p>
+     */
+    private static class BaseToggleButton extends View {
+        private boolean mChecked;
+
+        private CharSequence mTextOn;
+        private CharSequence mTextOff;
+
+        private Layout mOnLayout;
+        private Layout mOffLayout;
+
+        private TextPaint mTextPaint;
+
+        public BaseToggleButton(Context context, AttributeSet attrs) {
+            this(context, attrs, android.R.attr.buttonStyle);
+        }
+
+        public BaseToggleButton(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+
+            mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+
+            TypedValue typedValue = new TypedValue();
+            context.getTheme().resolveAttribute(android.R.attr.textSize, typedValue, true);
+            final int textSize = (int) typedValue.getDimension(
+                    context.getResources().getDisplayMetrics());
+            mTextPaint.setTextSize(textSize);
+
+            context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
+            final int textColor = context.getResources().getColor(typedValue.resourceId);
+            mTextPaint.setColor(textColor);
+
+            mTextOn = context.getString(R.string.accessibility_custom_on);
+            mTextOff = context.getString(R.string.accessibility_custom_off);
+        }
+
+        public boolean isChecked() {
+            return mChecked;
+        }
+
+        public CharSequence getText() {
+            return mChecked ? mTextOn : mTextOff;
+        }
+
+        @Override
+        public boolean performClick() {
+            final boolean handled = super.performClick();
+            if (!handled) {
+                mChecked ^= true;
+                invalidate();
+            }
+            return handled;
+        }
+
+        @Override
+        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            if (mOnLayout == null) {
+                mOnLayout = makeLayout(mTextOn);
+            }
+            if (mOffLayout == null) {
+                mOffLayout = makeLayout(mTextOff);
+            }
+            final int minWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+                    + getPaddingLeft() + getPaddingRight();
+            final int minHeight = Math.max(mOnLayout.getHeight(), mOffLayout.getHeight())
+                    + getPaddingLeft() + getPaddingRight();
+            setMeasuredDimension(resolveSizeAndState(minWidth, widthMeasureSpec, 0),
+                    resolveSizeAndState(minHeight, heightMeasureSpec, 0));
+        }
+
+        private Layout makeLayout(CharSequence text) {
+            return new StaticLayout(text, mTextPaint,
+                    (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)),
+                    Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingRight());
+            Layout switchText = mChecked ? mOnLayout : mOffLayout;
+            switchText.draw(canvas);
+            canvas.restore();
+        }
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html
index 7506cb3..713d913 100644
--- a/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html
+++ b/samples/ApiDemos/src/com/example/android/apis/accessibility/_index.html
@@ -9,7 +9,7 @@
     how to provide dynamic, context-dependent feedback &mdash; feedback type changes depending on
     the ringer mode.
   </dd>
-<dt>
+</dl>
 
 <dl>
   <dt><a href="TaskBackService.html">Window Querying Accessibility Service</a></dt>
@@ -21,3 +21,12 @@
     AccessibilityRecords.
   </dd>
 </dl>
+
+<dl>
+  <dt><a href="CustomViewAccessibilityActivity.html">Custom View Accessibility</a></dt>
+  <dd>Demonstrates how to implement accessibility support of custom views. Custom view
+      is a tailored widget developed by extending the base classes in the android.view
+      package. This sample shows how to implement the accessibility behavior via both
+      inheritance (non backwards compatible) and composition (backwards compatible).
+  </dd>
+</dl>