ViewCompat.java revision 25121559b53b9f6c7ef7159203d42e11b9aee281
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.graphics.Rect;
20import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
21import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
22import android.view.View;
23import android.view.accessibility.AccessibilityEvent;
24
25/**
26 * Helper for accessing features in {@link View} introduced after API
27 * level 4 in a backwards compatible fashion.
28 */
29public class ViewCompat {
30    /**
31     * Always allow a user to over-scroll this view, provided it is a
32     * view that can scroll.
33     */
34    public static final int OVER_SCROLL_ALWAYS = 0;
35
36    /**
37     * Allow a user to over-scroll this view only if the content is large
38     * enough to meaningfully scroll, provided it is a view that can scroll.
39     */
40    public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
41
42    /**
43     * Never allow a user to over-scroll this view.
44     */
45    public static final int OVER_SCROLL_NEVER = 2;
46
47    private static final long FAKE_FRAME_TIME = 10;
48
49    /**
50     * Automatically determine whether a view is important for accessibility.
51     */
52    public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
53
54    /**
55     * The view is important for accessibility.
56     */
57    public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
58
59    /**
60     * The view is not important for accessibility.
61     */
62    public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
63
64    interface ViewCompatImpl {
65        public boolean canScrollHorizontally(View v, int direction);
66        public boolean canScrollVertically(View v, int direction);
67        public int getOverScrollMode(View v);
68        public void setOverScrollMode(View v, int mode);
69        public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
70        public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
71        public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
72        public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate);
73        public boolean hasTransientState(View view);
74        public void setHasTransientState(View view, boolean hasTransientState);
75        public void postInvalidateOnAnimation(View view);
76        public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
77        public void postOnAnimation(View view, Runnable action);
78        public void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
79        public int getImportantForAccessibility(View view);
80        public void setImportantForAccessibility(View view, int mode);
81        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
82    }
83
84    static class BaseViewCompatImpl implements ViewCompatImpl {
85        public boolean canScrollHorizontally(View v, int direction) {
86            return false;
87        }
88        public boolean canScrollVertically(View v, int direction) {
89            return false;
90        }
91        public int getOverScrollMode(View v) {
92            return OVER_SCROLL_NEVER;
93        }
94        public void setOverScrollMode(View v, int mode) {
95            // Do nothing; API doesn't exist
96        }
97        public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
98            // Do nothing; API doesn't exist
99        }
100        public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
101            // Do nothing; API doesn't exist
102        }
103        public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
104         // Do nothing; API doesn't exist
105        }
106        public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) {
107            // Do nothing; API doesn't exist
108        }
109        public boolean hasTransientState(View view) {
110            // A view can't have transient state if transient state wasn't supported.
111            return false;
112        }
113        public void setHasTransientState(View view, boolean hasTransientState) {
114            // Do nothing; API doesn't exist
115        }
116        public void postInvalidateOnAnimation(View view) {
117            view.postInvalidateDelayed(getFrameTime());
118        }
119        public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) {
120            view.postInvalidateDelayed(getFrameTime(), left, top, right, bottom);
121        }
122        public void postOnAnimation(View view, Runnable action) {
123            view.postDelayed(action, getFrameTime());
124        }
125        public void postOnAnimationDelayed(View view, Runnable action, long delayMillis) {
126            view.postDelayed(action, getFrameTime() + delayMillis);
127        }
128        long getFrameTime() {
129            return FAKE_FRAME_TIME;
130        }
131        public int getImportantForAccessibility(View view) {
132            return 0;
133        }
134        public void setImportantForAccessibility(View view, int mode) {
135
136        }
137        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view) {
138            return null;
139        }
140    }
141
142    static class GBViewCompatImpl extends BaseViewCompatImpl {
143        @Override
144        public int getOverScrollMode(View v) {
145            return ViewCompatGingerbread.getOverScrollMode(v);
146        }
147        @Override
148        public void setOverScrollMode(View v, int mode) {
149            ViewCompatGingerbread.setOverScrollMode(v, mode);
150        }
151    }
152
153    static class HCViewCompatImpl extends GBViewCompatImpl {
154        long getFrameTime() {
155            return ViewCompatHC.getFrameTime();
156        }
157    }
158
159    static class ICSViewCompatImpl extends HCViewCompatImpl {
160        @Override
161        public boolean canScrollHorizontally(View v, int direction) {
162            return ViewCompatICS.canScrollHorizontally(v, direction);
163        }
164        @Override
165        public boolean canScrollVertically(View v, int direction) {
166            return ViewCompatICS.canScrollVertically(v, direction);
167        }
168        @Override
169        public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
170            ViewCompatICS.onPopulateAccessibilityEvent(v, event);
171        }
172        @Override
173        public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
174            ViewCompatICS.onInitializeAccessibilityEvent(v, event);
175        }
176        @Override
177        public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) {
178            ViewCompatICS.onInitializeAccessibilityNodeInfo(v, info.getInfo());
179        }
180        @Override
181        public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
182            ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge());
183        }
184    }
185
186    static class JBViewCompatImpl extends ICSViewCompatImpl {
187        @Override
188        public boolean hasTransientState(View view) {
189            return ViewCompatJB.hasTransientState(view);
190        }
191        @Override
192        public void setHasTransientState(View view, boolean hasTransientState) {
193            ViewCompatJB.setHasTransientState(view, hasTransientState);
194        }
195        @Override
196        public void postInvalidateOnAnimation(View view) {
197            ViewCompatJB.postInvalidateOnAnimation(view);
198        }
199        @Override
200        public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) {
201            ViewCompatJB.postInvalidateOnAnimation(view, left, top, right, bottom);
202        }
203        @Override
204        public void postOnAnimation(View view, Runnable action) {
205            ViewCompatJB.postOnAnimation(view, action);
206        }
207        @Override
208        public void postOnAnimationDelayed(View view, Runnable action, long delayMillis) {
209            ViewCompatJB.postOnAnimationDelayed(view, action, delayMillis);
210        }
211        @Override
212        public int getImportantForAccessibility(View view) {
213            return ViewCompatJB.getImportantForAccessibility(view);
214        }
215        @Override
216        public void setImportantForAccessibility(View view, int mode) {
217            ViewCompatJB.setImportantForAccessibility(view, mode);
218        }
219        @Override
220        public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view) {
221            Object compat = ViewCompatJB.getAccessibilityNodeProvider(view);
222            if (compat != null) {
223                return new AccessibilityNodeProviderCompat(compat);
224            }
225            return null;
226        }
227    }
228
229    static final ViewCompatImpl IMPL;
230    static {
231        final int version = android.os.Build.VERSION.SDK_INT;
232        if (version >= 16 || android.os.Build.VERSION.CODENAME.equals("JellyBean")) {
233            IMPL = new JBViewCompatImpl();
234        } else if (version >= 14) {
235            IMPL = new ICSViewCompatImpl();
236        } else if (version >= 11) {
237            IMPL = new HCViewCompatImpl();
238        } else if (version >= 9) {
239            IMPL = new GBViewCompatImpl();
240        } else {
241            IMPL = new BaseViewCompatImpl();
242        }
243    }
244
245    /**
246     * Check if this view can be scrolled horizontally in a certain direction.
247     *
248     * @param v The View against which to invoke the method.
249     * @param direction Negative to check scrolling left, positive to check scrolling right.
250     * @return true if this view can be scrolled in the specified direction, false otherwise.
251     */
252    public static boolean canScrollHorizontally(View v, int direction) {
253        return IMPL.canScrollHorizontally(v, direction);
254    }
255
256    /**
257     * Check if this view can be scrolled vertically in a certain direction.
258     *
259     * @param v The View against which to invoke the method.
260     * @param direction Negative to check scrolling up, positive to check scrolling down.
261     * @return true if this view can be scrolled in the specified direction, false otherwise.
262     */
263    public static boolean canScrollVertically(View v, int direction) {
264        return IMPL.canScrollVertically(v, direction);
265    }
266
267    /**
268     * Returns the over-scroll mode for this view. The result will be
269     * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
270     * (allow over-scrolling only if the view content is larger than the container),
271     * or {@link #OVER_SCROLL_NEVER}.
272     *
273     * @param v The View against which to invoke the method.
274     * @return This view's over-scroll mode.
275     */
276    public static int getOverScrollMode(View v) {
277        return IMPL.getOverScrollMode(v);
278    }
279
280    /**
281     * Set the over-scroll mode for this view. Valid over-scroll modes are
282     * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
283     * (allow over-scrolling only if the view content is larger than the container),
284     * or {@link #OVER_SCROLL_NEVER}.
285     *
286     * Setting the over-scroll mode of a view will have an effect only if the
287     * view is capable of scrolling.
288     *
289     * @param v The View against which to invoke the method.
290     * @param overScrollMode The new over-scroll mode for this view.
291     */
292    public static void setOverScrollMode(View v, int overScrollMode) {
293        IMPL.setOverScrollMode(v, overScrollMode);
294    }
295
296    /**
297     * Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
298     * giving a chance to this View to populate the accessibility event with its
299     * text content. While this method is free to modify event
300     * attributes other than text content, doing so should normally be performed in
301     * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}.
302     * <p>
303     * Example: Adding formatted date string to an accessibility event in addition
304     *          to the text added by the super implementation:
305     * <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
306     *     super.onPopulateAccessibilityEvent(event);
307     *     final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
308     *     String selectedDateUtterance = DateUtils.formatDateTime(mContext,
309     *         mCurrentDate.getTimeInMillis(), flags);
310     *     event.getText().add(selectedDateUtterance);
311     * }</pre>
312     * <p>
313     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
314     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
315     * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
316     *  AccessibilityEvent)}
317     * is responsible for handling this call.
318     * </p>
319     * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
320     * information to the event, in case the default implementation has basic information to add.
321     * </p>
322     *
323     * @param v The View against which to invoke the method.
324     * @param event The accessibility event which to populate.
325     *
326     * @see View#sendAccessibilityEvent(int)
327     * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
328     */
329    public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) {
330        IMPL.onPopulateAccessibilityEvent(v, event);
331    }
332
333    /**
334     * Initializes an {@link AccessibilityEvent} with information about
335     * this View which is the event source. In other words, the source of
336     * an accessibility event is the view whose state change triggered firing
337     * the event.
338     * <p>
339     * Example: Setting the password property of an event in addition
340     *          to properties set by the super implementation:
341     * <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
342     *     super.onInitializeAccessibilityEvent(event);
343     *     event.setPassword(true);
344     * }</pre>
345     * <p>
346     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
347     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
348     * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
349     *  AccessibilityEvent)}
350     * is responsible for handling this call.
351     * </p>
352     * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
353     * information to the event, in case the default implementation has basic information to add.
354     * </p>
355     *
356     * @param v The View against which to invoke the method.
357     * @param event The event to initialize.
358     *
359     * @see View#sendAccessibilityEvent(int)
360     * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
361     */
362    public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) {
363        IMPL.onInitializeAccessibilityEvent(v, event);
364    }
365
366    /**
367     * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
368     * about this view. The base implementation sets:
369     * <ul>
370     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li>
371     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
372     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
373     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
374     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li>
375     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
376     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li>
377     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li>
378     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li>
379     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li>
380     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li>
381     * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li>
382     * </ul>
383     * <p>
384     * Subclasses should override this method, call the super implementation,
385     * and set additional attributes.
386     * </p>
387     * <p>
388     * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
389     * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
390     * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
391     *  android.view.accessibility.AccessibilityNodeInfo)}
392     * is responsible for handling this call.
393     * </p>
394     *
395     * @param v The View against which to invoke the method.
396     * @param info The instance to initialize.
397     */
398    public static void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) {
399        IMPL.onInitializeAccessibilityNodeInfo(v, info);
400    }
401
402    /**
403     * Sets a delegate for implementing accessibility support via compositon as
404     * opposed to inheritance. The delegate's primary use is for implementing
405     * backwards compatible widgets. For more details see
406     * {@link android.view.View.AccessibilityDelegate}.
407     *
408     * @param v The View against which to invoke the method.
409     * @param delegate The delegate instance.
410     *
411     * @see android.view.View.AccessibilityDelegate
412     */
413    public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
414        IMPL.setAccessibilityDelegate(v, delegate);
415    }
416
417    /**
418     * Indicates whether the view is currently tracking transient state that the
419     * app should not need to concern itself with saving and restoring, but that
420     * the framework should take special note to preserve when possible.
421     *
422     * @param view View to check for transient state
423     * @return true if the view has transient state
424     */
425    public static boolean hasTransientState(View view) {
426        return IMPL.hasTransientState(view);
427    }
428
429    /**
430     * Set whether this view is currently tracking transient state that the
431     * framework should attempt to preserve when possible.
432     *
433     * @param view View tracking transient state
434     * @param hasTransientState true if this view has transient state
435     */
436    public static void setHasTransientState(View view, boolean hasTransientState) {
437        IMPL.setHasTransientState(view, hasTransientState);
438    }
439
440    /**
441     * <p>Cause an invalidate to happen on the next animation time step, typically the
442     * next display frame.</p>
443     *
444     * <p>This method can be invoked from outside of the UI thread
445     * only when this View is attached to a window.</p>
446     *
447     * @param view View to invalidate
448     */
449    public static void postInvalidateOnAnimation(View view) {
450        IMPL.postInvalidateOnAnimation(view);
451    }
452
453    /**
454     * <p>Cause an invalidate of the specified area to happen on the next animation
455     * time step, typically the next display frame.</p>
456     *
457     * <p>This method can be invoked from outside of the UI thread
458     * only when this View is attached to a window.</p>
459     *
460     * @param view View to invalidate
461     * @param left The left coordinate of the rectangle to invalidate.
462     * @param top The top coordinate of the rectangle to invalidate.
463     * @param right The right coordinate of the rectangle to invalidate.
464     * @param bottom The bottom coordinate of the rectangle to invalidate.
465     */
466    public static void postInvalidateOnAnimation(View view, int left, int top,
467            int right, int bottom) {
468        IMPL.postInvalidateOnAnimation(view, left, top, right, bottom);
469    }
470
471    /**
472     * <p>Causes the Runnable to execute on the next animation time step.
473     * The runnable will be run on the user interface thread.</p>
474     *
475     * <p>This method can be invoked from outside of the UI thread
476     * only when this View is attached to a window.</p>
477     *
478     * @param view View to post this Runnable to
479     * @param action The Runnable that will be executed.
480     */
481    public static void postOnAnimation(View view, Runnable action) {
482        IMPL.postOnAnimation(view, action);
483    }
484
485    /**
486     * <p>Causes the Runnable to execute on the next animation time step,
487     * after the specified amount of time elapses.
488     * The runnable will be run on the user interface thread.</p>
489     *
490     * <p>This method can be invoked from outside of the UI thread
491     * only when this View is attached to a window.</p>
492     *
493     * @param view The view to post this Runnable to
494     * @param action The Runnable that will be executed.
495     * @param delayMillis The delay (in milliseconds) until the Runnable
496     *        will be executed.
497     */
498    public static void postOnAnimationDelayed(View view, Runnable action, long delayMillis) {
499        IMPL.postOnAnimationDelayed(view, action, delayMillis);
500    }
501
502    /**
503     * Gets the mode for determining whether this View is important for accessibility
504     * which is if it fires accessibility events and if it is reported to
505     * accessibility services that query the screen.
506     *
507     * @param view The view whose property to get.
508     * @return The mode for determining whether a View is important for accessibility.
509     *
510     * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
511     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
512     * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
513     */
514    public static int getImportantForAccessibility(View view) {
515        return IMPL.getImportantForAccessibility(view);
516    }
517
518    /**
519     * Sets how to determine whether this view is important for accessibility
520     * which is if it fires accessibility events and if it is reported to
521     * accessibility services that query the screen.
522     *
523     * @param view The view whose property to set.
524     * @param mode How to determine whether this view is important for accessibility.
525     *
526     * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
527     * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
528     * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
529     */
530    public static void setImportantForAccessibility(View view, int mode) {
531        IMPL.setImportantForAccessibility(view, mode);
532    }
533
534    /**
535     * Gets the provider for managing a virtual view hierarchy rooted at this View
536     * and reported to {@link android.accessibilityservice.AccessibilityServiceCompat}s
537     * that explore the window content.
538     * <p>
539     * If this method returns an instance, this instance is responsible for managing
540     * {@link AccessibilityNodeInfoComapt}s describing the virtual sub-tree rooted at
541     * this View including the one representing the View itself. Similarly the returned
542     * instance is responsible for performing accessibility actions on any virtual
543     * view or the root view itself.
544     * </p>
545     * <p>
546     * If an {@link AccessibilityDelegateCompat} has been specified via calling
547     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
548     * {@link AccessibilityDelegateCompat#getAccessibilityNodeProvider(View)}
549     * is responsible for handling this call.
550     * </p>
551     *
552     * @param view The view whose property to get.
553     * @return The provider.
554     *
555     * @see AccessibilityNodeProviderCompat
556     */
557    public static AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view) {
558        return IMPL.getAccessibilityNodeProvider(view);
559    }
560}
561