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