DrawerLayout.java revision aeaa8ef97eb729fbee17e984d60e9ab27795f5d0
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
524            // Only send WINDOW_STATE_CHANGE if the host has window focus. This
525            // may change if support for multiple foreground windows (e.g. IME)
526            // improves.
527            if (hasWindowFocus()) {
528                final View rootView = getRootView();
529                if (rootView != null) {
530                    rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
531                }
532            }
533        }
534    }
535
536    void dispatchOnDrawerOpened(View drawerView) {
537        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
538        if (!lp.knownOpen) {
539            lp.knownOpen = true;
540            if (mListener != null) {
541                mListener.onDrawerOpened(drawerView);
542            }
543            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
544        }
545    }
546
547    void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
548        if (mListener != null) {
549            mListener.onDrawerSlide(drawerView, slideOffset);
550        }
551    }
552
553    void setDrawerViewOffset(View drawerView, float slideOffset) {
554        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
555        if (slideOffset == lp.onScreen) {
556            return;
557        }
558
559        lp.onScreen = slideOffset;
560        dispatchOnDrawerSlide(drawerView, slideOffset);
561    }
562
563    float getDrawerViewOffset(View drawerView) {
564        return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
565    }
566
567    /**
568     * @return the absolute gravity of the child drawerView, resolved according
569     *         to the current layout direction
570     */
571    int getDrawerViewAbsoluteGravity(View drawerView) {
572        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
573        return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
574    }
575
576    boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
577        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
578        return (absGravity & checkFor) == checkFor;
579    }
580
581    View findOpenDrawer() {
582        final int childCount = getChildCount();
583        for (int i = 0; i < childCount; i++) {
584            final View child = getChildAt(i);
585            if (((LayoutParams) child.getLayoutParams()).knownOpen) {
586                return child;
587            }
588        }
589        return null;
590    }
591
592    void moveDrawerToOffset(View drawerView, float slideOffset) {
593        final float oldOffset = getDrawerViewOffset(drawerView);
594        final int width = drawerView.getWidth();
595        final int oldPos = (int) (width * oldOffset);
596        final int newPos = (int) (width * slideOffset);
597        final int dx = newPos - oldPos;
598
599        drawerView.offsetLeftAndRight(
600                checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
601        setDrawerViewOffset(drawerView, slideOffset);
602    }
603
604    /**
605     * @param gravity the gravity of the child to return. If specified as a
606     *            relative value, it will be resolved according to the current
607     *            layout direction.
608     * @return the drawer with the specified gravity
609     */
610    View findDrawerWithGravity(int gravity) {
611        final int absHorizGravity = GravityCompat.getAbsoluteGravity(
612                gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
613        final int childCount = getChildCount();
614        for (int i = 0; i < childCount; i++) {
615            final View child = getChildAt(i);
616            final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
617            if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
618                return child;
619            }
620        }
621        return null;
622    }
623
624    /**
625     * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
626     *
627     * @param gravity Absolute gravity value
628     * @return LEFT or RIGHT as appropriate, or a hex string
629     */
630    static String gravityToString(int gravity) {
631        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
632            return "LEFT";
633        }
634        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
635            return "RIGHT";
636        }
637        return Integer.toHexString(gravity);
638    }
639
640    @Override
641    protected void onDetachedFromWindow() {
642        super.onDetachedFromWindow();
643        mFirstLayout = true;
644    }
645
646    @Override
647    protected void onAttachedToWindow() {
648        super.onAttachedToWindow();
649        mFirstLayout = true;
650    }
651
652    @Override
653    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
654        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
655        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
656        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
657        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
658
659        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
660            if (isInEditMode()) {
661                // Don't crash the layout editor. Consume all of the space if specified
662                // or pick a magic number from thin air otherwise.
663                // TODO Better communication with tools of this bogus state.
664                // It will crash on a real device.
665                if (widthMode == MeasureSpec.AT_MOST) {
666                    widthMode = MeasureSpec.EXACTLY;
667                } else if (widthMode == MeasureSpec.UNSPECIFIED) {
668                    widthMode = MeasureSpec.EXACTLY;
669                    widthSize = 300;
670                }
671                if (heightMode == MeasureSpec.AT_MOST) {
672                    heightMode = MeasureSpec.EXACTLY;
673                }
674                else if (heightMode == MeasureSpec.UNSPECIFIED) {
675                    heightMode = MeasureSpec.EXACTLY;
676                    heightSize = 300;
677                }
678            } else {
679                throw new IllegalArgumentException(
680                        "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
681            }
682        }
683
684        setMeasuredDimension(widthSize, heightSize);
685
686        // Gravity value for each drawer we've seen. Only one of each permitted.
687        int foundDrawers = 0;
688        final int childCount = getChildCount();
689        for (int i = 0; i < childCount; i++) {
690            final View child = getChildAt(i);
691
692            if (child.getVisibility() == GONE) {
693                continue;
694            }
695
696            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
697
698            if (isContentView(child)) {
699                // Content views get measured at exactly the layout's size.
700                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
701                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
702                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
703                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
704                child.measure(contentWidthSpec, contentHeightSpec);
705            } else if (isDrawerView(child)) {
706                final int childGravity =
707                        getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
708                if ((foundDrawers & childGravity) != 0) {
709                    throw new IllegalStateException("Child drawer has absolute gravity " +
710                            gravityToString(childGravity) + " but this " + TAG + " already has a " +
711                            "drawer view along that edge");
712                }
713                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
714                        mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
715                        lp.width);
716                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
717                        lp.topMargin + lp.bottomMargin,
718                        lp.height);
719                child.measure(drawerWidthSpec, drawerHeightSpec);
720            } else {
721                throw new IllegalStateException("Child " + child + " at index " + i +
722                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
723                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
724            }
725        }
726    }
727
728    @Override
729    protected void onLayout(boolean changed, int l, int t, int r, int b) {
730        mInLayout = true;
731        final int width = r - l;
732        final int childCount = getChildCount();
733        for (int i = 0; i < childCount; i++) {
734            final View child = getChildAt(i);
735
736            if (child.getVisibility() == GONE) {
737                continue;
738            }
739
740            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
741
742            if (isContentView(child)) {
743                child.layout(lp.leftMargin, lp.topMargin,
744                        lp.leftMargin + child.getMeasuredWidth(),
745                        lp.topMargin + child.getMeasuredHeight());
746            } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
747                final int childWidth = child.getMeasuredWidth();
748                final int childHeight = child.getMeasuredHeight();
749                int childLeft;
750
751                final float newOffset;
752                if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
753                    childLeft = -childWidth + (int) (childWidth * lp.onScreen);
754                    newOffset = (float) (childWidth + childLeft) / childWidth;
755                } else { // Right; onMeasure checked for us.
756                    childLeft = width - (int) (childWidth * lp.onScreen);
757                    newOffset = (float) (width - childLeft) / childWidth;
758                }
759
760                final boolean changeOffset = newOffset != lp.onScreen;
761
762                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
763
764                switch (vgrav) {
765                    default:
766                    case Gravity.TOP: {
767                        child.layout(childLeft, lp.topMargin, childLeft + childWidth,
768                                lp.topMargin + childHeight);
769                        break;
770                    }
771
772                    case Gravity.BOTTOM: {
773                        final int height = b - t;
774                        child.layout(childLeft,
775                                height - lp.bottomMargin - child.getMeasuredHeight(),
776                                childLeft + childWidth,
777                                height - lp.bottomMargin);
778                        break;
779                    }
780
781                    case Gravity.CENTER_VERTICAL: {
782                        final int height = b - t;
783                        int childTop = (height - childHeight) / 2;
784
785                        // Offset for margins. If things don't fit right because of
786                        // bad measurement before, oh well.
787                        if (childTop < lp.topMargin) {
788                            childTop = lp.topMargin;
789                        } else if (childTop + childHeight > height - lp.bottomMargin) {
790                            childTop = height - lp.bottomMargin - childHeight;
791                        }
792                        child.layout(childLeft, childTop, childLeft + childWidth,
793                                childTop + childHeight);
794                        break;
795                    }
796                }
797
798                if (changeOffset) {
799                    setDrawerViewOffset(child, newOffset);
800                }
801
802                final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
803                if (child.getVisibility() != newVisibility) {
804                    child.setVisibility(newVisibility);
805                }
806            }
807        }
808        mInLayout = false;
809        mFirstLayout = false;
810    }
811
812    @Override
813    public void requestLayout() {
814        if (!mInLayout) {
815            super.requestLayout();
816        }
817    }
818
819    @Override
820    public void computeScroll() {
821        final int childCount = getChildCount();
822        float scrimOpacity = 0;
823        for (int i = 0; i < childCount; i++) {
824            final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
825            scrimOpacity = Math.max(scrimOpacity, onscreen);
826        }
827        mScrimOpacity = scrimOpacity;
828
829        // "|" used on purpose; both need to run.
830        if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
831            ViewCompat.postInvalidateOnAnimation(this);
832        }
833    }
834
835    private static boolean hasOpaqueBackground(View v) {
836        final Drawable bg = v.getBackground();
837        if (bg != null) {
838            return bg.getOpacity() == PixelFormat.OPAQUE;
839        }
840        return false;
841    }
842
843    @Override
844    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
845        final int height = getHeight();
846        final boolean drawingContent = isContentView(child);
847        int clipLeft = 0, clipRight = getWidth();
848
849        final int restoreCount = canvas.save();
850        if (drawingContent) {
851            final int childCount = getChildCount();
852            for (int i = 0; i < childCount; i++) {
853                final View v = getChildAt(i);
854                if (v == child || v.getVisibility() != VISIBLE ||
855                        !hasOpaqueBackground(v) || !isDrawerView(v) ||
856                        v.getHeight() < height) {
857                    continue;
858                }
859
860                if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
861                    final int vright = v.getRight();
862                    if (vright > clipLeft) clipLeft = vright;
863                } else {
864                    final int vleft = v.getLeft();
865                    if (vleft < clipRight) clipRight = vleft;
866                }
867            }
868            canvas.clipRect(clipLeft, 0, clipRight, getHeight());
869        }
870        final boolean result = super.drawChild(canvas, child, drawingTime);
871        canvas.restoreToCount(restoreCount);
872
873        if (mScrimOpacity > 0 && drawingContent) {
874            final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
875            final int imag = (int) (baseAlpha * mScrimOpacity);
876            final int color = imag << 24 | (mScrimColor & 0xffffff);
877            mScrimPaint.setColor(color);
878
879            canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
880        } else if (mShadowLeft != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
881            final int shadowWidth = mShadowLeft.getIntrinsicWidth();
882            final int childRight = child.getRight();
883            final int drawerPeekDistance = mLeftDragger.getEdgeSize();
884            final float alpha =
885                    Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
886            mShadowLeft.setBounds(childRight, child.getTop(),
887                    childRight + shadowWidth, child.getBottom());
888            mShadowLeft.setAlpha((int) (0xff * alpha));
889            mShadowLeft.draw(canvas);
890        } else if (mShadowRight != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
891            final int shadowWidth = mShadowRight.getIntrinsicWidth();
892            final int childLeft = child.getLeft();
893            final int showing = getWidth() - childLeft;
894            final int drawerPeekDistance = mRightDragger.getEdgeSize();
895            final float alpha =
896                    Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
897            mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(),
898                    childLeft, child.getBottom());
899            mShadowRight.setAlpha((int) (0xff * alpha));
900            mShadowRight.draw(canvas);
901        }
902        return result;
903    }
904
905    boolean isContentView(View child) {
906        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
907    }
908
909    boolean isDrawerView(View child) {
910        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
911        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
912                ViewCompat.getLayoutDirection(child));
913        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
914    }
915
916    @Override
917    public boolean onInterceptTouchEvent(MotionEvent ev) {
918        final int action = MotionEventCompat.getActionMasked(ev);
919
920        // "|" used deliberately here; both methods should be invoked.
921        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) |
922                mRightDragger.shouldInterceptTouchEvent(ev);
923
924        boolean interceptForTap = false;
925
926        switch (action) {
927            case MotionEvent.ACTION_DOWN: {
928                final float x = ev.getX();
929                final float y = ev.getY();
930                mInitialMotionX = x;
931                mInitialMotionY = y;
932                if (mScrimOpacity > 0 &&
933                        isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
934                    interceptForTap = true;
935                }
936                mDisallowInterceptRequested = false;
937                mChildrenCanceledTouch = false;
938                break;
939            }
940
941            case MotionEvent.ACTION_MOVE: {
942                // If we cross the touch slop, don't perform the delayed peek for an edge touch.
943                if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
944                    mLeftCallback.removeCallbacks();
945                    mRightCallback.removeCallbacks();
946                }
947                break;
948            }
949
950            case MotionEvent.ACTION_CANCEL:
951            case MotionEvent.ACTION_UP: {
952                closeDrawers(true);
953                mDisallowInterceptRequested = false;
954                mChildrenCanceledTouch = false;
955            }
956        }
957
958        return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
959    }
960
961    @Override
962    public boolean onTouchEvent(MotionEvent ev) {
963        mLeftDragger.processTouchEvent(ev);
964        mRightDragger.processTouchEvent(ev);
965
966        final int action = ev.getAction();
967        boolean wantTouchEvents = true;
968
969        switch (action & MotionEventCompat.ACTION_MASK) {
970            case MotionEvent.ACTION_DOWN: {
971                final float x = ev.getX();
972                final float y = ev.getY();
973                mInitialMotionX = x;
974                mInitialMotionY = y;
975                mDisallowInterceptRequested = false;
976                mChildrenCanceledTouch = false;
977                break;
978            }
979
980            case MotionEvent.ACTION_UP: {
981                final float x = ev.getX();
982                final float y = ev.getY();
983                boolean peekingOnly = true;
984                final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
985                if (touchedView != null && isContentView(touchedView)) {
986                    final float dx = x - mInitialMotionX;
987                    final float dy = y - mInitialMotionY;
988                    final int slop = mLeftDragger.getTouchSlop();
989                    if (dx * dx + dy * dy < slop * slop) {
990                        // Taps close a dimmed open drawer but only if it isn't locked open.
991                        final View openDrawer = findOpenDrawer();
992                        if (openDrawer != null) {
993                            peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
994                        }
995                    }
996                }
997                closeDrawers(peekingOnly);
998                mDisallowInterceptRequested = false;
999                break;
1000            }
1001
1002            case MotionEvent.ACTION_CANCEL: {
1003                closeDrawers(true);
1004                mDisallowInterceptRequested = false;
1005                mChildrenCanceledTouch = false;
1006                break;
1007            }
1008        }
1009
1010        return wantTouchEvents;
1011    }
1012
1013    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1014        if (CHILDREN_DISALLOW_INTERCEPT ||
1015                (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) &&
1016                !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
1017            // If we have an edge touch we want to skip this and track it for later instead.
1018            super.requestDisallowInterceptTouchEvent(disallowIntercept);
1019        }
1020        mDisallowInterceptRequested = disallowIntercept;
1021        if (disallowIntercept) {
1022            closeDrawers(true);
1023        }
1024    }
1025
1026    /**
1027     * Close all currently open drawer views by animating them out of view.
1028     */
1029    public void closeDrawers() {
1030        closeDrawers(false);
1031    }
1032
1033    void closeDrawers(boolean peekingOnly) {
1034        boolean needsInvalidate = false;
1035        final int childCount = getChildCount();
1036        for (int i = 0; i < childCount; i++) {
1037            final View child = getChildAt(i);
1038            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1039
1040            if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
1041                continue;
1042            }
1043
1044            final int childWidth = child.getWidth();
1045
1046            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1047                needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
1048                        -childWidth, child.getTop());
1049            } else {
1050                needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
1051                        getWidth(), child.getTop());
1052            }
1053
1054            lp.isPeeking = false;
1055        }
1056
1057        mLeftCallback.removeCallbacks();
1058        mRightCallback.removeCallbacks();
1059
1060        if (needsInvalidate) {
1061            invalidate();
1062        }
1063    }
1064
1065    /**
1066     * Open the specified drawer view by animating it into view.
1067     *
1068     * @param drawerView Drawer view to open
1069     */
1070    public void openDrawer(View drawerView) {
1071        if (!isDrawerView(drawerView)) {
1072            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1073        }
1074
1075        if (mFirstLayout) {
1076            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1077            lp.onScreen = 1.f;
1078            lp.knownOpen = true;
1079        } else {
1080            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1081                mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
1082            } else {
1083                mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
1084                        drawerView.getTop());
1085            }
1086        }
1087        invalidate();
1088    }
1089
1090    /**
1091     * Open the specified drawer by animating it out of view.
1092     *
1093     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1094     *                GravityCompat.START or GravityCompat.END may also be used.
1095     */
1096    public void openDrawer(int gravity) {
1097        final View drawerView = findDrawerWithGravity(gravity);
1098        if (drawerView == null) {
1099            throw new IllegalArgumentException("No drawer view found with gravity " +
1100                    gravityToString(gravity));
1101        }
1102        openDrawer(drawerView);
1103    }
1104
1105    /**
1106     * Close the specified drawer view by animating it into view.
1107     *
1108     * @param drawerView Drawer view to close
1109     */
1110    public void closeDrawer(View drawerView) {
1111        if (!isDrawerView(drawerView)) {
1112            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
1113        }
1114
1115        if (mFirstLayout) {
1116            final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
1117            lp.onScreen = 0.f;
1118            lp.knownOpen = false;
1119        } else {
1120            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
1121                mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
1122                        drawerView.getTop());
1123            } else {
1124                mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
1125            }
1126        }
1127        invalidate();
1128    }
1129
1130    /**
1131     * Close the specified drawer by animating it out of view.
1132     *
1133     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
1134     *                GravityCompat.START or GravityCompat.END may also be used.
1135     */
1136    public void closeDrawer(int gravity) {
1137        final View drawerView = findDrawerWithGravity(gravity);
1138        if (drawerView == null) {
1139            throw new IllegalArgumentException("No drawer view found with gravity " +
1140                    gravityToString(gravity));
1141        }
1142        closeDrawer(drawerView);
1143    }
1144
1145    /**
1146     * Check if the given drawer view is currently in an open state.
1147     * To be considered "open" the drawer must have settled into its fully
1148     * visible state. To check for partial visibility use
1149     * {@link #isDrawerVisible(android.view.View)}.
1150     *
1151     * @param drawer Drawer view to check
1152     * @return true if the given drawer view is in an open state
1153     * @see #isDrawerVisible(android.view.View)
1154     */
1155    public boolean isDrawerOpen(View drawer) {
1156        if (!isDrawerView(drawer)) {
1157            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1158        }
1159        return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
1160    }
1161
1162    /**
1163     * Check if the given drawer view is currently in an open state.
1164     * To be considered "open" the drawer must have settled into its fully
1165     * visible state. If there is no drawer with the given gravity this method
1166     * will return false.
1167     *
1168     * @param drawerGravity Gravity of the drawer to check
1169     * @return true if the given drawer view is in an open state
1170     */
1171    public boolean isDrawerOpen(int drawerGravity) {
1172        final View drawerView = findDrawerWithGravity(drawerGravity);
1173        if (drawerView != null) {
1174            return isDrawerOpen(drawerView);
1175        }
1176        return false;
1177    }
1178
1179    /**
1180     * Check if a given drawer view is currently visible on-screen. The drawer
1181     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
1182     *
1183     * @param drawer Drawer view to check
1184     * @return true if the given drawer is visible on-screen
1185     * @see #isDrawerOpen(android.view.View)
1186     */
1187    public boolean isDrawerVisible(View drawer) {
1188        if (!isDrawerView(drawer)) {
1189            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
1190        }
1191        return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
1192    }
1193
1194    /**
1195     * Check if a given drawer view is currently visible on-screen. The drawer
1196     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
1197     * If there is no drawer with the given gravity this method will return false.
1198     *
1199     * @param drawerGravity Gravity of the drawer to check
1200     * @return true if the given drawer is visible on-screen
1201     */
1202    public boolean isDrawerVisible(int drawerGravity) {
1203        final View drawerView = findDrawerWithGravity(drawerGravity);
1204        if (drawerView != null) {
1205            return isDrawerVisible(drawerView);
1206        }
1207        return false;
1208    }
1209
1210    private boolean hasPeekingDrawer() {
1211        final int childCount = getChildCount();
1212        for (int i = 0; i < childCount; i++) {
1213            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
1214            if (lp.isPeeking) {
1215                return true;
1216            }
1217        }
1218        return false;
1219    }
1220
1221    @Override
1222    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1223        return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
1224    }
1225
1226    @Override
1227    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1228        return p instanceof LayoutParams
1229                ? new LayoutParams((LayoutParams) p)
1230                : p instanceof ViewGroup.MarginLayoutParams
1231                ? new LayoutParams((MarginLayoutParams) p)
1232                : new LayoutParams(p);
1233    }
1234
1235    @Override
1236    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1237        return p instanceof LayoutParams && super.checkLayoutParams(p);
1238    }
1239
1240    @Override
1241    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1242        return new LayoutParams(getContext(), attrs);
1243    }
1244
1245    private boolean hasVisibleDrawer() {
1246        return findVisibleDrawer() != null;
1247    }
1248
1249    private View findVisibleDrawer() {
1250        final int childCount = getChildCount();
1251        for (int i = 0; i < childCount; i++) {
1252            final View child = getChildAt(i);
1253            if (isDrawerView(child) && isDrawerVisible(child)) {
1254                return child;
1255            }
1256        }
1257        return null;
1258    }
1259
1260    void cancelChildViewTouch() {
1261        // Cancel child touches
1262        if (!mChildrenCanceledTouch) {
1263            final long now = SystemClock.uptimeMillis();
1264            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
1265                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
1266            final int childCount = getChildCount();
1267            for (int i = 0; i < childCount; i++) {
1268                getChildAt(i).dispatchTouchEvent(cancelEvent);
1269            }
1270            cancelEvent.recycle();
1271            mChildrenCanceledTouch = true;
1272        }
1273    }
1274
1275    @Override
1276    public boolean onKeyDown(int keyCode, KeyEvent event) {
1277        if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
1278            KeyEventCompat.startTracking(event);
1279            return true;
1280        }
1281        return super.onKeyDown(keyCode, event);
1282    }
1283
1284    @Override
1285    public boolean onKeyUp(int keyCode, KeyEvent event) {
1286        if (keyCode == KeyEvent.KEYCODE_BACK) {
1287            final View visibleDrawer = findVisibleDrawer();
1288            if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
1289                closeDrawers();
1290            }
1291            return visibleDrawer != null;
1292        }
1293        return super.onKeyUp(keyCode, event);
1294    }
1295
1296    @Override
1297    protected void onRestoreInstanceState(Parcelable state) {
1298        final SavedState ss = (SavedState) state;
1299        super.onRestoreInstanceState(ss.getSuperState());
1300
1301        if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
1302            final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
1303            if (toOpen != null) {
1304                openDrawer(toOpen);
1305            }
1306        }
1307
1308        setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
1309        setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
1310    }
1311
1312    @Override
1313    protected Parcelable onSaveInstanceState() {
1314        final Parcelable superState = super.onSaveInstanceState();
1315
1316        final SavedState ss = new SavedState(superState);
1317
1318        final int childCount = getChildCount();
1319        for (int i = 0; i < childCount; i++) {
1320            final View child = getChildAt(i);
1321            if (!isDrawerView(child)) {
1322                continue;
1323            }
1324
1325            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1326            if (lp.knownOpen) {
1327                ss.openDrawerGravity = lp.gravity;
1328                // Only one drawer can be open at a time.
1329                break;
1330            }
1331        }
1332
1333        ss.lockModeLeft = mLockModeLeft;
1334        ss.lockModeRight = mLockModeRight;
1335
1336        return ss;
1337    }
1338
1339    /**
1340     * State persisted across instances
1341     */
1342    protected static class SavedState extends BaseSavedState {
1343        int openDrawerGravity = Gravity.NO_GRAVITY;
1344        int lockModeLeft = LOCK_MODE_UNLOCKED;
1345        int lockModeRight = LOCK_MODE_UNLOCKED;
1346
1347        public SavedState(Parcel in) {
1348            super(in);
1349            openDrawerGravity = in.readInt();
1350        }
1351
1352        public SavedState(Parcelable superState) {
1353            super(superState);
1354        }
1355
1356        @Override
1357        public void writeToParcel(Parcel dest, int flags) {
1358            super.writeToParcel(dest, flags);
1359            dest.writeInt(openDrawerGravity);
1360        }
1361
1362        public static final Parcelable.Creator<SavedState> CREATOR =
1363                new Parcelable.Creator<SavedState>() {
1364            @Override
1365            public SavedState createFromParcel(Parcel source) {
1366                return new SavedState(source);
1367            }
1368
1369            @Override
1370            public SavedState[] newArray(int size) {
1371                return new SavedState[size];
1372            }
1373        };
1374    }
1375
1376    private class ViewDragCallback extends ViewDragHelper.Callback {
1377        private final int mAbsGravity;
1378        private ViewDragHelper mDragger;
1379
1380        private final Runnable mPeekRunnable = new Runnable() {
1381            @Override public void run() {
1382                peekDrawer();
1383            }
1384        };
1385
1386        public ViewDragCallback(int gravity) {
1387            mAbsGravity = gravity;
1388        }
1389
1390        public void setDragger(ViewDragHelper dragger) {
1391            mDragger = dragger;
1392        }
1393
1394        public void removeCallbacks() {
1395            DrawerLayout.this.removeCallbacks(mPeekRunnable);
1396        }
1397
1398        @Override
1399        public boolean tryCaptureView(View child, int pointerId) {
1400            // Only capture views where the gravity matches what we're looking for.
1401            // This lets us use two ViewDragHelpers, one for each side drawer.
1402            return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
1403                    && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
1404        }
1405
1406        @Override
1407        public void onViewDragStateChanged(int state) {
1408            updateDrawerState(mAbsGravity, state, mDragger.getCapturedView());
1409        }
1410
1411        @Override
1412        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1413            float offset;
1414            final int childWidth = changedView.getWidth();
1415
1416            // This reverses the positioning shown in onLayout.
1417            if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
1418                offset = (float) (childWidth + left) / childWidth;
1419            } else {
1420                final int width = getWidth();
1421                offset = (float) (width - left) / childWidth;
1422            }
1423            setDrawerViewOffset(changedView, offset);
1424            changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
1425            invalidate();
1426        }
1427
1428        @Override
1429        public void onViewCaptured(View capturedChild, int activePointerId) {
1430            final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
1431            lp.isPeeking = false;
1432
1433            closeOtherDrawer();
1434        }
1435
1436        private void closeOtherDrawer() {
1437            final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
1438            final View toClose = findDrawerWithGravity(otherGrav);
1439            if (toClose != null) {
1440                closeDrawer(toClose);
1441            }
1442        }
1443
1444        @Override
1445        public void onViewReleased(View releasedChild, float xvel, float yvel) {
1446            // Offset is how open the drawer is, therefore left/right values
1447            // are reversed from one another.
1448            final float offset = getDrawerViewOffset(releasedChild);
1449            final int childWidth = releasedChild.getWidth();
1450
1451            int left;
1452            if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
1453                left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth;
1454            } else {
1455                final int width = getWidth();
1456                left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width;
1457            }
1458
1459            mDragger.settleCapturedViewAt(left, releasedChild.getTop());
1460            invalidate();
1461        }
1462
1463        @Override
1464        public void onEdgeTouched(int edgeFlags, int pointerId) {
1465            postDelayed(mPeekRunnable, PEEK_DELAY);
1466        }
1467
1468        private void peekDrawer() {
1469            final View toCapture;
1470            final int childLeft;
1471            final int peekDistance = mDragger.getEdgeSize();
1472            final boolean leftEdge = mAbsGravity == Gravity.LEFT;
1473            if (leftEdge) {
1474                toCapture = findDrawerWithGravity(Gravity.LEFT);
1475                childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
1476            } else {
1477                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1478                childLeft = getWidth() - peekDistance;
1479            }
1480            // Only peek if it would mean making the drawer more visible and the drawer isn't locked
1481            if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) ||
1482                    (!leftEdge && toCapture.getLeft() > childLeft)) &&
1483                    getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1484                final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
1485                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
1486                lp.isPeeking = true;
1487                invalidate();
1488
1489                closeOtherDrawer();
1490
1491                cancelChildViewTouch();
1492            }
1493        }
1494
1495        @Override
1496        public boolean onEdgeLock(int edgeFlags) {
1497            if (ALLOW_EDGE_LOCK) {
1498                final View drawer = findDrawerWithGravity(mAbsGravity);
1499                if (drawer != null && !isDrawerOpen(drawer)) {
1500                    closeDrawer(drawer);
1501                }
1502                return true;
1503            }
1504            return false;
1505        }
1506
1507        @Override
1508        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
1509            final View toCapture;
1510            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
1511                toCapture = findDrawerWithGravity(Gravity.LEFT);
1512            } else {
1513                toCapture = findDrawerWithGravity(Gravity.RIGHT);
1514            }
1515
1516            if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
1517                mDragger.captureChildView(toCapture, pointerId);
1518            }
1519        }
1520
1521        @Override
1522        public int getViewHorizontalDragRange(View child) {
1523            return child.getWidth();
1524        }
1525
1526        @Override
1527        public int clampViewPositionHorizontal(View child, int left, int dx) {
1528            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
1529                return Math.max(-child.getWidth(), Math.min(left, 0));
1530            } else {
1531                final int width = getWidth();
1532                return Math.max(width - child.getWidth(), Math.min(left, width));
1533            }
1534        }
1535
1536        @Override
1537        public int clampViewPositionVertical(View child, int top, int dy) {
1538            return child.getTop();
1539        }
1540    }
1541
1542    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1543
1544        public int gravity = Gravity.NO_GRAVITY;
1545        float onScreen;
1546        boolean isPeeking;
1547        boolean knownOpen;
1548
1549        public LayoutParams(Context c, AttributeSet attrs) {
1550            super(c, attrs);
1551
1552            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
1553            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
1554            a.recycle();
1555        }
1556
1557        public LayoutParams(int width, int height) {
1558            super(width, height);
1559        }
1560
1561        public LayoutParams(int width, int height, int gravity) {
1562            this(width, height);
1563            this.gravity = gravity;
1564        }
1565
1566        public LayoutParams(LayoutParams source) {
1567            super(source);
1568            this.gravity = source.gravity;
1569        }
1570
1571        public LayoutParams(ViewGroup.LayoutParams source) {
1572            super(source);
1573        }
1574
1575        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1576            super(source);
1577        }
1578    }
1579
1580    class AccessibilityDelegate extends AccessibilityDelegateCompat {
1581        private final Rect mTmpRect = new Rect();
1582
1583        @Override
1584        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
1585            final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
1586            super.onInitializeAccessibilityNodeInfo(host, superNode);
1587
1588            info.setClassName(DrawerLayout.class.getName());
1589            info.setSource(host);
1590            final ViewParent parent = ViewCompat.getParentForAccessibility(host);
1591            if (parent instanceof View) {
1592                info.setParent((View) parent);
1593            }
1594            copyNodeInfoNoChildren(info, superNode);
1595
1596            superNode.recycle();
1597
1598            addChildrenForAccessibility(info, (ViewGroup) host);
1599        }
1600
1601        @Override
1602        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
1603            super.onInitializeAccessibilityEvent(host, event);
1604
1605            event.setClassName(DrawerLayout.class.getName());
1606        }
1607
1608        @Override
1609        public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
1610            // Special case to handle window state change events. As far as
1611            // accessibility services are concerned, state changes from
1612            // DrawerLayout invalidate the entire contents of the screen (like
1613            // an Activity or Dialog) and they should announce the title of the
1614            // new content.
1615            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1616                final List<CharSequence> eventText = event.getText();
1617                final View visibleDrawer = findVisibleDrawer();
1618                if (visibleDrawer != null) {
1619                    final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer);
1620                    final CharSequence title = getDrawerTitle(edgeGravity);
1621                    if (title != null) {
1622                        eventText.add(title);
1623                    }
1624                }
1625
1626                return true;
1627            }
1628
1629            return super.dispatchPopulateAccessibilityEvent(host, event);
1630        }
1631
1632        private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
1633            final int childCount = v.getChildCount();
1634            for (int i = 0; i < childCount; i++) {
1635                final View child = v.getChildAt(i);
1636                if (filter(child)) {
1637                    continue;
1638                }
1639
1640                // Adding children that are marked as not important for
1641                // accessibility will break the hierarchy, so we need to check
1642                // that value and re-parent views if necessary.
1643                final int importance = ViewCompat.getImportantForAccessibility(child);
1644                switch (importance) {
1645                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS:
1646                        // Always skip NO_HIDE views and their descendants.
1647                        break;
1648                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO:
1649                        // Re-parent children of NO view groups, skip NO views.
1650                        if (child instanceof ViewGroup) {
1651                            addChildrenForAccessibility(info, (ViewGroup) child);
1652                        }
1653                        break;
1654                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO:
1655                        // Force AUTO views to YES and add them.
1656                        ViewCompat.setImportantForAccessibility(
1657                                child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
1658                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES:
1659                        info.addChild(child);
1660                        break;
1661                }
1662            }
1663        }
1664
1665        @Override
1666        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
1667                AccessibilityEvent event) {
1668            if (!filter(child)) {
1669                return super.onRequestSendAccessibilityEvent(host, child, event);
1670            }
1671            return false;
1672        }
1673
1674        public boolean filter(View child) {
1675            final View openDrawer = findOpenDrawer();
1676            return openDrawer != null && openDrawer != child;
1677        }
1678
1679        /**
1680         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
1681         * seem to be a few elements that are not easily cloneable using the underlying API.
1682         * Leave it private here as it's not general-purpose useful.
1683         */
1684        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
1685                AccessibilityNodeInfoCompat src) {
1686            final Rect rect = mTmpRect;
1687
1688            src.getBoundsInParent(rect);
1689            dest.setBoundsInParent(rect);
1690
1691            src.getBoundsInScreen(rect);
1692            dest.setBoundsInScreen(rect);
1693
1694            dest.setVisibleToUser(src.isVisibleToUser());
1695            dest.setPackageName(src.getPackageName());
1696            dest.setClassName(src.getClassName());
1697            dest.setContentDescription(src.getContentDescription());
1698
1699            dest.setEnabled(src.isEnabled());
1700            dest.setClickable(src.isClickable());
1701            dest.setFocusable(src.isFocusable());
1702            dest.setFocused(src.isFocused());
1703            dest.setAccessibilityFocused(src.isAccessibilityFocused());
1704            dest.setSelected(src.isSelected());
1705            dest.setLongClickable(src.isLongClickable());
1706
1707            dest.addAction(src.getActions());
1708        }
1709    }
1710}
1711