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