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