DrawerLayout.java revision 70acb0c19be3831a2080e4f902324de16bfbf62e
1/*
2 * Copyright (C) 2013 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
18package android.support.v4.widget;
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.graphics.drawable.ColorDrawable;
27import android.graphics.drawable.Drawable;
28import android.os.Build;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.os.SystemClock;
32import android.support.annotation.ColorInt;
33import android.support.annotation.DrawableRes;
34import android.support.annotation.IntDef;
35import android.support.annotation.Nullable;
36import android.support.v4.content.ContextCompat;
37import android.support.v4.view.AccessibilityDelegateCompat;
38import android.support.v4.view.GravityCompat;
39import android.support.v4.view.KeyEventCompat;
40import android.support.v4.view.MotionEventCompat;
41import android.support.v4.view.ViewCompat;
42import android.support.v4.view.ViewGroupCompat;
43import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
44import android.util.AttributeSet;
45import android.view.Gravity;
46import android.view.KeyEvent;
47import android.view.MotionEvent;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.ViewParent;
51import android.view.accessibility.AccessibilityEvent;
52
53import java.lang.annotation.Retention;
54import java.lang.annotation.RetentionPolicy;
55import java.util.List;
56
57/**
58 * DrawerLayout acts as a top-level container for window content that allows for
59 * interactive "drawer" views to be pulled out from the edge of the window.
60 *
61 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
62 * attribute on child views corresponding to which side of the view you want the drawer
63 * to emerge from: left or right. (Or start/end on platform versions that support layout direction.)
64 * </p>
65 *
66 * <p>To use a DrawerLayout, position your primary content view as the first child with
67 * a width and height of <code>match_parent</code>. Add drawers as child views after the main
68 * content view and set the <code>layout_gravity</code> appropriately. Drawers commonly use
69 * <code>match_parent</code> for height with a fixed width.</p>
70 *
71 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
72 * Avoid performing expensive operations such as layout during animation as it can cause
73 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
74 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
75 *
76 * <p>As per the <a href="{@docRoot}design/patterns/navigation-drawer.html">Android Design
77 * guide</a>, any drawers positioned to the left/start should
78 * always contain content for navigating around the application, whereas any drawers
79 * positioned to the right/end should always contain actions to take on the current content.
80 * This preserves the same navigation left, actions right structure present in the Action Bar
81 * and elsewhere.</p>
82 *
83 * <p>For more information about how to use DrawerLayout, read <a
84 * href="{@docRoot}training/implementing-navigation/nav-drawer.html">Creating a Navigation
85 * Drawer</a>.</p>
86 */
87public class DrawerLayout extends ViewGroup implements DrawerLayoutImpl {
88    private static final String TAG = "DrawerLayout";
89
90    @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
91    @Retention(RetentionPolicy.SOURCE)
92    private @interface State {}
93
94    /**
95     * Indicates that any drawers are in an idle, settled state. No animation is in progress.
96     */
97    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
98
99    /**
100     * Indicates that a drawer is currently being dragged by the user.
101     */
102    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
103
104    /**
105     * Indicates that a drawer is in the process of settling to a final position.
106     */
107    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
108
109    /** @hide */
110    @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN})
111    @Retention(RetentionPolicy.SOURCE)
112    private @interface LockMode {}
113
114    /**
115     * The drawer is unlocked.
116     */
117    public static final int LOCK_MODE_UNLOCKED = 0;
118
119    /**
120     * The drawer is locked closed. The user may not open it, though
121     * the app may open it programmatically.
122     */
123    public static final int LOCK_MODE_LOCKED_CLOSED = 1;
124
125    /**
126     * The drawer is locked open. The user may not close it, though the app
127     * may close it programmatically.
128     */
129    public static final int LOCK_MODE_LOCKED_OPEN = 2;
130
131    /** @hide */
132    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
133    @Retention(RetentionPolicy.SOURCE)
134    private @interface EdgeGravity {}
135
136
137    private static final int MIN_DRAWER_MARGIN = 64; // dp
138
139    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
140
141    /**
142     * Length of time to delay before peeking the drawer.
143     */
144    private static final int PEEK_DELAY = 160; // ms
145
146    /**
147     * Minimum velocity that will be detected as a fling
148     */
149    private static final int MIN_FLING_VELOCITY = 400; // dips per second
150
151    /**
152     * Experimental feature.
153     */
154    private static final boolean ALLOW_EDGE_LOCK = false;
155
156    private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
157
158    private static final float TOUCH_SLOP_SENSITIVITY = 1.f;
159
160    private static final int[] LAYOUT_ATTRS = new int[] {
161            android.R.attr.layout_gravity
162    };
163
164    /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */
165    private static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19;
166
167    private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
168            new ChildAccessibilityDelegate();
169
170    private int mMinDrawerMargin;
171
172    private int mScrimColor = DEFAULT_SCRIM_COLOR;
173    private float mScrimOpacity;
174    private Paint mScrimPaint = new Paint();
175
176    private final ViewDragHelper mLeftDragger;
177    private final ViewDragHelper mRightDragger;
178    private final ViewDragCallback mLeftCallback;
179    private final ViewDragCallback mRightCallback;
180    private int mDrawerState;
181    private boolean mInLayout;
182    private boolean mFirstLayout = true;
183    private int mLockModeLeft;
184    private int mLockModeRight;
185    private boolean mDisallowInterceptRequested;
186    private boolean mChildrenCanceledTouch;
187
188    private DrawerListener mListener;
189
190    private float mInitialMotionX;
191    private float mInitialMotionY;
192
193    private Drawable mShadowLeft;
194    private Drawable mShadowRight;
195    private Drawable mStatusBarBackground;
196
197    private CharSequence mTitleLeft;
198    private CharSequence mTitleRight;
199
200    private Object mLastInsets;
201    private boolean mDrawStatusBarBackground;
202
203    /**
204     * Listener for monitoring events about drawers.
205     */
206    public interface DrawerListener {
207        /**
208         * Called when a drawer's position changes.
209         * @param drawerView The child view that was moved
210         * @param slideOffset The new offset of this drawer within its range, from 0-1
211         */
212        public void onDrawerSlide(View drawerView, float slideOffset);
213
214        /**
215         * Called when a drawer has settled in a completely open state.
216         * The drawer is interactive at this point.
217         *
218         * @param drawerView Drawer view that is now open
219         */
220        public void onDrawerOpened(View drawerView);
221
222        /**
223         * Called when a drawer has settled in a completely closed state.
224         *
225         * @param drawerView Drawer view that is now closed
226         */
227        public void onDrawerClosed(View drawerView);
228
229        /**
230         * Called when the drawer motion state changes. The new state will
231         * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
232         *
233         * @param newState The new drawer motion state
234         */
235        public void onDrawerStateChanged(@State int newState);
236    }
237
238    /**
239     * Stub/no-op implementations of all methods of {@link DrawerListener}.
240     * Override this if you only care about a few of the available callback methods.
241     */
242    public static abstract class SimpleDrawerListener implements DrawerListener {
243        @Override
244        public void onDrawerSlide(View drawerView, float slideOffset) {
245        }
246
247        @Override
248        public void onDrawerOpened(View drawerView) {
249        }
250
251        @Override
252        public void onDrawerClosed(View drawerView) {
253        }
254
255        @Override
256        public void onDrawerStateChanged(int newState) {
257        }
258    }
259
260    interface DrawerLayoutCompatImpl {
261        void configureApplyInsets(View drawerLayout);
262        void dispatchChildInsets(View child, Object insets, int drawerGravity);
263        void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity);
264        int getTopInset(Object lastInsets);
265        Drawable getDefaultStatusBarBackground(Context context);
266    }
267
268    static class DrawerLayoutCompatImplBase implements DrawerLayoutCompatImpl {
269        public void configureApplyInsets(View drawerLayout) {
270            // This space for rent
271        }
272
273        public void dispatchChildInsets(View child, Object insets, int drawerGravity) {
274            // This space for rent
275        }
276
277        public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) {
278            // This space for rent
279        }
280
281        public int getTopInset(Object insets) {
282            return 0;
283        }
284
285        @Override
286        public Drawable getDefaultStatusBarBackground(Context context) {
287            return null;
288        }
289    }
290
291    static class DrawerLayoutCompatImplApi21 implements DrawerLayoutCompatImpl {
292        public void configureApplyInsets(View drawerLayout) {
293            DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout);
294        }
295
296        public void dispatchChildInsets(View child, Object insets, int drawerGravity) {
297            DrawerLayoutCompatApi21.dispatchChildInsets(child, insets, drawerGravity);
298        }
299
300        public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) {
301            DrawerLayoutCompatApi21.applyMarginInsets(lp, insets, drawerGravity);
302        }
303
304        public int getTopInset(Object insets) {
305            return DrawerLayoutCompatApi21.getTopInset(insets);
306        }
307
308        @Override
309        public Drawable getDefaultStatusBarBackground(Context context) {
310            return DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context);
311        }
312    }
313
314    static {
315        final int version = Build.VERSION.SDK_INT;
316        if (version >= 21) {
317            IMPL = new DrawerLayoutCompatImplApi21();
318        } else {
319            IMPL = new DrawerLayoutCompatImplBase();
320        }
321    }
322
323    static final DrawerLayoutCompatImpl IMPL;
324
325    public DrawerLayout(Context context) {
326        this(context, null);
327    }
328
329    public DrawerLayout(Context context, AttributeSet attrs) {
330        this(context, attrs, 0);
331    }
332
333    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
334        super(context, attrs, defStyle);
335        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
336        final float density = getResources().getDisplayMetrics().density;
337        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
338        final float minVel = MIN_FLING_VELOCITY * density;
339
340        mLeftCallback = new ViewDragCallback(Gravity.LEFT);
341        mRightCallback = new ViewDragCallback(Gravity.RIGHT);
342
343        mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
344        mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
345        mLeftDragger.setMinVelocity(minVel);
346        mLeftCallback.setDragger(mLeftDragger);
347
348        mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
349        mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
350        mRightDragger.setMinVelocity(minVel);
351        mRightCallback.setDragger(mRightDragger);
352
353        // So that we can catch the back button
354        setFocusableInTouchMode(true);
355
356        ViewCompat.setImportantForAccessibility(this,
357                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
358
359        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
360        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
361        if (ViewCompat.getFitsSystemWindows(this)) {
362            IMPL.configureApplyInsets(this);
363            mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context);
364        }
365    }
366
367    /**
368     * @hide Internal use only; called to apply window insets when configured
369     * with fitsSystemWindows="true"
370     */
371    @Override
372    public void setChildInsets(Object insets, boolean draw) {
373        mLastInsets = insets;
374        mDrawStatusBarBackground = draw;
375        setWillNotDraw(!draw && getBackground() == null);
376        requestLayout();
377    }
378
379    /**
380     * Set a simple drawable used for the left or right shadow.
381     * The drawable provided must have a nonzero intrinsic width.
382     *
383     * @param shadowDrawable Shadow drawable to use at the edge of a drawer
384     * @param gravity Which drawer the shadow should apply to
385     */
386    public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) {
387        /*
388         * TODO Someone someday might want to set more complex drawables here.
389         * They're probably nuts, but we might want to consider registering callbacks,
390         * setting states, etc. properly.
391         */
392
393        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
394                ViewCompat.getLayoutDirection(this));
395        if ((absGravity & Gravity.LEFT) == Gravity.LEFT) {
396            mShadowLeft = shadowDrawable;
397            invalidate();
398        }
399        if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) {
400            mShadowRight = shadowDrawable;
401            invalidate();
402        }
403    }
404
405    /**
406     * Set a simple drawable used for the left or right shadow.
407     * The drawable provided must have a nonzero intrinsic width.
408     *
409     * @param resId Resource id of a shadow drawable to use at the edge of a drawer
410     * @param gravity Which drawer the shadow should apply to
411     */
412    public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) {
413        setDrawerShadow(getResources().getDrawable(resId), gravity);
414    }
415
416    /**
417     * Set a color to use for the scrim that obscures primary content while a drawer is open.
418     *
419     * @param color Color to use in 0xAARRGGBB format.
420     */
421    public void setScrimColor(@ColorInt int color) {
422        mScrimColor = color;
423        invalidate();
424    }
425
426    /**
427     * Set a listener to be notified of drawer events.
428     *
429     * @param listener Listener to notify when drawer events occur
430     * @see DrawerListener
431     */
432    public void setDrawerListener(DrawerListener listener) {
433        mListener = listener;
434    }
435
436    /**
437     * Enable or disable interaction with all drawers.
438     *
439     * <p>This allows the application to restrict the user's ability to open or close
440     * any drawer within this layout. DrawerLayout will still respond to calls to
441     * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
442     *
443     * <p>Locking drawers open or closed will implicitly open or close
444     * any drawers as appropriate.</p>
445     *
446     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
447     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
448     */
449    public void setDrawerLockMode(@LockMode int lockMode) {
450        setDrawerLockMode(lockMode, Gravity.LEFT);
451        setDrawerLockMode(lockMode, Gravity.RIGHT);
452    }
453
454    /**
455     * Enable or disable interaction with the given drawer.
456     *
457     * <p>This allows the application to restrict the user's ability to open or close
458     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
459     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
460     *
461     * <p>Locking a drawer open or closed will implicitly open or close
462     * that drawer as appropriate.</p>
463     *
464     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
465     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
466     * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
467     *                    Expresses which drawer to change the mode for.
468     *
469     * @see #LOCK_MODE_UNLOCKED
470     * @see #LOCK_MODE_LOCKED_CLOSED
471     * @see #LOCK_MODE_LOCKED_OPEN
472     */
473    public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
474        final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
475                ViewCompat.getLayoutDirection(this));
476        if (absGravity == Gravity.LEFT) {
477            mLockModeLeft = lockMode;
478        } else if (absGravity == Gravity.RIGHT) {
479            mLockModeRight = lockMode;
480        }
481        if (lockMode != LOCK_MODE_UNLOCKED) {
482            // Cancel interaction in progress
483            final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
484            helper.cancel();
485        }
486        switch (lockMode) {
487            case LOCK_MODE_LOCKED_OPEN:
488                final View toOpen = findDrawerWithGravity(absGravity);
489                if (toOpen != null) {
490                    openDrawer(toOpen);
491                }
492                break;
493            case LOCK_MODE_LOCKED_CLOSED:
494                final View toClose = findDrawerWithGravity(absGravity);
495                if (toClose != null) {
496                    closeDrawer(toClose);
497                }
498                break;
499            // default: do nothing
500        }
501    }
502
503    /**
504     * Enable or disable interaction with the given drawer.
505     *
506     * <p>This allows the application to restrict the user's ability to open or close
507     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
508     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
509     *
510     * <p>Locking a drawer open or closed will implicitly open or close
511     * that drawer as appropriate.</p>
512     *
513     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
514     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
515     * @param drawerView The drawer view to change the lock mode for
516     *
517     * @see #LOCK_MODE_UNLOCKED
518     * @see #LOCK_MODE_LOCKED_CLOSED
519     * @see #LOCK_MODE_LOCKED_OPEN
520     */
521    public void setDrawerLockMode(@LockMode int lockMode, View drawerView) {
522        if (!isDrawerView(drawerView)) {
523            throw new IllegalArgumentException("View " + drawerView + " is not a " +
524                    "drawer with appropriate layout_gravity");
525        }
526        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
527        setDrawerLockMode(lockMode, gravity);
528    }
529
530    /**
531     * Check the lock mode of the drawer with the given gravity.
532     *
533     * @param edgeGravity Gravity of the drawer to check
534     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
535     *         {@link #LOCK_MODE_LOCKED_OPEN}.
536     */
537    @LockMode
538    public int getDrawerLockMode(@EdgeGravity int edgeGravity) {
539        final int absGravity = GravityCompat.getAbsoluteGravity(
540                edgeGravity, ViewCompat.getLayoutDirection(this));
541        if (absGravity == Gravity.LEFT) {
542            return mLockModeLeft;
543        } else if (absGravity == Gravity.RIGHT) {
544            return mLockModeRight;
545        }
546        return LOCK_MODE_UNLOCKED;
547    }
548
549    /**
550     * Check the lock mode of the given drawer view.
551     *
552     * @param drawerView Drawer view to check lock mode
553     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
554     *         {@link #LOCK_MODE_LOCKED_OPEN}.
555     */
556    @LockMode
557    public int getDrawerLockMode(View drawerView) {
558        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
559        if (absGravity == Gravity.LEFT) {
560            return mLockModeLeft;
561        } else if (absGravity == Gravity.RIGHT) {
562            return mLockModeRight;
563        }
564        return LOCK_MODE_UNLOCKED;
565    }
566
567    /**
568     * Sets the title of the drawer with the given gravity.
569     * <p>
570     * When accessibility is turned on, this is the title that will be used to
571     * identify the drawer to the active accessibility service.
572     *
573     * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
574     *            drawer to set the title for.
575     * @param title The title for the drawer.
576     */
577    public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) {
578        final int absGravity = GravityCompat.getAbsoluteGravity(
579                edgeGravity, ViewCompat.getLayoutDirection(this));
580        if (absGravity == Gravity.LEFT) {
581            mTitleLeft = title;
582        } else if (absGravity == Gravity.RIGHT) {
583            mTitleRight = title;
584        }
585    }
586
587    /**
588     * Returns the title of the drawer with the given gravity.
589     *
590     * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
591     *            drawer to return the title for.
592     * @return The title of the drawer, or null if none set.
593     * @see #setDrawerTitle(int, CharSequence)
594     */
595    @Nullable
596    public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) {
597        final int absGravity = GravityCompat.getAbsoluteGravity(
598                edgeGravity, ViewCompat.getLayoutDirection(this));
599        if (absGravity == Gravity.LEFT) {
600            return mTitleLeft;
601        } else if (absGravity == Gravity.RIGHT) {
602            return mTitleRight;
603        }
604        return null;
605    }
606
607    /**
608     * Resolve the shared state of all drawers from the component ViewDragHelpers.
609     * Should be called whenever a ViewDragHelper's state changes.
610     */
611    void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) {
612        final int leftState = mLeftDragger.getViewDragState();
613        final int rightState = mRightDragger.getViewDragState();
614
615        final int state;
616        if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
617            state = STATE_DRAGGING;
618        } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
619            state = STATE_SETTLING;
620        } else {
621            state = STATE_IDLE;
622        }
623
624        if (activeDrawer != null && activeState == STATE_IDLE) {
625            final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
626            if (lp.onScreen == 0) {
627                dispatchOnDrawerClosed(activeDrawer);
628            } else if (lp.onScreen == 1) {
629                dispatchOnDrawerOpened(activeDrawer);
630            }
631        }
632
633        if (state != mDrawerState) {
634            mDrawerState = state;
635
636            if (mListener != null) {
637                mListener.onDrawerStateChanged(state);
638            }
639        }
640    }
641
642    void dispatchOnDrawerClosed(View drawerView) {
643        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
644        if (lp.knownOpen) {
645            lp.knownOpen = false;
646            if (mListener != null) {
647                mListener.onDrawerClosed(drawerView);
648            }
649
650            updateChildrenImportantForAccessibility(drawerView, false);
651
652            // Only send WINDOW_STATE_CHANGE if the host has window focus. This
653            // may change if support for multiple foreground windows (e.g. IME)
654            // improves.
655            if (hasWindowFocus()) {
656                final View rootView = getRootView();
657                if (rootView != null) {
658                    rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
659                }
660            }
661        }
662    }
663
664    void dispatchOnDrawerOpened(View drawerView) {
665        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
666        if (!lp.knownOpen) {
667            lp.knownOpen = true;
668            if (mListener != null) {
669                mListener.onDrawerOpened(drawerView);
670            }
671
672            updateChildrenImportantForAccessibility(drawerView, true);
673
674            // Only send WINDOW_STATE_CHANGE if the host has window focus.
675            if (hasWindowFocus()) {
676                sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
677            }
678
679            drawerView.requestFocus();
680        }
681    }
682
683    private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) {
684        final int childCount = getChildCount();
685        for (int i = 0; i < childCount; i++) {
686            final View child = getChildAt(i);
687            if (!isDrawerOpen && !isDrawerView(child)
688                    || isDrawerOpen && child == drawerView) {
689                // Drawer is closed and this is a content view or this is an
690                // open drawer view, so it should be visible.
691                ViewCompat.setImportantForAccessibility(child,
692                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
693            } else {
694                ViewCompat.setImportantForAccessibility(child,
695                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
696            }
697        }
698    }
699
700    void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
701        if (mListener != null) {
702            mListener.onDrawerSlide(drawerView, slideOffset);
703        }
704    }
705
706    void setDrawerViewOffset(View drawerView, float slideOffset) {
707        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
708        if (slideOffset == lp.onScreen) {
709            return;
710        }
711
712        lp.onScreen = slideOffset;
713        dispatchOnDrawerSlide(drawerView, slideOffset);
714    }
715
716    float getDrawerViewOffset(View drawerView) {
717        return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
718    }
719
720    /**
721     * @return the absolute gravity of the child drawerView, resolved according
722     *         to the current layout direction
723     */
724    int getDrawerViewAbsoluteGravity(View drawerView) {
725        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
726        return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
727    }
728
729    boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
730        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
731        return (absGravity & checkFor) == checkFor;
732    }
733
734    View findOpenDrawer() {
735        final int childCount = getChildCount();
736        for (int i = 0; i < childCount; i++) {
737            final View child = getChildAt(i);
738            if (((LayoutParams) child.getLayoutParams()).knownOpen) {
739                return child;
740            }
741        }
742        return null;
743    }
744
745    void moveDrawerToOffset(View drawerView, float slideOffset) {
746        final float oldOffset = getDrawerViewOffset(drawerView);
747        final int width = drawerView.getWidth();
748        final int oldPos = (int) (width * oldOffset);
749        final int newPos = (int) (width * slideOffset);
750        final int dx = newPos - oldPos;
751
752        drawerView.offsetLeftAndRight(
753                checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
754        setDrawerViewOffset(drawerView, slideOffset);
755    }
756
757    /**
758     * @param gravity the gravity of the child to return. If specified as a
759     *            relative value, it will be resolved according to the current
760     *            layout direction.
761     * @return the drawer with the specified gravity
762     */
763    View findDrawerWithGravity(int gravity) {
764        final int absHorizGravity = GravityCompat.getAbsoluteGravity(
765                gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
766        final int childCount = getChildCount();
767        for (int i = 0; i < childCount; i++) {
768            final View child = getChildAt(i);
769            final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
770            if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
771                return child;
772            }
773        }
774        return null;
775    }
776
777    /**
778     * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
779     *
780     * @param gravity Absolute gravity value
781     * @return LEFT or RIGHT as appropriate, or a hex string
782     */
783    static String gravityToString(@EdgeGravity int gravity) {
784        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
785            return "LEFT";
786        }
787        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
788            return "RIGHT";
789        }
790        return Integer.toHexString(gravity);
791    }
792
793    @Override
794    protected void onDetachedFromWindow() {
795        super.onDetachedFromWindow();
796        mFirstLayout = true;
797    }
798
799    @Override
800    protected void onAttachedToWindow() {
801        super.onAttachedToWindow();
802        mFirstLayout = true;
803    }
804
805    @Override
806    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
807        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
808        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
809        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
810        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
811
812        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
813            if (isInEditMode()) {
814                // Don't crash the layout editor. Consume all of the space if specified
815                // or pick a magic number from thin air otherwise.
816                // TODO Better communication with tools of this bogus state.
817                // It will crash on a real device.
818                if (widthMode == MeasureSpec.AT_MOST) {
819                    widthMode = MeasureSpec.EXACTLY;
820                } else if (widthMode == MeasureSpec.UNSPECIFIED) {
821                    widthMode = MeasureSpec.EXACTLY;
822                    widthSize = 300;
823                }
824                if (heightMode == MeasureSpec.AT_MOST) {
825                    heightMode = MeasureSpec.EXACTLY;
826                }
827                else if (heightMode == MeasureSpec.UNSPECIFIED) {
828                    heightMode = MeasureSpec.EXACTLY;
829                    heightSize = 300;
830                }
831            } else {
832                throw new IllegalArgumentException(
833                        "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
834            }
835        }
836
837        setMeasuredDimension(widthSize, heightSize);
838
839        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
840        final int layoutDirection = ViewCompat.getLayoutDirection(this);
841
842        // Gravity value for each drawer we've seen. Only one of each permitted.
843        int foundDrawers = 0;
844        final int childCount = getChildCount();
845        for (int i = 0; i < childCount; i++) {
846            final View child = getChildAt(i);
847
848            if (child.getVisibility() == GONE) {
849                continue;
850            }
851
852            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
853
854            if (applyInsets) {
855                final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
856                if (ViewCompat.getFitsSystemWindows(child)) {
857                    IMPL.dispatchChildInsets(child, mLastInsets, cgrav);
858                } else {
859                    IMPL.applyMarginInsets(lp, mLastInsets, cgrav);
860                }
861            }
862
863            if (isContentView(child)) {
864                // Content views get measured at exactly the layout's size.
865                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
866                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
867                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
868                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
869                child.measure(contentWidthSpec, contentHeightSpec);
870            } else if (isDrawerView(child)) {
871                final int childGravity =
872                        getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
873                if ((foundDrawers & childGravity) != 0) {
874                    throw new IllegalStateException("Child drawer has absolute gravity " +
875                            gravityToString(childGravity) + " but this " + TAG + " already has a " +
876                            "drawer view along that edge");
877                }
878                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
879                        mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
880                        lp.width);
881                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
882                        lp.topMargin + lp.bottomMargin,
883                        lp.height);
884                child.measure(drawerWidthSpec, drawerHeightSpec);
885            } else {
886                throw new IllegalStateException("Child " + child + " at index " + i +
887                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
888                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
889            }
890        }
891    }
892
893    @Override
894    protected void onLayout(boolean changed, int l, int t, int r, int b) {
895        mInLayout = true;
896        final int width = r - l;
897        final int childCount = getChildCount();
898        for (int i = 0; i < childCount; i++) {
899            final View child = getChildAt(i);
900
901            if (child.getVisibility() == GONE) {
902                continue;
903            }
904
905            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
906
907            if (isContentView(child)) {
908                child.layout(lp.leftMargin, lp.topMargin,
909                        lp.leftMargin + child.getMeasuredWidth(),
910                        lp.topMargin + child.getMeasuredHeight());
911            } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
912                final int childWidth = child.getMeasuredWidth();
913                final int childHeight = child.getMeasuredHeight();
914                int childLeft;
915
916                final float newOffset;
917                if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
918                    childLeft = -childWidth + (int) (childWidth * lp.onScreen);
919                    newOffset = (float) (childWidth + childLeft) / childWidth;
920                } else { // Right; onMeasure checked for us.
921                    childLeft = width - (int) (childWidth * lp.onScreen);
922                    newOffset = (float) (width - childLeft) / childWidth;
923                }
924
925                final boolean changeOffset = newOffset != lp.onScreen;
926
927                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
928
929                switch (vgrav) {
930                    default:
931                    case Gravity.TOP: {
932                        child.layout(childLeft, lp.topMargin, childLeft + childWidth,
933                                lp.topMargin + childHeight);
934                        break;
935                    }
936
937                    case Gravity.BOTTOM: {
938                        final int height = b - t;
939                        child.layout(childLeft,
940                                height - lp.bottomMargin - child.getMeasuredHeight(),
941                                childLeft + childWidth,
942                                height - lp.bottomMargin);
943                        break;
944                    }
945
946                    case Gravity.CENTER_VERTICAL: {
947                        final int height = b - t;
948                        int childTop = (height - childHeight) / 2;
949
950                        // Offset for margins. If things don't fit right because of
951                        // bad measurement before, oh well.
952                        if (childTop < lp.topMargin) {
953                            childTop = lp.topMargin;
954                        } else if (childTop + childHeight > height - lp.bottomMargin) {
955                            childTop = height - lp.bottomMargin - childHeight;
956                        }
957                        child.layout(childLeft, childTop, childLeft + childWidth,
958                                childTop + childHeight);
959                        break;
960                    }
961                }
962
963                if (changeOffset) {
964                    setDrawerViewOffset(child, newOffset);
965                }
966
967                final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
968                if (child.getVisibility() != newVisibility) {
969                    child.setVisibility(newVisibility);
970                }
971            }
972        }
973        mInLayout = false;
974        mFirstLayout = false;
975    }
976
977    @Override
978    public void requestLayout() {
979        if (!mInLayout) {
980            super.requestLayout();
981        }
982    }
983
984    @Override
985    public void computeScroll() {
986        final int childCount = getChildCount();
987        float scrimOpacity = 0;
988        for (int i = 0; i < childCount; i++) {
989            final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
990            scrimOpacity = Math.max(scrimOpacity, onscreen);
991        }
992        mScrimOpacity = scrimOpacity;
993
994        // "|" used on purpose; both need to run.
995        if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
996            ViewCompat.postInvalidateOnAnimation(this);
997        }
998    }
999
1000    private static boolean hasOpaqueBackground(View v) {
1001        final Drawable bg = v.getBackground();
1002        if (bg != null) {
1003            return bg.getOpacity() == PixelFormat.OPAQUE;
1004        }
1005        return false;
1006    }
1007
1008    /**
1009     * Set a drawable to draw in the insets area for the status bar.
1010     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
1011     *
1012     * @param bg Background drawable to draw behind the status bar
1013     */
1014    public void setStatusBarBackground(Drawable bg) {
1015        mStatusBarBackground = bg;
1016        invalidate();
1017    }
1018
1019    /**
1020     * Gets the drawable used to draw in the insets area for the status bar.
1021     *
1022     * @return The status bar background drawable, or null if none set
1023     */
1024    public Drawable getStatusBarBackgroundDrawable() {
1025        return mStatusBarBackground;
1026    }
1027
1028    /**
1029     * Set a drawable to draw in the insets area for the status bar.
1030     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
1031     *
1032     * @param resId Resource id of a background drawable to draw behind the status bar
1033     */
1034    public void setStatusBarBackground(int resId) {
1035        mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null;
1036        invalidate();
1037    }
1038
1039    /**
1040     * Set a drawable to draw in the insets area for the status bar.
1041     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
1042     *
1043     * @param color Color to use as a background drawable to draw behind the status bar
1044     *              in 0xAARRGGBB format.
1045     */
1046    public void setStatusBarBackgroundColor(@ColorInt int color) {
1047        mStatusBarBackground = new ColorDrawable(color);
1048        invalidate();
1049    }
1050
1051    @Override
1052    public void onDraw(Canvas c) {
1053        super.onDraw(c);
1054        if (mDrawStatusBarBackground && mStatusBarBackground != null) {
1055            final int inset = IMPL.getTopInset(mLastInsets);
1056            if (inset > 0) {
1057                mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
1058                mStatusBarBackground.draw(c);
1059            }
1060        }
1061    }
1062
1063    @Override
1064    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1065        final int height = getHeight();
1066        final boolean drawingContent = isContentView(child);
1067        int clipLeft = 0, clipRight = getWidth();
1068
1069        final int restoreCount = canvas.save();
1070        if (drawingContent) {
1071            final int childCount = getChildCount();
1072            for (int i = 0; i < childCount; i++) {
1073                final View v = getChildAt(i);
1074                if (v == child || v.getVisibility() != VISIBLE ||
1075                        !hasOpaqueBackground(v) || !isDrawerView(v) ||
1076                        v.getHeight() < height) {
1077                    continue;
1078                }
1079
1080                if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
1081                    final int vright = v.getRight();
1082                    if (vright > clipLeft) clipLeft = vright;
1083                } else {
1084                    final int vleft = v.getLeft();
1085                    if (vleft < clipRight) clipRight = vleft;
1086                }
1087            }
1088            canvas.clipRect(clipLeft, 0, clipRight, getHeight());
1089        }
1090        final boolean result = super.drawChild(canvas, child, drawingTime);
1091        canvas.restoreToCount(restoreCount);
1092
1093        if (mScrimOpacity > 0 && drawingContent) {
1094            final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
1095            final int imag = (int) (baseAlpha * mScrimOpacity);
1096            final int color = imag << 24 | (mScrimColor & 0xffffff);
1097            mScrimPaint.setColor(color);
1098
1099            canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
1100        } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1101            final int shadowWidth = mShadowLeft.getIntrinsicWidth();
1102            final int childRight = child.getRight();
1103            final int drawerPeekDistance = mLeftDragger.getEdgeSize();
1104            final float alpha =
1105                    Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
1106            mShadowLeft.setBounds(childRight, child.getTop(),
1107                    childRight + shadowWidth, child.getBottom());
1108            mShadowLeft.setAlpha((int) (0xff * alpha));
1109            mShadowLeft.draw(canvas);
1110        } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
1111            final int shadowWidth = mShadowRight.getIntrinsicWidth();
1112            final int childLeft = child.getLeft();
1113            final int showing = getWidth() - childLeft;
1114            final int drawerPeekDistance = mRightDragger.getEdgeSize();
1115            final float alpha =
1116                    Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
1117            mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(),
1118                    childLeft, child.getBottom());
1119            mShadowRight.setAlpha((int) (0xff * alpha));
1120            mShadowRight.draw(canvas);
1121        }
1122        return result;
1123    }
1124
1125    boolean isContentView(View child) {
1126        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
1127    }
1128
1129    boolean isDrawerView(View child) {
1130        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
1131        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
1132                ViewCompat.getLayoutDirection(child));
1133        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
1134    }
1135
1136    @Override
1137    public boolean onInterceptTouchEvent(MotionEvent ev) {
1138        final int action = MotionEventCompat.getActionMasked(ev);
1139
1140        // "|" used deliberately here; both methods should be invoked.
1141        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
1142                mRightDragger.shouldInterceptTouchEvent(ev);
1143
1144        boolean interceptForTap = false;
1145
1146        switch (action) {
1147            case MotionEvent.ACTION_DOWN: {
1148                final float x = ev.getX();
1149                final float y = ev.getY();
1150                mInitialMotionX = x;
1151                mInitialMotionY = y;
1152                if (mScrimOpacity > 0) {
1153                    final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
1154                    if (child != null && isContentView(child)) {
1155                        interceptForTap = true;
1156                    }
1157                }
1158                mDisallowInterceptRequested = false;
1159                mChildrenCanceledTouch = false;
1160                break;
1161            }
1162
1163            case MotionEvent.ACTION_MOVE: {
1164                // If we cross the touch slop, don't perform the delayed peek for an edge touch.
1165                if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
1166                    mLeftCallback.removeCallbacks();
1167                    mRightCallback.removeCallbacks();
1168                }
1169                break;
1170            }
1171
1172            case MotionEvent.ACTION_CANCEL:
1173            case MotionEvent.ACTION_UP: {
1174                closeDrawers(true);
1175                mDisallowInterceptRequested = false;
1176                mChildrenCanceledTouch = false;
1177            }
1178        }
1179
1180        return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
1181    }
1182
1183    @Override
1184    public boolean onTouchEvent(MotionEvent ev) {
1185        mLeftDragger.processTouchEvent(ev);
1186        mRightDragger.processTouchEvent(ev);
1187
1188        final int action = ev.getAction();
1189        boolean wantTouchEvents = true;
1190
1191        switch (action & MotionEventCompat.ACTION_MASK) {
1192            case MotionEvent.ACTION_DOWN: {
1193                final float x = ev.getX();
1194                final float y = ev.getY();
1195                mInitialMotionX = x;
1196                mInitialMotionY = y;
1197                mDisallowInterceptRequested = false;
1198                mChildrenCanceledTouch = false;
1199                break;
1200            }
1201
1202            case MotionEvent.ACTION_UP: {
1203                final float x = ev.getX();
1204                final float y = ev.getY();
1205                boolean peekingOnly = true;
1206                final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
1207                if (touchedView != null && isContentView(touchedView)) {
1208                    final float dx = x - mInitialMotionX;
1209                    final float dy = y - mInitialMotionY;
1210                    final int slop = mLeftDragger.getTouchSlop();
1211                    if (dx * dx + dy * dy < slop * slop) {
1212                        // Taps close a dimmed open drawer but only if it isn't locked open.
1213                        final View openDrawer = findOpenDrawer();
1214                        if (openDrawer != null) {
1215                            peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
1216                        }
1217                    }
1218                }
1219                closeDrawers(peekingOnly);
1220                mDisallowInterceptRequested = false;
1221                break;
1222            }
1223
1224            case MotionEvent.ACTION_CANCEL: {
1225                closeDrawers(true);
1226                mDisallowInterceptRequested = false;
1227                mChildrenCanceledTouch = false;
1228                break;
1229            }
1230        }
1231
1232        return wantTouchEvents;
1233    }
1234
1235    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1236        if (CHILDREN_DISALLOW_INTERCEPT ||
1237                (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) &&
1238                !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
1239            // If we have an edge touch we want to skip this and track it for later instead.
1240            super.requestDisallowInterceptTouchEvent(disallowIntercept);
1241        }
1242        mDisallowInterceptRequested = disallowIntercept;
1243        if (disallowIntercept) {
1244            closeDrawers(true);
1245        }
1246    }
1247
1248    /**
1249     * Close all currently open drawer views by animating them out of view.
1250     */
1251    public void closeDrawers() {
1252        closeDrawers(false);
1253    }
1254
1255    void closeDrawers(boolean peekingOnly) {
1256        boolean needsInvalidate = false;
1257        final int childCount = getChildCount();
1258        for (int i = 0; i < childCount; i++) {
1259            final View child = getChildAt(i);
1260            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1261
1262            if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
1263                continue;
1264            }
1265
1266            final int childWidth = child.getWidth();
1267
1268            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1269                needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
1270                        -childWidth, child.getTop());
1271            } else {
1272                needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
1273                        getWidth(), child.getTop());
1274            }
1275
1276            lp.isPeeking = false;
1277        }
1278
1279        mLeftCallback.removeCallbacks();
1280        mRightCallback.removeCallbacks();
1281
1282        if (needsInvalidate) {
1283            invalidate();
1284        }
1285    }
1286
1287    /**
1288     * Open the specified drawer view by animating it into view.
1289     *
1290     * @param drawerView Drawer view to open
1291     */
1292    public void openDrawer(View drawerView) {
1293        if (!isDrawerView(drawerView)) {
1294            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1295        }
1296
1297        if (mFirstLayout) {
1298            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1299            lp.onScreen = 1.f;
1300            lp.knownOpen = true;
1301
1302            updateChildrenImportantForAccessibility(drawerView, true);
1303        } else {
1304            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1305                mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
1306            } else {
1307                mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
1308                        drawerView.getTop());
1309            }
1310        }
1311        invalidate();
1312    }
1313
1314    /**
1315     * Open the specified drawer by animating it out of view.
1316     *
1317     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1318     *                GravityCompat.START or GravityCompat.END may also be used.
1319     */
1320    public void openDrawer(@EdgeGravity int gravity) {
1321        final View drawerView = findDrawerWithGravity(gravity);
1322        if (drawerView == null) {
1323            throw new IllegalArgumentException("No drawer view found with gravity " +
1324                    gravityToString(gravity));
1325        }
1326        openDrawer(drawerView);
1327    }
1328
1329    /**
1330     * Close the specified drawer view by animating it into view.
1331     *
1332     * @param drawerView Drawer view to close
1333     */
1334    public void closeDrawer(View drawerView) {
1335        if (!isDrawerView(drawerView)) {
1336            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1337        }
1338
1339        if (mFirstLayout) {
1340            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1341            lp.onScreen = 0.f;
1342            lp.knownOpen = false;
1343        } else {
1344            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1345                mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
1346                        drawerView.getTop());
1347            } else {
1348                mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
1349            }
1350        }
1351        invalidate();
1352    }
1353
1354    /**
1355     * Close the specified drawer by animating it out of view.
1356     *
1357     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1358     *                GravityCompat.START or GravityCompat.END may also be used.
1359     */
1360    public void closeDrawer(@EdgeGravity int gravity) {
1361        final View drawerView = findDrawerWithGravity(gravity);
1362        if (drawerView == null) {
1363            throw new IllegalArgumentException("No drawer view found with gravity " +
1364                    gravityToString(gravity));
1365        }
1366        closeDrawer(drawerView);
1367    }
1368
1369    /**
1370     * Check if the given drawer view is currently in an open state.
1371     * To be considered "open" the drawer must have settled into its fully
1372     * visible state. To check for partial visibility use
1373     * {@link #isDrawerVisible(android.view.View)}.
1374     *
1375     * @param drawer Drawer view to check
1376     * @return true if the given drawer view is in an open state
1377     * @see #isDrawerVisible(android.view.View)
1378     */
1379    public boolean isDrawerOpen(View drawer) {
1380        if (!isDrawerView(drawer)) {
1381            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1382        }
1383        return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
1384    }
1385
1386    /**
1387     * Check if the given drawer view is currently in an open state.
1388     * To be considered "open" the drawer must have settled into its fully
1389     * visible state. If there is no drawer with the given gravity this method
1390     * will return false.
1391     *
1392     * @param drawerGravity Gravity of the drawer to check
1393     * @return true if the given drawer view is in an open state
1394     */
1395    public boolean isDrawerOpen(@EdgeGravity int drawerGravity) {
1396        final View drawerView = findDrawerWithGravity(drawerGravity);
1397        if (drawerView != null) {
1398            return isDrawerOpen(drawerView);
1399        }
1400        return false;
1401    }
1402
1403    /**
1404     * Check if a given drawer view is currently visible on-screen. The drawer
1405     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
1406     *
1407     * @param drawer Drawer view to check
1408     * @return true if the given drawer is visible on-screen
1409     * @see #isDrawerOpen(android.view.View)
1410     */
1411    public boolean isDrawerVisible(View drawer) {
1412        if (!isDrawerView(drawer)) {
1413            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1414        }
1415        return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
1416    }
1417
1418    /**
1419     * Check if a given drawer view is currently visible on-screen. The drawer
1420     * may be only peeking onto the screen, fully extended, or anywhere in between.
1421     * If there is no drawer with the given gravity this method will return false.
1422     *
1423     * @param drawerGravity Gravity of the drawer to check
1424     * @return true if the given drawer is visible on-screen
1425     */
1426    public boolean isDrawerVisible(@EdgeGravity int drawerGravity) {
1427        final View drawerView = findDrawerWithGravity(drawerGravity);
1428        if (drawerView != null) {
1429            return isDrawerVisible(drawerView);
1430        }
1431        return false;
1432    }
1433
1434    private boolean hasPeekingDrawer() {
1435        final int childCount = getChildCount();
1436        for (int i = 0; i < childCount; i++) {
1437            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
1438            if (lp.isPeeking) {
1439                return true;
1440            }
1441        }
1442        return false;
1443    }
1444
1445    @Override
1446    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1447        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
1448    }
1449
1450    @Override
1451    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1452        return p instanceof LayoutParams
1453                ? new LayoutParams((LayoutParams) p)
1454                : p instanceof ViewGroup.MarginLayoutParams
1455                ? new LayoutParams((MarginLayoutParams) p)
1456                : new LayoutParams(p);
1457    }
1458
1459    @Override
1460    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1461        return p instanceof LayoutParams && super.checkLayoutParams(p);
1462    }
1463
1464    @Override
1465    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1466        return new LayoutParams(getContext(), attrs);
1467    }
1468
1469    private boolean hasVisibleDrawer() {
1470        return findVisibleDrawer() != null;
1471    }
1472
1473    private View findVisibleDrawer() {
1474        final int childCount = getChildCount();
1475        for (int i = 0; i < childCount; i++) {
1476            final View child = getChildAt(i);
1477            if (isDrawerView(child) && isDrawerVisible(child)) {
1478                return child;
1479            }
1480        }
1481        return null;
1482    }
1483
1484    void cancelChildViewTouch() {
1485        // Cancel child touches
1486        if (!mChildrenCanceledTouch) {
1487            final long now = SystemClock.uptimeMillis();
1488            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
1489                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1490            final int childCount = getChildCount();
1491            for (int i = 0; i < childCount; i++) {
1492                getChildAt(i).dispatchTouchEvent(cancelEvent);
1493            }
1494            cancelEvent.recycle();
1495            mChildrenCanceledTouch = true;
1496        }
1497    }
1498
1499    @Override
1500    public boolean onKeyDown(int keyCode, KeyEvent event) {
1501        if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
1502            KeyEventCompat.startTracking(event);
1503            return true;
1504        }
1505        return super.onKeyDown(keyCode, event);
1506    }
1507
1508    @Override
1509    public boolean onKeyUp(int keyCode, KeyEvent event) {
1510        if (keyCode == KeyEvent.KEYCODE_BACK) {
1511            final View visibleDrawer = findVisibleDrawer();
1512            if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
1513                closeDrawers();
1514            }
1515            return visibleDrawer != null;
1516        }
1517        return super.onKeyUp(keyCode, event);
1518    }
1519
1520    @Override
1521    protected void onRestoreInstanceState(Parcelable state) {
1522        final SavedState ss = (SavedState) state;
1523        super.onRestoreInstanceState(ss.getSuperState());
1524
1525        if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
1526            final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
1527            if (toOpen != null) {
1528                openDrawer(toOpen);
1529            }
1530        }
1531
1532        setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
1533        setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
1534    }
1535
1536    @Override
1537    protected Parcelable onSaveInstanceState() {
1538        final Parcelable superState = super.onSaveInstanceState();
1539        final SavedState ss = new SavedState(superState);
1540
1541        final View openDrawer = findOpenDrawer();
1542        if (openDrawer != null) {
1543            ss.openDrawerGravity = ((LayoutParams) openDrawer.getLayoutParams()).gravity;
1544        }
1545
1546        ss.lockModeLeft = mLockModeLeft;
1547        ss.lockModeRight = mLockModeRight;
1548
1549        return ss;
1550    }
1551
1552    @Override
1553    public void addView(View child, int index, ViewGroup.LayoutParams params) {
1554        super.addView(child, index, params);
1555
1556        final View openDrawer = findOpenDrawer();
1557        if (openDrawer != null || isDrawerView(child)) {
1558            // A drawer is already open or the new view is a drawer, so the
1559            // new view should start out hidden.
1560            ViewCompat.setImportantForAccessibility(child,
1561                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
1562        } else {
1563            // Otherwise this is a content view and no drawer is open, so the
1564            // new view should start out visible.
1565            ViewCompat.setImportantForAccessibility(child,
1566                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1567        }
1568
1569        // We only need a delegate here if the framework doesn't understand
1570        // NO_HIDE_DESCENDANTS importance.
1571        if (!CAN_HIDE_DESCENDANTS) {
1572            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
1573        }
1574    }
1575
1576    private static boolean includeChildForAccessibility(View child) {
1577        // If the child is not important for accessibility we make
1578        // sure this hides the entire subtree rooted at it as the
1579        // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not
1580        // supported on older platforms but we want to hide the entire
1581        // content and not opened drawers if a drawer is opened.
1582        return ViewCompat.getImportantForAccessibility(child)
1583                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
1584                    && ViewCompat.getImportantForAccessibility(child)
1585                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
1586    }
1587
1588    /**
1589     * State persisted across instances
1590     */
1591    protected static class SavedState extends BaseSavedState {
1592        int openDrawerGravity = Gravity.NO_GRAVITY;
1593        int lockModeLeft = LOCK_MODE_UNLOCKED;
1594        int lockModeRight = LOCK_MODE_UNLOCKED;
1595
1596        public SavedState(Parcel in) {
1597            super(in);
1598            openDrawerGravity = in.readInt();
1599        }
1600
1601        public SavedState(Parcelable superState) {
1602            super(superState);
1603        }
1604
1605        @Override
1606        public void writeToParcel(Parcel dest, int flags) {
1607            super.writeToParcel(dest, flags);
1608            dest.writeInt(openDrawerGravity);
1609        }
1610
1611        public static final Parcelable.Creator<SavedState> CREATOR =
1612                new Parcelable.Creator<SavedState>() {
1613            @Override
1614            public SavedState createFromParcel(Parcel source) {
1615                return new SavedState(source);
1616            }
1617
1618            @Override
1619            public SavedState[] newArray(int size) {
1620                return new SavedState[size];
1621            }
1622        };
1623    }
1624
1625    private class ViewDragCallback extends ViewDragHelper.Callback {
1626        private final int mAbsGravity;
1627        private ViewDragHelper mDragger;
1628
1629        private final Runnable mPeekRunnable = new Runnable() {
1630            @Override public void run() {
1631                peekDrawer();
1632            }
1633        };
1634
1635        public ViewDragCallback(int gravity) {
1636            mAbsGravity = gravity;
1637        }
1638
1639        public void setDragger(ViewDragHelper dragger) {
1640            mDragger = dragger;
1641        }
1642
1643        public void removeCallbacks() {
1644            DrawerLayout.this.removeCallbacks(mPeekRunnable);
1645        }
1646
1647        @Override
1648        public boolean tryCaptureView(View child, int pointerId) {
1649            // Only capture views where the gravity matches what we're looking for.
1650            // This lets us use two ViewDragHelpers, one for each side drawer.
1651            return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
1652                    && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
1653        }
1654
1655        @Override
1656        public void onViewDragStateChanged(int state) {
1657            updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
1658        }
1659
1660        @Override
1661        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1662            float offset;
1663            final int childWidth = changedView.getWidth();
1664
1665            // This reverses the positioning shown in onLayout.
1666            if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
1667                offset = (float) (childWidth + left) / childWidth;
1668            } else {
1669                final int width = getWidth();
1670                offset = (float) (width - left) / childWidth;
1671            }
1672            setDrawerViewOffset(changedView, offset);
1673            changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
1674            invalidate();
1675        }
1676
1677        @Override
1678        public void onViewCaptured(View capturedChild, int activePointerId) {
1679            final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
1680            lp.isPeeking = false;
1681
1682            closeOtherDrawer();
1683        }
1684
1685        private void closeOtherDrawer() {
1686            final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
1687            final View toClose = findDrawerWithGravity(otherGrav);
1688            if (toClose != null) {
1689                closeDrawer(toClose);
1690            }
1691        }
1692
1693        @Override
1694        public void onViewReleased(View releasedChild, float xvel, float yvel) {
1695            // Offset is how open the drawer is, therefore left/right values
1696            // are reversed from one another.
1697            final float offset = getDrawerViewOffset(releasedChild);
1698            final int childWidth = releasedChild.getWidth();
1699
1700            int left;
1701            if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
1702                left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
1703            } else {
1704                final int width = getWidth();
1705                left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width;
1706            }
1707
1708            mDragger.settleCapturedViewAt(left, releasedChild.getTop());
1709            invalidate();
1710        }
1711
1712        @Override
1713        public void onEdgeTouched(int edgeFlags, int pointerId) {
1714            postDelayed(mPeekRunnable, PEEK_DELAY);
1715        }
1716
1717        private void peekDrawer() {
1718            final View toCapture;
1719            final int childLeft;
1720            final int peekDistance = mDragger.getEdgeSize();
1721            final boolean leftEdge = mAbsGravity == Gravity.LEFT;
1722            if (leftEdge) {
1723                toCapture = findDrawerWithGravity(Gravity.LEFT);
1724                childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
1725            } else {
1726                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1727                childLeft = getWidth() - peekDistance;
1728            }
1729            // Only peek if it would mean making the drawer more visible and the drawer isn't locked
1730            if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) ||
1731                    (!leftEdge && toCapture.getLeft() > childLeft)) &&
1732                    getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1733                final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
1734                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
1735                lp.isPeeking = true;
1736                invalidate();
1737
1738                closeOtherDrawer();
1739
1740                cancelChildViewTouch();
1741            }
1742        }
1743
1744        @Override
1745        public boolean onEdgeLock(int edgeFlags) {
1746            if (ALLOW_EDGE_LOCK) {
1747                final View drawer = findDrawerWithGravity(mAbsGravity);
1748                if (drawer != null && !isDrawerOpen(drawer)) {
1749                    closeDrawer(drawer);
1750                }
1751                return true;
1752            }
1753            return false;
1754        }
1755
1756        @Override
1757        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1758            final View toCapture;
1759            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
1760                toCapture = findDrawerWithGravity(Gravity.LEFT);
1761            } else {
1762                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1763            }
1764
1765            if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1766                mDragger.captureChildView(toCapture, pointerId);
1767            }
1768        }
1769
1770        @Override
1771        public int getViewHorizontalDragRange(View child) {
1772            return isDrawerView(child) ? child.getWidth() : 0;
1773        }
1774
1775        @Override
1776        public int clampViewPositionHorizontal(View child, int left, int dx) {
1777            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1778                return Math.max(-child.getWidth(), Math.min(left, 0));
1779            } else {
1780                final int width = getWidth();
1781                return Math.max(width - child.getWidth(), Math.min(left, width));
1782            }
1783        }
1784
1785        @Override
1786        public int clampViewPositionVertical(View child, int top, int dy) {
1787            return child.getTop();
1788        }
1789    }
1790
1791    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1792
1793        public int gravity = Gravity.NO_GRAVITY;
1794        float onScreen;
1795        boolean isPeeking;
1796        boolean knownOpen;
1797
1798        public LayoutParams(Context c, AttributeSet attrs) {
1799            super(c, attrs);
1800
1801            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
1802            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
1803            a.recycle();
1804        }
1805
1806        public LayoutParams(int width, int height) {
1807            super(width, height);
1808        }
1809
1810        public LayoutParams(int width, int height, int gravity) {
1811            this(width, height);
1812            this.gravity = gravity;
1813        }
1814
1815        public LayoutParams(LayoutParams source) {
1816            super(source);
1817            this.gravity = source.gravity;
1818        }
1819
1820        public LayoutParams(ViewGroup.LayoutParams source) {
1821            super(source);
1822        }
1823
1824        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1825            super(source);
1826        }
1827    }
1828
1829    class AccessibilityDelegate extends AccessibilityDelegateCompat {
1830        private final Rect mTmpRect = new Rect();
1831
1832        @Override
1833        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1834            if (CAN_HIDE_DESCENDANTS) {
1835                super.onInitializeAccessibilityNodeInfo(host, info);
1836            } else {
1837                // Obtain a node for the host, then manually generate the list
1838                // of children to only include non-obscured views.
1839                final AccessibilityNodeInfoCompat superNode =
1840                        AccessibilityNodeInfoCompat.obtain(info);
1841                super.onInitializeAccessibilityNodeInfo(host, superNode);
1842
1843                info.setSource(host);
1844                final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1845                if (parent instanceof View) {
1846                    info.setParent((View) parent);
1847                }
1848                copyNodeInfoNoChildren(info, superNode);
1849                superNode.recycle();
1850
1851                addChildrenForAccessibility(info, (ViewGroup) host);
1852            }
1853
1854            info.setClassName(DrawerLayout.class.getName());
1855
1856            // This view reports itself as focusable so that it can intercept
1857            // the back button, but we should prevent this view from reporting
1858            // itself as focusable to accessibility services.
1859            info.setFocusable(false);
1860            info.setFocused(false);
1861        }
1862
1863        @Override
1864        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1865            super.onInitializeAccessibilityEvent(host, event);
1866
1867            event.setClassName(DrawerLayout.class.getName());
1868        }
1869
1870        @Override
1871        public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
1872            // Special case to handle window state change events. As far as
1873            // accessibility services are concerned, state changes from
1874            // DrawerLayout invalidate the entire contents of the screen (like
1875            // an Activity or Dialog) and they should announce the title of the
1876            // new content.
1877            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1878                final List<CharSequence> eventText = event.getText();
1879                final View visibleDrawer = findVisibleDrawer();
1880                if (visibleDrawer != null) {
1881                    final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer);
1882                    final CharSequence title = getDrawerTitle(edgeGravity);
1883                    if (title != null) {
1884                        eventText.add(title);
1885                    }
1886                }
1887
1888                return true;
1889            }
1890
1891            return super.dispatchPopulateAccessibilityEvent(host, event);
1892        }
1893
1894        @Override
1895        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1896                AccessibilityEvent event) {
1897            if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
1898                return super.onRequestSendAccessibilityEvent(host, child, event);
1899            }
1900            return false;
1901        }
1902
1903        private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
1904            final int childCount = v.getChildCount();
1905            for (int i = 0; i < childCount; i++) {
1906                final View child = v.getChildAt(i);
1907                if (includeChildForAccessibility(child)) {
1908                    info.addChild(child);
1909                }
1910            }
1911        }
1912
1913        /**
1914         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1915         * seem to be a few elements that are not easily cloneable using the underlying API.
1916         * Leave it private here as it's not general-purpose useful.
1917         */
1918        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1919                AccessibilityNodeInfoCompat src) {
1920            final Rect rect = mTmpRect;
1921
1922            src.getBoundsInParent(rect);
1923            dest.setBoundsInParent(rect);
1924
1925            src.getBoundsInScreen(rect);
1926            dest.setBoundsInScreen(rect);
1927
1928            dest.setVisibleToUser(src.isVisibleToUser());
1929            dest.setPackageName(src.getPackageName());
1930            dest.setClassName(src.getClassName());
1931            dest.setContentDescription(src.getContentDescription());
1932
1933            dest.setEnabled(src.isEnabled());
1934            dest.setClickable(src.isClickable());
1935            dest.setFocusable(src.isFocusable());
1936            dest.setFocused(src.isFocused());
1937            dest.setAccessibilityFocused(src.isAccessibilityFocused());
1938            dest.setSelected(src.isSelected());
1939            dest.setLongClickable(src.isLongClickable());
1940
1941            dest.addAction(src.getActions());
1942        }
1943    }
1944
1945    final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat {
1946        @Override
1947        public void onInitializeAccessibilityNodeInfo(View child,
1948                AccessibilityNodeInfoCompat info) {
1949            super.onInitializeAccessibilityNodeInfo(child, info);
1950
1951            if (!includeChildForAccessibility(child)) {
1952                // If we are ignoring the sub-tree rooted at the child,
1953                // break the connection to the rest of the node tree.
1954                // For details refer to includeChildForAccessibility.
1955                info.setParent(null);
1956            }
1957        }
1958    }
1959}
1960