1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.support.v4.widget;
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.SystemClock;
30import android.support.v4.view.AccessibilityDelegateCompat;
31import android.support.v4.view.GravityCompat;
32import android.support.v4.view.KeyEventCompat;
33import android.support.v4.view.MotionEventCompat;
34import android.support.v4.view.ViewCompat;
35import android.support.v4.view.ViewGroupCompat;
36import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
37import android.util.AttributeSet;
38import android.view.Gravity;
39import android.view.KeyEvent;
40import android.view.MotionEvent;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.ViewParent;
44import android.view.accessibility.AccessibilityEvent;
45
46/**
47 * DrawerLayout acts as a top-level container for window content that allows for
48 * interactive "drawer" views to be pulled out from the edge of the window.
49 *
50 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
51 * attribute on child views corresponding to which side of the view you want the drawer
52 * to emerge from: left or right. (Or start/end on platform versions that support layout direction.)
53 * </p>
54 *
55 * <p>To use a DrawerLayout, position your primary content view as the first child with
56 * a width and height of <code>match_parent</code>. Add drawers as child views after the main
57 * content view and set the <code>layout_gravity</code> appropriately. Drawers commonly use
58 * <code>match_parent</code> for height with a fixed width.</p>
59 *
60 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
61 * Avoid performing expensive operations such as layout during animation as it can cause
62 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
63 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
64 *
65 * <p>As per the Android Design guide, any drawers positioned to the left/start should
66 * always contain content for navigating around the application, whereas any drawers
67 * positioned to the right/end should always contain actions to take on the current content.
68 * This preserves the same navigation left, actions right structure present in the Action Bar
69 * and elsewhere.</p>
70 */
71public class DrawerLayout extends ViewGroup {
72    private static final String TAG = "DrawerLayout";
73
74    /**
75     * Indicates that any drawers are in an idle, settled state. No animation is in progress.
76     */
77    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;
78
79    /**
80     * Indicates that a drawer is currently being dragged by the user.
81     */
82    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;
83
84    /**
85     * Indicates that a drawer is in the process of settling to a final position.
86     */
87    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
88
89    /**
90     * The drawer is unlocked.
91     */
92    public static final int LOCK_MODE_UNLOCKED = 0;
93
94    /**
95     * The drawer is locked closed. The user may not open it, though
96     * the app may open it programmatically.
97     */
98    public static final int LOCK_MODE_LOCKED_CLOSED = 1;
99
100    /**
101     * The drawer is locked open. The user may not close it, though the app
102     * may close it programmatically.
103     */
104    public static final int LOCK_MODE_LOCKED_OPEN = 2;
105
106    private static final int MIN_DRAWER_MARGIN = 64; // dp
107
108    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
109
110    /**
111     * Length of time to delay before peeking the drawer.
112     */
113    private static final int PEEK_DELAY = 160; // ms
114
115    /**
116     * Minimum velocity that will be detected as a fling
117     */
118    private static final int MIN_FLING_VELOCITY = 400; // dips per second
119
120    /**
121     * Experimental feature.
122     */
123    private static final boolean ALLOW_EDGE_LOCK = false;
124
125    private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
126
127    private static final float TOUCH_SLOP_SENSITIVITY = 1.f;
128
129    private static final int[] LAYOUT_ATTRS = new int[] {
130            android.R.attr.layout_gravity
131    };
132
133    private int mMinDrawerMargin;
134
135    private int mScrimColor = DEFAULT_SCRIM_COLOR;
136    private float mScrimOpacity;
137    private Paint mScrimPaint = new Paint();
138
139    private final ViewDragHelper mLeftDragger;
140    private final ViewDragHelper mRightDragger;
141    private final ViewDragCallback mLeftCallback;
142    private final ViewDragCallback mRightCallback;
143    private int mDrawerState;
144    private boolean mInLayout;
145    private boolean mFirstLayout = true;
146    private int mLockModeLeft;
147    private int mLockModeRight;
148    private boolean mDisallowInterceptRequested;
149    private boolean mChildrenCanceledTouch;
150
151    private DrawerListener mListener;
152
153    private float mInitialMotionX;
154    private float mInitialMotionY;
155
156    private Drawable mShadowLeft;
157    private Drawable mShadowRight;
158
159    /**
160     * Listener for monitoring events about drawers.
161     */
162    public interface DrawerListener {
163        /**
164         * Called when a drawer's position changes.
165         * @param drawerView The child view that was moved
166         * @param slideOffset The new offset of this drawer within its range, from 0-1
167         */
168        public void onDrawerSlide(View drawerView, float slideOffset);
169
170        /**
171         * Called when a drawer has settled in a completely open state.
172         * The drawer is interactive at this point.
173         *
174         * @param drawerView Drawer view that is now open
175         */
176        public void onDrawerOpened(View drawerView);
177
178        /**
179         * Called when a drawer has settled in a completely closed state.
180         *
181         * @param drawerView Drawer view that is now closed
182         */
183        public void onDrawerClosed(View drawerView);
184
185        /**
186         * Called when the drawer motion state changes. The new state will
187         * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
188         *
189         * @param newState The new drawer motion state
190         */
191        public void onDrawerStateChanged(int newState);
192    }
193
194    /**
195     * Stub/no-op implementations of all methods of {@link DrawerListener}.
196     * Override this if you only care about a few of the available callback methods.
197     */
198    public static abstract class SimpleDrawerListener implements DrawerListener {
199        @Override
200        public void onDrawerSlide(View drawerView, float slideOffset) {
201        }
202
203        @Override
204        public void onDrawerOpened(View drawerView) {
205        }
206
207        @Override
208        public void onDrawerClosed(View drawerView) {
209        }
210
211        @Override
212        public void onDrawerStateChanged(int newState) {
213        }
214    }
215
216    public DrawerLayout(Context context) {
217        this(context, null);
218    }
219
220    public DrawerLayout(Context context, AttributeSet attrs) {
221        this(context, attrs, 0);
222    }
223
224    public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
225        super(context, attrs, defStyle);
226
227        final float density = getResources().getDisplayMetrics().density;
228        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
229        final float minVel = MIN_FLING_VELOCITY * density;
230
231        mLeftCallback = new ViewDragCallback(Gravity.LEFT);
232        mRightCallback = new ViewDragCallback(Gravity.RIGHT);
233
234        mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
235        mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
236        mLeftDragger.setMinVelocity(minVel);
237        mLeftCallback.setDragger(mLeftDragger);
238
239        mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
240        mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
241        mRightDragger.setMinVelocity(minVel);
242        mRightCallback.setDragger(mRightDragger);
243
244        // So that we can catch the back button
245        setFocusableInTouchMode(true);
246
247        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
248        ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
249    }
250
251    /**
252     * Set a simple drawable used for the left or right shadow.
253     * The drawable provided must have a nonzero intrinsic width.
254     *
255     * @param shadowDrawable Shadow drawable to use at the edge of a drawer
256     * @param gravity Which drawer the shadow should apply to
257     */
258    public void setDrawerShadow(Drawable shadowDrawable, int gravity) {
259        /*
260         * TODO Someone someday might want to set more complex drawables here.
261         * They're probably nuts, but we might want to consider registering callbacks,
262         * setting states, etc. properly.
263         */
264
265        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
266                ViewCompat.getLayoutDirection(this));
267        if ((absGravity & Gravity.LEFT) == Gravity.LEFT) {
268            mShadowLeft = shadowDrawable;
269            invalidate();
270        }
271        if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) {
272            mShadowRight = shadowDrawable;
273            invalidate();
274        }
275    }
276
277    /**
278     * Set a simple drawable used for the left or right shadow.
279     * The drawable provided must have a nonzero intrinsic width.
280     *
281     * @param resId Resource id of a shadow drawable to use at the edge of a drawer
282     * @param gravity Which drawer the shadow should apply to
283     */
284    public void setDrawerShadow(int resId, int gravity) {
285        setDrawerShadow(getResources().getDrawable(resId), gravity);
286    }
287
288    /**
289     * Set a color to use for the scrim that obscures primary content while a drawer is open.
290     *
291     * @param color Color to use in 0xAARRGGBB format.
292     */
293    public void setScrimColor(int color) {
294        mScrimColor = color;
295        invalidate();
296    }
297
298    /**
299     * Set a listener to be notified of drawer events.
300     *
301     * @param listener Listener to notify when drawer events occur
302     * @see DrawerListener
303     */
304    public void setDrawerListener(DrawerListener listener) {
305        mListener = listener;
306    }
307
308    /**
309     * Enable or disable interaction with all drawers.
310     *
311     * <p>This allows the application to restrict the user's ability to open or close
312     * any drawer within this layout. DrawerLayout will still respond to calls to
313     * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
314     *
315     * <p>Locking drawers open or closed will implicitly open or close
316     * any drawers as appropriate.</p>
317     *
318     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
319     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
320     */
321    public void setDrawerLockMode(int lockMode) {
322        setDrawerLockMode(lockMode, Gravity.LEFT);
323        setDrawerLockMode(lockMode, Gravity.RIGHT);
324    }
325
326    /**
327     * Enable or disable interaction with the given drawer.
328     *
329     * <p>This allows the application to restrict the user's ability to open or close
330     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
331     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
332     *
333     * <p>Locking a drawer open or closed will implicitly open or close
334     * that drawer as appropriate.</p>
335     *
336     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
337     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
338     * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
339     *                    Expresses which drawer to change the mode for.
340     *
341     * @see #LOCK_MODE_UNLOCKED
342     * @see #LOCK_MODE_LOCKED_CLOSED
343     * @see #LOCK_MODE_LOCKED_OPEN
344     */
345    public void setDrawerLockMode(int lockMode, int edgeGravity) {
346        final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
347                ViewCompat.getLayoutDirection(this));
348        if (absGravity == Gravity.LEFT) {
349            mLockModeLeft = lockMode;
350        } else if (absGravity == Gravity.RIGHT) {
351            mLockModeRight = lockMode;
352        }
353        if (lockMode != LOCK_MODE_UNLOCKED) {
354            // Cancel interaction in progress
355            final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
356            helper.cancel();
357        }
358        switch (lockMode) {
359            case LOCK_MODE_LOCKED_OPEN:
360                final View toOpen = findDrawerWithGravity(absGravity);
361                if (toOpen != null) {
362                    openDrawer(toOpen);
363                }
364                break;
365            case LOCK_MODE_LOCKED_CLOSED:
366                final View toClose = findDrawerWithGravity(absGravity);
367                if (toClose != null) {
368                    closeDrawer(toClose);
369                }
370                break;
371            // default: do nothing
372        }
373    }
374
375    /**
376     * Enable or disable interaction with the given drawer.
377     *
378     * <p>This allows the application to restrict the user's ability to open or close
379     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
380     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
381     *
382     * <p>Locking a drawer open or closed will implicitly open or close
383     * that drawer as appropriate.</p>
384     *
385     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
386     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
387     * @param drawerView The drawer view to change the lock mode for
388     *
389     * @see #LOCK_MODE_UNLOCKED
390     * @see #LOCK_MODE_LOCKED_CLOSED
391     * @see #LOCK_MODE_LOCKED_OPEN
392     */
393    public void setDrawerLockMode(int lockMode, View drawerView) {
394        if (!isDrawerView(drawerView)) {
395            throw new IllegalArgumentException("View " + drawerView + " is not a " +
396                    "drawer with appropriate layout_gravity");
397        }
398        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
399        setDrawerLockMode(lockMode, gravity);
400    }
401
402    /**
403     * Check the lock mode of the drawer with the given gravity.
404     *
405     * @param edgeGravity Gravity of the drawer to check
406     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
407     *         {@link #LOCK_MODE_LOCKED_OPEN}.
408     */
409    public int getDrawerLockMode(int edgeGravity) {
410        final int absGravity = GravityCompat.getAbsoluteGravity(
411                edgeGravity, ViewCompat.getLayoutDirection(this));
412        if (absGravity == Gravity.LEFT) {
413            return mLockModeLeft;
414        } else if (absGravity == Gravity.RIGHT) {
415            return mLockModeRight;
416        }
417        return LOCK_MODE_UNLOCKED;
418    }
419
420    /**
421     * Check the lock mode of the given drawer view.
422     *
423     * @param drawerView Drawer view to check lock mode
424     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
425     *         {@link #LOCK_MODE_LOCKED_OPEN}.
426     */
427    public int getDrawerLockMode(View drawerView) {
428        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
429        if (absGravity == Gravity.LEFT) {
430            return mLockModeLeft;
431        } else if (absGravity == Gravity.RIGHT) {
432            return mLockModeRight;
433        }
434        return LOCK_MODE_UNLOCKED;
435    }
436
437    /**
438     * Resolve the shared state of all drawers from the component ViewDragHelpers.
439     * Should be called whenever a ViewDragHelper's state changes.
440     */
441    void updateDrawerState(int forGravity, int activeState, View activeDrawer) {
442        final int leftState = mLeftDragger.getViewDragState();
443        final int rightState = mRightDragger.getViewDragState();
444
445        final int state;
446        if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
447            state = STATE_DRAGGING;
448        } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
449            state = STATE_SETTLING;
450        } else {
451            state = STATE_IDLE;
452        }
453
454        if (activeDrawer != null && activeState == STATE_IDLE) {
455            final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
456            if (lp.onScreen == 0) {
457                dispatchOnDrawerClosed(activeDrawer);
458            } else if (lp.onScreen == 1) {
459                dispatchOnDrawerOpened(activeDrawer);
460            }
461        }
462
463        if (state != mDrawerState) {
464            mDrawerState = state;
465
466            if (mListener != null) {
467                mListener.onDrawerStateChanged(state);
468            }
469        }
470    }
471
472    void dispatchOnDrawerClosed(View drawerView) {
473        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
474        if (lp.knownOpen) {
475            lp.knownOpen = false;
476            if (mListener != null) {
477                mListener.onDrawerClosed(drawerView);
478            }
479            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
480        }
481    }
482
483    void dispatchOnDrawerOpened(View drawerView) {
484        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
485        if (!lp.knownOpen) {
486            lp.knownOpen = true;
487            if (mListener != null) {
488                mListener.onDrawerOpened(drawerView);
489            }
490            drawerView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
491        }
492    }
493
494    void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
495        if (mListener != null) {
496            mListener.onDrawerSlide(drawerView, slideOffset);
497        }
498    }
499
500    void setDrawerViewOffset(View drawerView, float slideOffset) {
501        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
502        if (slideOffset == lp.onScreen) {
503            return;
504        }
505
506        lp.onScreen = slideOffset;
507        dispatchOnDrawerSlide(drawerView, slideOffset);
508    }
509
510    float getDrawerViewOffset(View drawerView) {
511        return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
512    }
513
514    /**
515     * @return the absolute gravity of the child drawerView, resolved according
516     *         to the current layout direction
517     */
518    int getDrawerViewAbsoluteGravity(View drawerView) {
519        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
520        return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
521    }
522
523    boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
524        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
525        return (absGravity & checkFor) == checkFor;
526    }
527
528    View findOpenDrawer() {
529        final int childCount = getChildCount();
530        for (int i = 0; i < childCount; i++) {
531            final View child = getChildAt(i);
532            if (((LayoutParams) child.getLayoutParams()).knownOpen) {
533                return child;
534            }
535        }
536        return null;
537    }
538
539    void moveDrawerToOffset(View drawerView, float slideOffset) {
540        final float oldOffset = getDrawerViewOffset(drawerView);
541        final int width = drawerView.getWidth();
542        final int oldPos = (int) (width * oldOffset);
543        final int newPos = (int) (width * slideOffset);
544        final int dx = newPos - oldPos;
545
546        drawerView.offsetLeftAndRight(
547                checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
548        setDrawerViewOffset(drawerView, slideOffset);
549    }
550
551    /**
552     * @param gravity the gravity of the child to return. If specified as a
553     *            relative value, it will be resolved according to the current
554     *            layout direction.
555     * @return the drawer with the specified gravity
556     */
557    View findDrawerWithGravity(int gravity) {
558        final int absHorizGravity = GravityCompat.getAbsoluteGravity(
559                gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
560        final int childCount = getChildCount();
561        for (int i = 0; i < childCount; i++) {
562            final View child = getChildAt(i);
563            final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
564            if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
565                return child;
566            }
567        }
568        return null;
569    }
570
571    /**
572     * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
573     *
574     * @param gravity Absolute gravity value
575     * @return LEFT or RIGHT as appropriate, or a hex string
576     */
577    static String gravityToString(int gravity) {
578        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
579            return "LEFT";
580        }
581        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
582            return "RIGHT";
583        }
584        return Integer.toHexString(gravity);
585    }
586
587    @Override
588    protected void onDetachedFromWindow() {
589        super.onDetachedFromWindow();
590        mFirstLayout = true;
591    }
592
593    @Override
594    protected void onAttachedToWindow() {
595        super.onAttachedToWindow();
596        mFirstLayout = true;
597    }
598
599    @Override
600    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
601        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
602        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
603        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
604        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
605
606        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
607            if (isInEditMode()) {
608                // Don't crash the layout editor. Consume all of the space if specified
609                // or pick a magic number from thin air otherwise.
610                // TODO Better communication with tools of this bogus state.
611                // It will crash on a real device.
612                if (widthMode == MeasureSpec.AT_MOST) {
613                    widthMode = MeasureSpec.EXACTLY;
614                } else if (widthMode == MeasureSpec.UNSPECIFIED) {
615                    widthMode = MeasureSpec.EXACTLY;
616                    widthSize = 300;
617                }
618                if (heightMode == MeasureSpec.AT_MOST) {
619                    heightMode = MeasureSpec.EXACTLY;
620                }
621                else if (heightMode == MeasureSpec.UNSPECIFIED) {
622                    heightMode = MeasureSpec.EXACTLY;
623                    heightSize = 300;
624                }
625            } else {
626                throw new IllegalArgumentException(
627                        "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
628            }
629        }
630
631        setMeasuredDimension(widthSize, heightSize);
632
633        // Gravity value for each drawer we've seen. Only one of each permitted.
634        int foundDrawers = 0;
635        final int childCount = getChildCount();
636        for (int i = 0; i < childCount; i++) {
637            final View child = getChildAt(i);
638
639            if (child.getVisibility() == GONE) {
640                continue;
641            }
642
643            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
644
645            if (isContentView(child)) {
646                // Content views get measured at exactly the layout's size.
647                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
648                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
649                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
650                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
651                child.measure(contentWidthSpec, contentHeightSpec);
652            } else if (isDrawerView(child)) {
653                final int childGravity =
654                        getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
655                if ((foundDrawers & childGravity) != 0) {
656                    throw new IllegalStateException("Child drawer has absolute gravity " +
657                            gravityToString(childGravity) + " but this " + TAG + " already has a " +
658                            "drawer view along that edge");
659                }
660                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
661                        mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
662                        lp.width);
663                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
664                        lp.topMargin + lp.bottomMargin,
665                        lp.height);
666                child.measure(drawerWidthSpec, drawerHeightSpec);
667            } else {
668                throw new IllegalStateException("Child " + child + " at index " + i +
669                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
670                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
671            }
672        }
673    }
674
675    @Override
676    protected void onLayout(boolean changed, int l, int t, int r, int b) {
677        mInLayout = true;
678        final int width = r - l;
679        final int childCount = getChildCount();
680        for (int i = 0; i < childCount; i++) {
681            final View child = getChildAt(i);
682
683            if (child.getVisibility() == GONE) {
684                continue;
685            }
686
687            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
688
689            if (isContentView(child)) {
690                child.layout(lp.leftMargin, lp.topMargin,
691                        lp.leftMargin + child.getMeasuredWidth(),
692                        lp.topMargin + child.getMeasuredHeight());
693            } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
694                final int childWidth = child.getMeasuredWidth();
695                final int childHeight = child.getMeasuredHeight();
696                int childLeft;
697
698                final float newOffset;
699                if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
700                    childLeft = -childWidth + (int) (childWidth * lp.onScreen);
701                    newOffset = (float) (childWidth + childLeft) / childWidth;
702                } else { // Right; onMeasure checked for us.
703                    childLeft = width - (int) (childWidth * lp.onScreen);
704                    newOffset = (float) (width - childLeft) / childWidth;
705                }
706
707                final boolean changeOffset = newOffset != lp.onScreen;
708
709                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
710
711                switch (vgrav) {
712                    default:
713                    case Gravity.TOP: {
714                        child.layout(childLeft, lp.topMargin, childLeft + childWidth,
715                                lp.topMargin + childHeight);
716                        break;
717                    }
718
719                    case Gravity.BOTTOM: {
720                        final int height = b - t;
721                        child.layout(childLeft,
722                                height - lp.bottomMargin - child.getMeasuredHeight(),
723                                childLeft + childWidth,
724                                height - lp.bottomMargin);
725                        break;
726                    }
727
728                    case Gravity.CENTER_VERTICAL: {
729                        final int height = b - t;
730                        int childTop = (height - childHeight) / 2;
731
732                        // Offset for margins. If things don't fit right because of
733                        // bad measurement before, oh well.
734                        if (childTop < lp.topMargin) {
735                            childTop = lp.topMargin;
736                        } else if (childTop + childHeight > height - lp.bottomMargin) {
737                            childTop = height - lp.bottomMargin - childHeight;
738                        }
739                        child.layout(childLeft, childTop, childLeft + childWidth,
740                                childTop + childHeight);
741                        break;
742                    }
743                }
744
745                if (changeOffset) {
746                    setDrawerViewOffset(child, newOffset);
747                }
748
749                final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
750                if (child.getVisibility() != newVisibility) {
751                    child.setVisibility(newVisibility);
752                }
753            }
754        }
755        mInLayout = false;
756        mFirstLayout = false;
757    }
758
759    @Override
760    public void requestLayout() {
761        if (!mInLayout) {
762            super.requestLayout();
763        }
764    }
765
766    @Override
767    public void computeScroll() {
768        final int childCount = getChildCount();
769        float scrimOpacity = 0;
770        for (int i = 0; i < childCount; i++) {
771            final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
772            scrimOpacity = Math.max(scrimOpacity, onscreen);
773        }
774        mScrimOpacity = scrimOpacity;
775
776        // "|" used on purpose; both need to run.
777        if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
778            ViewCompat.postInvalidateOnAnimation(this);
779        }
780    }
781
782    private static boolean hasOpaqueBackground(View v) {
783        final Drawable bg = v.getBackground();
784        if (bg != null) {
785            return bg.getOpacity() == PixelFormat.OPAQUE;
786        }
787        return false;
788    }
789
790    @Override
791    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
792        final int height = getHeight();
793        final boolean drawingContent = isContentView(child);
794        int clipLeft = 0, clipRight = getWidth();
795
796        final int restoreCount = canvas.save();
797        if (drawingContent) {
798            final int childCount = getChildCount();
799            for (int i = 0; i < childCount; i++) {
800                final View v = getChildAt(i);
801                if (v == child || v.getVisibility() != VISIBLE ||
802                        !hasOpaqueBackground(v) || !isDrawerView(v) ||
803                        v.getHeight() < height) {
804                    continue;
805                }
806
807                if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
808                    final int vright = v.getRight();
809                    if (vright > clipLeft) clipLeft = vright;
810                } else {
811                    final int vleft = v.getLeft();
812                    if (vleft < clipRight) clipRight = vleft;
813                }
814            }
815            canvas.clipRect(clipLeft, 0, clipRight, getHeight());
816        }
817        final boolean result = super.drawChild(canvas, child, drawingTime);
818        canvas.restoreToCount(restoreCount);
819
820        if (mScrimOpacity > 0 && drawingContent) {
821            final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
822            final int imag = (int) (baseAlpha * mScrimOpacity);
823            final int color = imag << 24 | (mScrimColor & 0xffffff);
824            mScrimPaint.setColor(color);
825
826            canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
827        } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
828            final int shadowWidth = mShadowLeft.getIntrinsicWidth();
829            final int childRight = child.getRight();
830            final int drawerPeekDistance = mLeftDragger.getEdgeSize();
831            final float alpha =
832                    Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
833            mShadowLeft.setBounds(childRight, child.getTop(),
834                    childRight + shadowWidth, child.getBottom());
835            mShadowLeft.setAlpha((int) (0xff * alpha));
836            mShadowLeft.draw(canvas);
837        } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
838            final int shadowWidth = mShadowRight.getIntrinsicWidth();
839            final int childLeft = child.getLeft();
840            final int showing = getWidth() - childLeft;
841            final int drawerPeekDistance = mRightDragger.getEdgeSize();
842            final float alpha =
843                    Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
844            mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(),
845                    childLeft, child.getBottom());
846            mShadowRight.setAlpha((int) (0xff * alpha));
847            mShadowRight.draw(canvas);
848        }
849        return result;
850    }
851
852    boolean isContentView(View child) {
853        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
854    }
855
856    boolean isDrawerView(View child) {
857        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
858        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
859                ViewCompat.getLayoutDirection(child));
860        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
861    }
862
863    @Override
864    public boolean onInterceptTouchEvent(MotionEvent ev) {
865        final int action = MotionEventCompat.getActionMasked(ev);
866
867        // "|" used deliberately here; both methods should be invoked.
868        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
869                mRightDragger.shouldInterceptTouchEvent(ev);
870
871        boolean interceptForTap = false;
872
873        switch (action) {
874            case MotionEvent.ACTION_DOWN: {
875                final float x = ev.getX();
876                final float y = ev.getY();
877                mInitialMotionX = x;
878                mInitialMotionY = y;
879                if (mScrimOpacity > 0 &&
880                        isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
881                    interceptForTap = true;
882                }
883                mDisallowInterceptRequested = false;
884                mChildrenCanceledTouch = false;
885                break;
886            }
887
888            case MotionEvent.ACTION_MOVE: {
889                // If we cross the touch slop, don't perform the delayed peek for an edge touch.
890                if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
891                    mLeftCallback.removeCallbacks();
892                    mRightCallback.removeCallbacks();
893                }
894                break;
895            }
896
897            case MotionEvent.ACTION_CANCEL:
898            case MotionEvent.ACTION_UP: {
899                closeDrawers(true);
900                mDisallowInterceptRequested = false;
901                mChildrenCanceledTouch = false;
902            }
903        }
904
905        return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
906    }
907
908    @Override
909    public boolean onTouchEvent(MotionEvent ev) {
910        mLeftDragger.processTouchEvent(ev);
911        mRightDragger.processTouchEvent(ev);
912
913        final int action = ev.getAction();
914        boolean wantTouchEvents = true;
915
916        switch (action & MotionEventCompat.ACTION_MASK) {
917            case MotionEvent.ACTION_DOWN: {
918                final float x = ev.getX();
919                final float y = ev.getY();
920                mInitialMotionX = x;
921                mInitialMotionY = y;
922                mDisallowInterceptRequested = false;
923                mChildrenCanceledTouch = false;
924                break;
925            }
926
927            case MotionEvent.ACTION_UP: {
928                final float x = ev.getX();
929                final float y = ev.getY();
930                boolean peekingOnly = true;
931                final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
932                if (touchedView != null && isContentView(touchedView)) {
933                    final float dx = x - mInitialMotionX;
934                    final float dy = y - mInitialMotionY;
935                    final int slop = mLeftDragger.getTouchSlop();
936                    if (dx * dx + dy * dy < slop * slop) {
937                        // Taps close a dimmed open drawer but only if it isn't locked open.
938                        final View openDrawer = findOpenDrawer();
939                        if (openDrawer != null) {
940                            peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
941                        }
942                    }
943                }
944                closeDrawers(peekingOnly);
945                mDisallowInterceptRequested = false;
946                break;
947            }
948
949            case MotionEvent.ACTION_CANCEL: {
950                closeDrawers(true);
951                mDisallowInterceptRequested = false;
952                mChildrenCanceledTouch = false;
953                break;
954            }
955        }
956
957        return wantTouchEvents;
958    }
959
960    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
961        if (CHILDREN_DISALLOW_INTERCEPT ||
962                (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) &&
963                !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
964            // If we have an edge touch we want to skip this and track it for later instead.
965            super.requestDisallowInterceptTouchEvent(disallowIntercept);
966        }
967        mDisallowInterceptRequested = disallowIntercept;
968        if (disallowIntercept) {
969            closeDrawers(true);
970        }
971    }
972
973    /**
974     * Close all currently open drawer views by animating them out of view.
975     */
976    public void closeDrawers() {
977        closeDrawers(false);
978    }
979
980    void closeDrawers(boolean peekingOnly) {
981        boolean needsInvalidate = false;
982        final int childCount = getChildCount();
983        for (int i = 0; i < childCount; i++) {
984            final View child = getChildAt(i);
985            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
986
987            if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
988                continue;
989            }
990
991            final int childWidth = child.getWidth();
992
993            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
994                needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
995                        -childWidth, child.getTop());
996            } else {
997                needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
998                        getWidth(), child.getTop());
999            }
1000
1001            lp.isPeeking = false;
1002        }
1003
1004        mLeftCallback.removeCallbacks();
1005        mRightCallback.removeCallbacks();
1006
1007        if (needsInvalidate) {
1008            invalidate();
1009        }
1010    }
1011
1012    /**
1013     * Open the specified drawer view by animating it into view.
1014     *
1015     * @param drawerView Drawer view to open
1016     */
1017    public void openDrawer(View drawerView) {
1018        if (!isDrawerView(drawerView)) {
1019            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1020        }
1021
1022        if (mFirstLayout) {
1023            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1024            lp.onScreen = 1.f;
1025            lp.knownOpen = true;
1026        } else {
1027            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1028                mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
1029            } else {
1030                mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
1031                        drawerView.getTop());
1032            }
1033        }
1034        invalidate();
1035    }
1036
1037    /**
1038     * Open the specified drawer by animating it out of view.
1039     *
1040     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1041     *                GravityCompat.START or GravityCompat.END may also be used.
1042     */
1043    public void openDrawer(int gravity) {
1044        final View drawerView = findDrawerWithGravity(gravity);
1045        if (drawerView == null) {
1046            throw new IllegalArgumentException("No drawer view found with gravity " +
1047                    gravityToString(gravity));
1048        }
1049        openDrawer(drawerView);
1050    }
1051
1052    /**
1053     * Close the specified drawer view by animating it into view.
1054     *
1055     * @param drawerView Drawer view to close
1056     */
1057    public void closeDrawer(View drawerView) {
1058        if (!isDrawerView(drawerView)) {
1059            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1060        }
1061
1062        if (mFirstLayout) {
1063            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1064            lp.onScreen = 0.f;
1065            lp.knownOpen = false;
1066        } else {
1067            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1068                mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
1069                        drawerView.getTop());
1070            } else {
1071                mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
1072            }
1073        }
1074        invalidate();
1075    }
1076
1077    /**
1078     * Close the specified drawer by animating it out of view.
1079     *
1080     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1081     *                GravityCompat.START or GravityCompat.END may also be used.
1082     */
1083    public void closeDrawer(int gravity) {
1084        final View drawerView = findDrawerWithGravity(gravity);
1085        if (drawerView == null) {
1086            throw new IllegalArgumentException("No drawer view found with gravity " +
1087                    gravityToString(gravity));
1088        }
1089        closeDrawer(drawerView);
1090    }
1091
1092    /**
1093     * Check if the given drawer view is currently in an open state.
1094     * To be considered "open" the drawer must have settled into its fully
1095     * visible state. To check for partial visibility use
1096     * {@link #isDrawerVisible(android.view.View)}.
1097     *
1098     * @param drawer Drawer view to check
1099     * @return true if the given drawer view is in an open state
1100     * @see #isDrawerVisible(android.view.View)
1101     */
1102    public boolean isDrawerOpen(View drawer) {
1103        if (!isDrawerView(drawer)) {
1104            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1105        }
1106        return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
1107    }
1108
1109    /**
1110     * Check if the given drawer view is currently in an open state.
1111     * To be considered "open" the drawer must have settled into its fully
1112     * visible state. If there is no drawer with the given gravity this method
1113     * will return false.
1114     *
1115     * @param drawerGravity Gravity of the drawer to check
1116     * @return true if the given drawer view is in an open state
1117     */
1118    public boolean isDrawerOpen(int drawerGravity) {
1119        final View drawerView = findDrawerWithGravity(drawerGravity);
1120        if (drawerView != null) {
1121            return isDrawerOpen(drawerView);
1122        }
1123        return false;
1124    }
1125
1126    /**
1127     * Check if a given drawer view is currently visible on-screen. The drawer
1128     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
1129     *
1130     * @param drawer Drawer view to check
1131     * @return true if the given drawer is visible on-screen
1132     * @see #isDrawerOpen(android.view.View)
1133     */
1134    public boolean isDrawerVisible(View drawer) {
1135        if (!isDrawerView(drawer)) {
1136            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1137        }
1138        return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
1139    }
1140
1141    /**
1142     * Check if a given drawer view is currently visible on-screen. The drawer
1143     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
1144     * If there is no drawer with the given gravity this method will return false.
1145     *
1146     * @param drawerGravity Gravity of the drawer to check
1147     * @return true if the given drawer is visible on-screen
1148     */
1149    public boolean isDrawerVisible(int drawerGravity) {
1150        final View drawerView = findDrawerWithGravity(drawerGravity);
1151        if (drawerView != null) {
1152            return isDrawerVisible(drawerView);
1153        }
1154        return false;
1155    }
1156
1157    private boolean hasPeekingDrawer() {
1158        final int childCount = getChildCount();
1159        for (int i = 0; i < childCount; i++) {
1160            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
1161            if (lp.isPeeking) {
1162                return true;
1163            }
1164        }
1165        return false;
1166    }
1167
1168    @Override
1169    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1170        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
1171    }
1172
1173    @Override
1174    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1175        return p instanceof LayoutParams
1176                ? new LayoutParams((LayoutParams) p)
1177                : p instanceof ViewGroup.MarginLayoutParams
1178                ? new LayoutParams((MarginLayoutParams) p)
1179                : new LayoutParams(p);
1180    }
1181
1182    @Override
1183    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1184        return p instanceof LayoutParams && super.checkLayoutParams(p);
1185    }
1186
1187    @Override
1188    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1189        return new LayoutParams(getContext(), attrs);
1190    }
1191
1192    private boolean hasVisibleDrawer() {
1193        return findVisibleDrawer() != null;
1194    }
1195
1196    private View findVisibleDrawer() {
1197        final int childCount = getChildCount();
1198        for (int i = 0; i < childCount; i++) {
1199            final View child = getChildAt(i);
1200            if (isDrawerView(child) && isDrawerVisible(child)) {
1201                return child;
1202            }
1203        }
1204        return null;
1205    }
1206
1207    void cancelChildViewTouch() {
1208        // Cancel child touches
1209        if (!mChildrenCanceledTouch) {
1210            final long now = SystemClock.uptimeMillis();
1211            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
1212                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1213            final int childCount = getChildCount();
1214            for (int i = 0; i < childCount; i++) {
1215                getChildAt(i).dispatchTouchEvent(cancelEvent);
1216            }
1217            cancelEvent.recycle();
1218            mChildrenCanceledTouch = true;
1219        }
1220    }
1221
1222    @Override
1223    public boolean onKeyDown(int keyCode, KeyEvent event) {
1224        if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
1225            KeyEventCompat.startTracking(event);
1226            return true;
1227        }
1228        return super.onKeyDown(keyCode, event);
1229    }
1230
1231    @Override
1232    public boolean onKeyUp(int keyCode, KeyEvent event) {
1233        if (keyCode == KeyEvent.KEYCODE_BACK) {
1234            final View visibleDrawer = findVisibleDrawer();
1235            if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
1236                closeDrawers();
1237            }
1238            return visibleDrawer != null;
1239        }
1240        return super.onKeyUp(keyCode, event);
1241    }
1242
1243    @Override
1244    protected void onRestoreInstanceState(Parcelable state) {
1245        final SavedState ss = (SavedState) state;
1246        super.onRestoreInstanceState(ss.getSuperState());
1247
1248        if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
1249            final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
1250            if (toOpen != null) {
1251                openDrawer(toOpen);
1252            }
1253        }
1254
1255        setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
1256        setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
1257    }
1258
1259    @Override
1260    protected Parcelable onSaveInstanceState() {
1261        final Parcelable superState = super.onSaveInstanceState();
1262
1263        final SavedState ss = new SavedState(superState);
1264
1265        final int childCount = getChildCount();
1266        for (int i = 0; i < childCount; i++) {
1267            final View child = getChildAt(i);
1268            if (!isDrawerView(child)) {
1269                continue;
1270            }
1271
1272            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1273            if (lp.knownOpen) {
1274                ss.openDrawerGravity = lp.gravity;
1275                // Only one drawer can be open at a time.
1276                break;
1277            }
1278        }
1279
1280        ss.lockModeLeft = mLockModeLeft;
1281        ss.lockModeRight = mLockModeRight;
1282
1283        return ss;
1284    }
1285
1286    /**
1287     * State persisted across instances
1288     */
1289    protected static class SavedState extends BaseSavedState {
1290        int openDrawerGravity = Gravity.NO_GRAVITY;
1291        int lockModeLeft = LOCK_MODE_UNLOCKED;
1292        int lockModeRight = LOCK_MODE_UNLOCKED;
1293
1294        public SavedState(Parcel in) {
1295            super(in);
1296            openDrawerGravity = in.readInt();
1297        }
1298
1299        public SavedState(Parcelable superState) {
1300            super(superState);
1301        }
1302
1303        @Override
1304        public void writeToParcel(Parcel dest, int flags) {
1305            super.writeToParcel(dest, flags);
1306            dest.writeInt(openDrawerGravity);
1307        }
1308
1309        public static final Parcelable.Creator<SavedState> CREATOR =
1310                new Parcelable.Creator<SavedState>() {
1311            @Override
1312            public SavedState createFromParcel(Parcel source) {
1313                return new SavedState(source);
1314            }
1315
1316            @Override
1317            public SavedState[] newArray(int size) {
1318                return new SavedState[size];
1319            }
1320        };
1321    }
1322
1323    private class ViewDragCallback extends ViewDragHelper.Callback {
1324        private final int mAbsGravity;
1325        private ViewDragHelper mDragger;
1326
1327        private final Runnable mPeekRunnable = new Runnable() {
1328            @Override public void run() {
1329                peekDrawer();
1330            }
1331        };
1332
1333        public ViewDragCallback(int gravity) {
1334            mAbsGravity = gravity;
1335        }
1336
1337        public void setDragger(ViewDragHelper dragger) {
1338            mDragger = dragger;
1339        }
1340
1341        public void removeCallbacks() {
1342            DrawerLayout.this.removeCallbacks(mPeekRunnable);
1343        }
1344
1345        @Override
1346        public boolean tryCaptureView(View child, int pointerId) {
1347            // Only capture views where the gravity matches what we're looking for.
1348            // This lets us use two ViewDragHelpers, one for each side drawer.
1349            return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
1350                    && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
1351        }
1352
1353        @Override
1354        public void onViewDragStateChanged(int state) {
1355            updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
1356        }
1357
1358        @Override
1359        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1360            float offset;
1361            final int childWidth = changedView.getWidth();
1362
1363            // This reverses the positioning shown in onLayout.
1364            if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
1365                offset = (float) (childWidth + left) / childWidth;
1366            } else {
1367                final int width = getWidth();
1368                offset = (float) (width - left) / childWidth;
1369            }
1370            setDrawerViewOffset(changedView, offset);
1371            changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
1372            invalidate();
1373        }
1374
1375        @Override
1376        public void onViewCaptured(View capturedChild, int activePointerId) {
1377            final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
1378            lp.isPeeking = false;
1379
1380            closeOtherDrawer();
1381        }
1382
1383        private void closeOtherDrawer() {
1384            final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
1385            final View toClose = findDrawerWithGravity(otherGrav);
1386            if (toClose != null) {
1387                closeDrawer(toClose);
1388            }
1389        }
1390
1391        @Override
1392        public void onViewReleased(View releasedChild, float xvel, float yvel) {
1393            // Offset is how open the drawer is, therefore left/right values
1394            // are reversed from one another.
1395            final float offset = getDrawerViewOffset(releasedChild);
1396            final int childWidth = releasedChild.getWidth();
1397
1398            int left;
1399            if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
1400                left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
1401            } else {
1402                final int width = getWidth();
1403                left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width;
1404            }
1405
1406            mDragger.settleCapturedViewAt(left, releasedChild.getTop());
1407            invalidate();
1408        }
1409
1410        @Override
1411        public void onEdgeTouched(int edgeFlags, int pointerId) {
1412            postDelayed(mPeekRunnable, PEEK_DELAY);
1413        }
1414
1415        private void peekDrawer() {
1416            final View toCapture;
1417            final int childLeft;
1418            final int peekDistance = mDragger.getEdgeSize();
1419            final boolean leftEdge = mAbsGravity == Gravity.LEFT;
1420            if (leftEdge) {
1421                toCapture = findDrawerWithGravity(Gravity.LEFT);
1422                childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
1423            } else {
1424                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1425                childLeft = getWidth() - peekDistance;
1426            }
1427            // Only peek if it would mean making the drawer more visible and the drawer isn't locked
1428            if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) ||
1429                    (!leftEdge && toCapture.getLeft() > childLeft)) &&
1430                    getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1431                final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
1432                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
1433                lp.isPeeking = true;
1434                invalidate();
1435
1436                closeOtherDrawer();
1437
1438                cancelChildViewTouch();
1439            }
1440        }
1441
1442        @Override
1443        public boolean onEdgeLock(int edgeFlags) {
1444            if (ALLOW_EDGE_LOCK) {
1445                final View drawer = findDrawerWithGravity(mAbsGravity);
1446                if (drawer != null && !isDrawerOpen(drawer)) {
1447                    closeDrawer(drawer);
1448                }
1449                return true;
1450            }
1451            return false;
1452        }
1453
1454        @Override
1455        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1456            final View toCapture;
1457            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
1458                toCapture = findDrawerWithGravity(Gravity.LEFT);
1459            } else {
1460                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1461            }
1462
1463            if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1464                mDragger.captureChildView(toCapture, pointerId);
1465            }
1466        }
1467
1468        @Override
1469        public int getViewHorizontalDragRange(View child) {
1470            return child.getWidth();
1471        }
1472
1473        @Override
1474        public int clampViewPositionHorizontal(View child, int left, int dx) {
1475            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1476                return Math.max(-child.getWidth(), Math.min(left, 0));
1477            } else {
1478                final int width = getWidth();
1479                return Math.max(width - child.getWidth(), Math.min(left, width));
1480            }
1481        }
1482
1483        @Override
1484        public int clampViewPositionVertical(View child, int top, int dy) {
1485            return child.getTop();
1486        }
1487    }
1488
1489    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1490
1491        public int gravity = Gravity.NO_GRAVITY;
1492        float onScreen;
1493        boolean isPeeking;
1494        boolean knownOpen;
1495
1496        public LayoutParams(Context c, AttributeSet attrs) {
1497            super(c, attrs);
1498
1499            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
1500            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
1501            a.recycle();
1502        }
1503
1504        public LayoutParams(int width, int height) {
1505            super(width, height);
1506        }
1507
1508        public LayoutParams(int width, int height, int gravity) {
1509            this(width, height);
1510            this.gravity = gravity;
1511        }
1512
1513        public LayoutParams(LayoutParams source) {
1514            super(source);
1515            this.gravity = source.gravity;
1516        }
1517
1518        public LayoutParams(ViewGroup.LayoutParams source) {
1519            super(source);
1520        }
1521
1522        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1523            super(source);
1524        }
1525    }
1526
1527    class AccessibilityDelegate extends AccessibilityDelegateCompat {
1528        private final Rect mTmpRect = new Rect();
1529
1530        @Override
1531        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1532            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1533            super.onInitializeAccessibilityNodeInfo(host, superNode);
1534
1535            info.setSource(host);
1536            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1537            if (parent instanceof View) {
1538                info.setParent((View) parent);
1539            }
1540            copyNodeInfoNoChildren(info, superNode);
1541
1542            superNode.recycle();
1543
1544            final int childCount = getChildCount();
1545            for (int i = 0; i < childCount; i++) {
1546                final View child = getChildAt(i);
1547                if (!filter(child)) {
1548                    info.addChild(child);
1549                }
1550            }
1551        }
1552
1553        @Override
1554        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1555                AccessibilityEvent event) {
1556            if (!filter(child)) {
1557                return super.onRequestSendAccessibilityEvent(host, child, event);
1558            }
1559            return false;
1560        }
1561
1562        public boolean filter(View child) {
1563            final View openDrawer = findOpenDrawer();
1564            return openDrawer != null && openDrawer != child;
1565        }
1566
1567        /**
1568         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1569         * seem to be a few elements that are not easily cloneable using the underlying API.
1570         * Leave it private here as it's not general-purpose useful.
1571         */
1572        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1573                AccessibilityNodeInfoCompat src) {
1574            final Rect rect = mTmpRect;
1575
1576            src.getBoundsInParent(rect);
1577            dest.setBoundsInParent(rect);
1578
1579            src.getBoundsInScreen(rect);
1580            dest.setBoundsInScreen(rect);
1581
1582            dest.setVisibleToUser(src.isVisibleToUser());
1583            dest.setPackageName(src.getPackageName());
1584            dest.setClassName(src.getClassName());
1585            dest.setContentDescription(src.getContentDescription());
1586
1587            dest.setEnabled(src.isEnabled());
1588            dest.setClickable(src.isClickable());
1589            dest.setFocusable(src.isFocusable());
1590            dest.setFocused(src.isFocused());
1591            dest.setAccessibilityFocused(src.isAccessibilityFocused());
1592            dest.setSelected(src.isSelected());
1593            dest.setLongClickable(src.isLongClickable());
1594
1595            dest.addAction(src.getActions());
1596        }
1597    }
1598}
1599