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