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