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