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