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