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
17package com.example.android.apis.accessibility;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.os.Build;
24import android.os.Bundle;
25import android.text.Layout;
26import android.text.StaticLayout;
27import android.text.TextPaint;
28import android.text.TextUtils;
29import android.util.AttributeSet;
30import android.util.TypedValue;
31import android.view.View;
32import android.view.accessibility.AccessibilityEvent;
33import android.view.accessibility.AccessibilityNodeInfo;
34
35import 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 */
51public 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