Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.apis.accessibility;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.os.Build;
     24 import android.os.Bundle;
     25 import android.text.Layout;
     26 import android.text.StaticLayout;
     27 import android.text.TextPaint;
     28 import android.text.TextUtils;
     29 import android.util.AttributeSet;
     30 import android.util.TypedValue;
     31 import android.view.View;
     32 import android.view.accessibility.AccessibilityEvent;
     33 import android.view.accessibility.AccessibilityNodeInfo;
     34 
     35 import com.example.android.apis.R;
     36 
     37 /**
     38  * Demonstrates how to implement accessibility support of custom views. Custom view
     39  * is a tailored widget developed by extending the base classes in the android.view
     40  * package. This sample shows how to implement the accessibility behavior via both
     41  * inheritance (non backwards compatible) and composition (backwards compatible).
     42  * <p>
     43  * While the Android framework has a diverse portfolio of views tailored for various
     44  * use cases, sometimes a developer needs a specific functionality not implemented
     45  * by the standard views. A solution is to write a custom view that extends one the
     46  * base view classes. While implementing the desired functionality a developer should
     47  * also implement accessibility support for that new functionality such that
     48  * disabled users can leverage it.
     49  * </p>
     50  */
     51 public class CustomViewAccessibilityActivity extends Activity {
     52 
     53     @Override
     54     public void onCreate(Bundle savedInstanceState) {
     55         super.onCreate(savedInstanceState);
     56         setContentView(R.layout.custom_view_accessibility);
     57     }
     58 
     59     /**
     60      * Demonstrates how to enhance the accessibility support via inheritance.
     61      * <p>
     62      * <strong>Note:</strong> Using inheritance may break your application's
     63      * backwards compatibility. In particular, overriding a method that takes as
     64      * an argument or returns a class not present on an older platform
     65      * version will prevent your application from running on that platform.
     66      * For example, {@link AccessibilityNodeInfo} was introduced in
     67      * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}, thus overriding
     68      * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
     69      *  View.onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}
     70      * will prevent you application from running on a platform older than
     71      * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH API 14}.
     72      * </p>
     73      */
     74     public static class AccessibleCompoundButtonInheritance extends BaseToggleButton {
     75 
     76         public AccessibleCompoundButtonInheritance(Context context, AttributeSet attrs) {
     77             super(context, attrs);
     78         }
     79 
     80         @Override
     81         public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
     82             super.onInitializeAccessibilityEvent(event);
     83             // We called the super implementation to let super classes
     84             // set appropriate event properties. Then we add the new property
     85             // (checked) which is not supported by a super class.
     86             event.setChecked(isChecked());
     87         }
     88 
     89         @Override
     90         public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
     91             super.onInitializeAccessibilityNodeInfo(info);
     92             // We called the super implementation to let super classes set
     93             // appropriate info properties. Then we add our properties
     94             // (checkable and checked) which are not supported by a super class.
     95             info.setCheckable(true);
     96             info.setChecked(isChecked());
     97             // Very often you will need to add only the text on the custom view.
     98             CharSequence text = getText();
     99             if (!TextUtils.isEmpty(text)) {
    100                 info.setText(text);
    101             }
    102         }
    103 
    104         @Override
    105         public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    106             super.onPopulateAccessibilityEvent(event);
    107             // We called the super implementation to populate its text to the
    108             // event. Then we add our text not present in a super class.
    109             // Very often you will need to add only the text on the custom view.
    110             CharSequence text = getText();
    111             if (!TextUtils.isEmpty(text)) {
    112                 event.getText().add(text);
    113             }
    114         }
    115     }
    116 
    117     /**
    118      * Demonstrates how to enhance the accessibility support via composition.
    119      * <p>
    120      * <strong>Note:</strong> Using composition ensures that your application is
    121      * backwards compatible. The android-support-v4 library has API that allow
    122      * using the accessibility APIs in a backwards compatible manner.
    123      * </p>
    124      */
    125     public static class AccessibleCompoundButtonComposition extends BaseToggleButton {
    126 
    127         public AccessibleCompoundButtonComposition(Context context, AttributeSet attrs) {
    128             super(context, attrs);
    129             tryInstallAccessibilityDelegate();
    130         }
    131 
    132         public void tryInstallAccessibilityDelegate() {
    133             // If the API version of the platform we are running is too old
    134             // and does not support the AccessibilityDelegate APIs, do not
    135             // call View.setAccessibilityDelegate(AccessibilityDelegate) or
    136             // refer to AccessibilityDelegate, otherwise an exception will
    137             // be thrown.
    138             // NOTE: The android-support-v4 library contains APIs the enable
    139             // using the accessibility APIs in a backwards compatible fashion.
    140             if (Build.VERSION.SDK_INT < 14) {
    141                 return;
    142             }
    143             // AccessibilityDelegate allows clients to override its methods that
    144             // correspond to the accessibility methods in View and register the
    145             // delegate in the View essentially injecting the accessibility support.
    146             setAccessibilityDelegate(new AccessibilityDelegate() {
    147                 @Override
    148                 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
    149                     super.onInitializeAccessibilityEvent(host, event);
    150                     // We called the super implementation to let super classes
    151                     // set appropriate event properties. Then we add the new property
    152                     // (checked) which is not supported by a super class.
    153                     event.setChecked(isChecked());
    154                 }
    155 
    156                 @Override
    157                 public void onInitializeAccessibilityNodeInfo(View host,
    158                         AccessibilityNodeInfo info) {
    159                     super.onInitializeAccessibilityNodeInfo(host, info);
    160                     // We called the super implementation to let super classes set
    161                     // appropriate info properties. Then we add our properties
    162                     // (checkable and checked) which are not supported by a super class.
    163                     info.setCheckable(true);
    164                     info.setChecked(isChecked());
    165                     // Very often you will need to add only the text on the custom view.
    166                     CharSequence text = getText();
    167                     if (!TextUtils.isEmpty(text)) {
    168                         info.setText(text);
    169                     }
    170                 }
    171 
    172                 @Override
    173                 public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
    174                     super.onPopulateAccessibilityEvent(host, event);
    175                     // We called the super implementation to populate its text to the
    176                     // event. Then we add our text not present in a super class.
    177                     // Very often you will need to add only the text on the custom view.
    178                     CharSequence text = getText();
    179                     if (!TextUtils.isEmpty(text)) {
    180                         event.getText().add(text);
    181                     }
    182                 }
    183             });
    184         }
    185     }
    186 
    187     /**
    188      * This is a base toggle button class whose accessibility is not tailored
    189      * to reflect the new functionality it implements.
    190      * <p>
    191      * <strong>Note:</strong> This is not a sample implementation of a toggle
    192      * button, rather a simple class needed to demonstrate how to refine the
    193      * accessibility support of a custom View.
    194      * </p>
    195      */
    196     private static class BaseToggleButton extends View {
    197         private boolean mChecked;
    198 
    199         private CharSequence mTextOn;
    200         private CharSequence mTextOff;
    201 
    202         private Layout mOnLayout;
    203         private Layout mOffLayout;
    204 
    205         private TextPaint mTextPaint;
    206 
    207         public BaseToggleButton(Context context, AttributeSet attrs) {
    208             this(context, attrs, android.R.attr.buttonStyle);
    209         }
    210 
    211         public BaseToggleButton(Context context, AttributeSet attrs, int defStyle) {
    212             super(context, attrs, defStyle);
    213 
    214             mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    215 
    216             TypedValue typedValue = new TypedValue();
    217             context.getTheme().resolveAttribute(android.R.attr.textSize, typedValue, true);
    218             final int textSize = (int) typedValue.getDimension(
    219                     context.getResources().getDisplayMetrics());
    220             mTextPaint.setTextSize(textSize);
    221 
    222             context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
    223             final int textColor = context.getResources().getColor(typedValue.resourceId);
    224             mTextPaint.setColor(textColor);
    225 
    226             mTextOn = context.getString(R.string.accessibility_custom_on);
    227             mTextOff = context.getString(R.string.accessibility_custom_off);
    228         }
    229 
    230         public boolean isChecked() {
    231             return mChecked;
    232         }
    233 
    234         public CharSequence getText() {
    235             return mChecked ? mTextOn : mTextOff;
    236         }
    237 
    238         @Override
    239         public boolean performClick() {
    240             final boolean handled = super.performClick();
    241             if (!handled) {
    242                 mChecked ^= true;
    243                 invalidate();
    244             }
    245             return handled;
    246         }
    247 
    248         @Override
    249         public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    250             if (mOnLayout == null) {
    251                 mOnLayout = makeLayout(mTextOn);
    252             }
    253             if (mOffLayout == null) {
    254                 mOffLayout = makeLayout(mTextOff);
    255             }
    256             final int minWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
    257                     + getPaddingLeft() + getPaddingRight();
    258             final int minHeight = Math.max(mOnLayout.getHeight(), mOffLayout.getHeight())
    259                     + getPaddingLeft() + getPaddingRight();
    260             setMeasuredDimension(resolveSizeAndState(minWidth, widthMeasureSpec, 0),
    261                     resolveSizeAndState(minHeight, heightMeasureSpec, 0));
    262         }
    263 
    264         private Layout makeLayout(CharSequence text) {
    265             return new StaticLayout(text, mTextPaint,
    266                     (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)),
    267                     Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
    268         }
    269 
    270         @Override
    271         protected void onDraw(Canvas canvas) {
    272             super.onDraw(canvas);
    273             canvas.save();
    274             canvas.translate(getPaddingLeft(), getPaddingRight());
    275             Layout switchText = mChecked ? mOnLayout : mOffLayout;
    276             switchText.draw(canvas);
    277             canvas.restore();
    278         }
    279     }
    280 }
    281