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