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