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