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 android.support.v4.view;
18
19import android.os.Build;
20import android.os.Bundle;
21import android.support.annotation.RequiresApi;
22import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
23import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
24import android.view.View;
25import android.view.View.AccessibilityDelegate;
26import android.view.ViewGroup;
27import android.view.accessibility.AccessibilityEvent;
28import android.view.accessibility.AccessibilityNodeInfo;
29import android.view.accessibility.AccessibilityNodeProvider;
30
31/**
32 * Helper for accessing {@link AccessibilityDelegate} introduced after
33 * API level 4 in a backwards compatible fashion.
34 * <p>
35 * <strong>Note:</strong> On platform versions prior to
36 * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
37 * views in the {@code android.widget.*} package are called <i>before</i>
38 * host methods. This prevents certain properties such as class name from
39 * being modified by overriding
40 * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
41 * as any changes will be overwritten by the host class.
42 * <p>
43 * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
44 * methods are called <i>after</i> host methods, which all properties to be
45 * modified without being overwritten by the host class.
46 */
47public class AccessibilityDelegateCompat {
48
49    static class AccessibilityDelegateBaseImpl {
50        public AccessibilityDelegate newAccessibilityDelegateBridge(
51                final AccessibilityDelegateCompat compat) {
52            return new AccessibilityDelegate() {
53                @Override
54                public boolean dispatchPopulateAccessibilityEvent(View host,
55                        AccessibilityEvent event) {
56                    return compat.dispatchPopulateAccessibilityEvent(host, event);
57                }
58
59                @Override
60                public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
61                    compat.onInitializeAccessibilityEvent(host, event);
62                }
63
64                @Override
65                public void onInitializeAccessibilityNodeInfo(
66                        View host, AccessibilityNodeInfo info) {
67                    compat.onInitializeAccessibilityNodeInfo(host,
68                            AccessibilityNodeInfoCompat.wrap(info));
69                }
70
71                @Override
72                public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
73                    compat.onPopulateAccessibilityEvent(host, event);
74                }
75
76                @Override
77                public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
78                        AccessibilityEvent event) {
79                    return compat.onRequestSendAccessibilityEvent(host, child, event);
80                }
81
82                @Override
83                public void sendAccessibilityEvent(View host, int eventType) {
84                    compat.sendAccessibilityEvent(host, eventType);
85                }
86
87                @Override
88                public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
89                    compat.sendAccessibilityEventUnchecked(host, event);
90                }
91            };
92        }
93
94        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(
95                AccessibilityDelegate delegate, View host) {
96            // Do nothing. Added in API 16.
97            return null;
98        }
99
100        public boolean performAccessibilityAction(AccessibilityDelegate delegate, View host,
101                int action, Bundle args) {
102            // Do nothing. Added in API 16.
103            return false;
104        }
105    }
106
107    @RequiresApi(16)
108    static class AccessibilityDelegateApi16Impl extends AccessibilityDelegateBaseImpl {
109        @Override
110        public AccessibilityDelegate newAccessibilityDelegateBridge(
111                final AccessibilityDelegateCompat compat) {
112            return new AccessibilityDelegate()  {
113                @Override
114                public boolean dispatchPopulateAccessibilityEvent(View host,
115                        AccessibilityEvent event) {
116                    return compat.dispatchPopulateAccessibilityEvent(host, event);
117                }
118
119                @Override
120                public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
121                    compat.onInitializeAccessibilityEvent(host, event);
122                }
123
124                @Override
125                public void onInitializeAccessibilityNodeInfo(
126                        View host, AccessibilityNodeInfo info) {
127                    compat.onInitializeAccessibilityNodeInfo(host,
128                            AccessibilityNodeInfoCompat.wrap(info));
129                }
130
131                @Override
132                public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
133                    compat.onPopulateAccessibilityEvent(host, event);
134                }
135
136                @Override
137                public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
138                        AccessibilityEvent event) {
139                    return compat.onRequestSendAccessibilityEvent(host, child, event);
140                }
141
142                @Override
143                public void sendAccessibilityEvent(View host, int eventType) {
144                    compat.sendAccessibilityEvent(host, eventType);
145                }
146
147                @Override
148                public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
149                    compat.sendAccessibilityEventUnchecked(host, event);
150                }
151
152                @Override
153                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
154                    AccessibilityNodeProviderCompat provider =
155                        compat.getAccessibilityNodeProvider(host);
156                    return (provider != null)
157                            ? (AccessibilityNodeProvider) provider.getProvider() : null;
158                }
159
160                @Override
161                public boolean performAccessibilityAction(View host, int action, Bundle args) {
162                    return compat.performAccessibilityAction(host, action, args);
163                }
164            };
165        }
166
167        @Override
168        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(
169                AccessibilityDelegate delegate, View host) {
170            Object provider = delegate.getAccessibilityNodeProvider(host);
171            if (provider != null) {
172                return new AccessibilityNodeProviderCompat(provider);
173            }
174            return null;
175        }
176
177        @Override
178        public boolean performAccessibilityAction(AccessibilityDelegate delegate, View host,
179                int action, Bundle args) {
180            return delegate.performAccessibilityAction(host, action, args);
181        }
182    }
183
184    private static final AccessibilityDelegateBaseImpl IMPL;
185    private static final AccessibilityDelegate DEFAULT_DELEGATE;
186
187    static {
188        if (Build.VERSION.SDK_INT >= 16) { // JellyBean
189            IMPL = new AccessibilityDelegateApi16Impl();
190        } else {
191            IMPL = new AccessibilityDelegateBaseImpl();
192        }
193        DEFAULT_DELEGATE = new AccessibilityDelegate();
194    }
195
196    final AccessibilityDelegate mBridge;
197
198    /**
199     * Creates a new instance.
200     */
201    public AccessibilityDelegateCompat() {
202        mBridge = IMPL.newAccessibilityDelegateBridge(this);
203    }
204
205    /**
206     * @return The wrapped bridge implementation.
207     */
208    AccessibilityDelegate getBridge() {
209        return mBridge;
210    }
211
212    /**
213     * Sends an accessibility event of the given type. If accessibility is not
214     * enabled this method has no effect.
215     * <p>
216     * The default implementation behaves as {@link View#sendAccessibilityEvent(int)
217     * View#sendAccessibilityEvent(int)} for the case of no accessibility delegate
218     * been set.
219     * </p>
220     *
221     * @param host The View hosting the delegate.
222     * @param eventType The type of the event to send.
223     *
224     * @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int)
225     */
226    public void sendAccessibilityEvent(View host, int eventType) {
227        DEFAULT_DELEGATE.sendAccessibilityEvent(host, eventType);
228    }
229
230    /**
231     * Sends an accessibility event. This method behaves exactly as
232     * {@link #sendAccessibilityEvent(View, int)} but takes as an argument an
233     * empty {@link AccessibilityEvent} and does not perform a check whether
234     * accessibility is enabled.
235     * <p>
236     * The default implementation behaves as
237     * {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)
238     * View#sendAccessibilityEventUnchecked(AccessibilityEvent)} for
239     * the case of no accessibility delegate been set.
240     * </p>
241     *
242     * @param host The View hosting the delegate.
243     * @param event The event to send.
244     *
245     * @see View#sendAccessibilityEventUnchecked(AccessibilityEvent)
246     *      View#sendAccessibilityEventUnchecked(AccessibilityEvent)
247     */
248    public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
249        DEFAULT_DELEGATE.sendAccessibilityEventUnchecked(host, event);
250    }
251
252    /**
253     * Dispatches an {@link AccessibilityEvent} to the host {@link View} first and then
254     * to its children for adding their text content to the event.
255     * <p>
256     * The default implementation behaves as
257     * {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
258     * View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} for
259     * the case of no accessibility delegate been set.
260     * </p>
261     *
262     * @param host The View hosting the delegate.
263     * @param event The event.
264     * @return True if the event population was completed.
265     *
266     * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
267     *      View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
268     */
269    public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
270        return DEFAULT_DELEGATE.dispatchPopulateAccessibilityEvent(host, event);
271    }
272
273    /**
274     * Gives a chance to the host View to populate the accessibility event with its
275     * text content.
276     * <p>
277     * The default implementation behaves as
278     * {@link ViewCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)
279     * ViewCompat#onPopulateAccessibilityEvent(AccessibilityEvent)} for
280     * the case of no accessibility delegate been set.
281     * </p>
282     *
283     * @param host The View hosting the delegate.
284     * @param event The accessibility event which to populate.
285     *
286     * @see ViewCompat#onPopulateAccessibilityEvent(View ,AccessibilityEvent)
287     *      ViewCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)
288     */
289    public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
290        DEFAULT_DELEGATE.onPopulateAccessibilityEvent(host, event);
291    }
292
293    /**
294     * Initializes an {@link AccessibilityEvent} with information about the
295     * the host View which is the event source.
296     * <p>
297     * The default implementation behaves as
298     * {@link ViewCompat#onInitializeAccessibilityEvent(View v, AccessibilityEvent event)
299     * ViewCompat#onInitalizeAccessibilityEvent(View v, AccessibilityEvent event)} for
300     * the case of no accessibility delegate been set.
301     * </p>
302     *
303     * @param host The View hosting the delegate.
304     * @param event The event to initialize.
305     *
306     * @see ViewCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)
307     *      ViewCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)
308     */
309    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
310        DEFAULT_DELEGATE.onInitializeAccessibilityEvent(host, event);
311    }
312
313    /**
314     * Initializes an {@link AccessibilityNodeInfoCompat} with information about the host view.
315     * <p>
316     * The default implementation behaves as
317     * {@link ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)
318     * ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)} for
319     * the case of no accessibility delegate been set.
320     * </p>
321     *
322     * @param host The View hosting the delegate.
323     * @param info The instance to initialize.
324     *
325     * @see ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)
326     *      ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)
327     */
328    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
329        DEFAULT_DELEGATE.onInitializeAccessibilityNodeInfo(
330                host, info.unwrap());
331    }
332
333    /**
334     * Called when a child of the host View has requested sending an
335     * {@link AccessibilityEvent} and gives an opportunity to the parent (the host)
336     * to augment the event.
337     * <p>
338     * The default implementation behaves as
339     * {@link ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)
340     * ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)} for
341     * the case of no accessibility delegate been set.
342     * </p>
343     *
344     * @param host The View hosting the delegate.
345     * @param child The child which requests sending the event.
346     * @param event The event to be sent.
347     * @return True if the event should be sent
348     *
349     * @see ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)
350     *      ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)
351     */
352    public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
353            AccessibilityEvent event) {
354        return DEFAULT_DELEGATE.onRequestSendAccessibilityEvent(host, child, event);
355    }
356
357    /**
358     * Gets the provider for managing a virtual view hierarchy rooted at this View
359     * and reported to {@link android.accessibilityservice.AccessibilityService}s
360     * that explore the window content.
361     * <p>
362     * The default implementation behaves as
363     * {@link ViewCompat#getAccessibilityNodeProvider(View) ViewCompat#getAccessibilityNodeProvider(View)}
364     * for the case of no accessibility delegate been set.
365     * </p>
366     *
367     * @return The provider.
368     *
369     * @see AccessibilityNodeProviderCompat
370     */
371    public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
372        return IMPL.getAccessibilityNodeProvider(DEFAULT_DELEGATE, host);
373    }
374
375    /**
376     * Performs the specified accessibility action on the view. For
377     * possible accessibility actions look at {@link AccessibilityNodeInfoCompat}.
378     * <p>
379     * The default implementation behaves as
380     * {@link View#performAccessibilityAction(int, Bundle)
381     *  View#performAccessibilityAction(int, Bundle)} for the case of
382     *  no accessibility delegate been set.
383     * </p>
384     *
385     * @param action The action to perform.
386     * @return Whether the action was performed.
387     *
388     * @see View#performAccessibilityAction(int, Bundle)
389     *      View#performAccessibilityAction(int, Bundle)
390     */
391    public boolean performAccessibilityAction(View host, int action, Bundle args) {
392        return IMPL.performAccessibilityAction(DEFAULT_DELEGATE, host, action, args);
393    }
394}
395