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