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