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