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