public class

DrawerLayout

extends ViewGroup

implements Openable

 java.lang.Object

↳ViewGroup

↳androidx.drawerlayout.widget.DrawerLayout

Gradle dependencies

compile group: 'androidx.drawerlayout', name: 'drawerlayout', version: '1.2.0'

  • groupId: androidx.drawerlayout
  • artifactId: drawerlayout
  • version: 1.2.0

Artifact androidx.drawerlayout:drawerlayout:1.2.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.drawerlayout:drawerlayout com.android.support:drawerlayout

Androidx class mapping:

androidx.drawerlayout.widget.DrawerLayout android.support.v4.widget.DrawerLayout

Overview

DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled out from one or both vertical edges of the window.

Drawer positioning and layout is controlled using the android:layout_gravity attribute on child views corresponding to which side of the view you want the drawer to emerge from: left or right (or start/end on platform versions that support layout direction.) Note that you can only have one drawer view for each vertical edge of the window. If your layout configures more than one drawer view per vertical edge of the window, an exception will be thrown at runtime.

To use a DrawerLayout, position your primary content view as the first child with width and height of match_parent and no layout_gravity>. Add drawers as child views after the main content view and set the layout_gravity appropriately. Drawers commonly use match_parent for height with a fixed width.

DrawerLayout.DrawerListener can be used to monitor the state and motion of drawer views. Avoid performing expensive operations such as layout during animation as it can cause stuttering; try to perform expensive operations during the DrawerLayout.STATE_IDLE state. DrawerLayout.SimpleDrawerListener offers default/no-op implementations of each callback method.

As per the Android Design guide, any drawers positioned to the left/start should always contain content for navigating around the application, whereas any drawers positioned to the right/end should always contain actions to take on the current content. This preserves the same navigation left, actions right structure present in the Action Bar and elsewhere.

For more information about how to use DrawerLayout, read Creating a Navigation Drawer.

Summary

Fields
public static final intLOCK_MODE_LOCKED_CLOSED

The drawer is locked closed.

public static final intLOCK_MODE_LOCKED_OPEN

The drawer is locked open.

public static final intLOCK_MODE_UNDEFINED

The drawer's lock state is reset to default.

public static final intLOCK_MODE_UNLOCKED

The drawer is unlocked.

public static final intSTATE_DRAGGING

Indicates that a drawer is currently being dragged by the user.

public static final intSTATE_IDLE

Indicates that any drawers are in an idle, settled state.

public static final intSTATE_SETTLING

Indicates that a drawer is in the process of settling to a final position.

Constructors
publicDrawerLayout(Context context)

publicDrawerLayout(Context context, AttributeSet attrs)

publicDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public voidaddDrawerListener(DrawerLayout.DrawerListener listener)

Adds the specified listener to the list of listeners that will be notified of drawer events.

public voidaddFocusables(java.util.ArrayList<View> views, int direction, int focusableMode)

public voidaddView(View child, int index, ViewGroup.LayoutParams params)

protected booleancheckLayoutParams(ViewGroup.LayoutParams p)

public voidclose()

Close the GravityCompat.START drawer by animating it out of view.

public voidcloseDrawer(View drawerView)

Close the specified drawer view by animating it into view.

public voidcloseDrawer(View drawerView, boolean animate)

Close the specified drawer view.

public voidcloseDrawers()

Close all currently open drawer views by animating them out of view.

public voidcomputeScroll()

public booleandispatchGenericMotionEvent(MotionEvent event)

protected booleandrawChild(Canvas canvas, View child, long drawingTime)

protected ViewGroup.LayoutParamsgenerateDefaultLayoutParams()

public ViewGroup.LayoutParamsgenerateLayoutParams(AttributeSet attrs)

protected ViewGroup.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p)

public floatgetDrawerElevation()

The base elevation of the drawer(s) relative to the parent, in pixels.

public intgetDrawerLockMode(int edgeGravity)

Check the lock mode of the drawer with the given gravity.

public intgetDrawerLockMode(View drawerView)

Check the lock mode of the given drawer view.

public java.lang.CharSequencegetDrawerTitle(int edgeGravity)

Returns the title of the drawer with the given gravity.

public DrawablegetStatusBarBackgroundDrawable()

Gets the drawable used to draw in the insets area for the status bar.

public booleanisDrawerOpen(View drawer)

Check if the given drawer view is currently in an open state.

public booleanisDrawerVisible(View drawer)

Check if a given drawer view is currently visible on-screen.

public booleanisOpen()

Check if the GravityCompat.START drawer is currently in an open state.

protected voidonAttachedToWindow()

protected voidonDetachedFromWindow()

public voidonDraw(Canvas c)

public booleanonInterceptTouchEvent(MotionEvent ev)

public booleanonKeyDown(int keyCode, KeyEvent event)

public booleanonKeyUp(int keyCode, KeyEvent event)

protected voidonLayout(boolean changed, int l, int t, int r, int b)

protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

protected voidonRestoreInstanceState(Parcelable state)

public voidonRtlPropertiesChanged(int layoutDirection)

protected ParcelableonSaveInstanceState()

public booleanonTouchEvent(MotionEvent ev)

public voidopen()

Open the GravityCompat.START drawer by animating it into view.

public voidopenDrawer(View drawerView)

Open the specified drawer view by animating it into view.

public voidopenDrawer(View drawerView, boolean animate)

Open the specified drawer view.

public voidremoveDrawerListener(DrawerLayout.DrawerListener listener)

Removes the specified listener from the list of listeners that will be notified of drawer events.

public voidrequestDisallowInterceptTouchEvent(boolean disallowIntercept)

public voidrequestLayout()

public voidsetChildInsets(WindowInsetsCompat insets, boolean draw)

public voidsetDrawerElevation(float elevation)

Sets the base elevation of the drawer(s) relative to the parent, in pixels.

public voidsetDrawerListener(DrawerLayout.DrawerListener listener)

Set a listener to be notified of drawer events.

public voidsetDrawerLockMode(int lockMode)

Enable or disable interaction with all drawers.

public voidsetDrawerLockMode(int lockMode, int edgeGravity)

Enable or disable interaction with the given drawer.

public voidsetDrawerLockMode(int lockMode, View drawerView)

Enable or disable interaction with the given drawer.

public voidsetDrawerShadow(Drawable shadowDrawable, int gravity)

Set a simple drawable used for the left or right shadow.

public voidsetDrawerTitle(int edgeGravity, java.lang.CharSequence title)

Sets the title of the drawer with the given gravity.

public voidsetScrimColor(int color)

Set a color to use for the scrim that obscures primary content while a drawer is open.

public voidsetStatusBarBackground(Drawable bg)

Set a drawable to draw in the insets area for the status bar.

public voidsetStatusBarBackgroundColor(int color)

Set a drawable to draw in the insets area for the status bar.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int STATE_IDLE

Indicates that any drawers are in an idle, settled state. No animation is in progress.

public static final int STATE_DRAGGING

Indicates that a drawer is currently being dragged by the user.

public static final int STATE_SETTLING

Indicates that a drawer is in the process of settling to a final position.

public static final int LOCK_MODE_UNLOCKED

The drawer is unlocked.

public static final int LOCK_MODE_LOCKED_CLOSED

The drawer is locked closed. The user may not open it, though the app may open it programmatically.

public static final int LOCK_MODE_LOCKED_OPEN

The drawer is locked open. The user may not close it, though the app may close it programmatically.

public static final int LOCK_MODE_UNDEFINED

The drawer's lock state is reset to default.

Constructors

public DrawerLayout(Context context)

public DrawerLayout(Context context, AttributeSet attrs)

public DrawerLayout(Context context, AttributeSet attrs, int defStyleAttr)

Methods

public void setDrawerElevation(float elevation)

Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the elevation change is only supported in API 21 and above.

Parameters:

elevation: The base depth position of the view, in pixels.

public float getDrawerElevation()

The base elevation of the drawer(s) relative to the parent, in pixels. Note that the elevation change is only supported in API 21 and above. For unsupported API levels, 0 will be returned as the elevation.

Returns:

The base depth position of the view, in pixels.

public void setChildInsets(WindowInsetsCompat insets, boolean draw)

public void setDrawerShadow(Drawable shadowDrawable, int gravity)

Set a simple drawable used for the left or right shadow. The drawable provided must have a nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer instead of using the provided shadow drawable.

Note that for better support for both left-to-right and right-to-left layout directions, a drawable for RTL layout (in additional to the one in LTR layout) can be defined with a resource qualifier "ldrtl" for API 17 and above with the gravity GravityCompat.START. Alternatively, for API 23 and above, the drawable can auto-mirrored such that the drawable will be mirrored in RTL layout.

Parameters:

shadowDrawable: Shadow drawable to use at the edge of a drawer
gravity: Which drawer the shadow should apply to

public void setScrimColor(int color)

Set a color to use for the scrim that obscures primary content while a drawer is open.

Parameters:

color: Color to use in 0xAARRGGBB format.

public void setDrawerListener(DrawerLayout.DrawerListener listener)

Deprecated: Use DrawerLayout.addDrawerListener(DrawerLayout.DrawerListener)

Set a listener to be notified of drawer events. Note that this method is deprecated and you should use DrawerLayout.addDrawerListener(DrawerLayout.DrawerListener) to add a listener and DrawerLayout.removeDrawerListener(DrawerLayout.DrawerListener) to remove a registered listener.

Parameters:

listener: Listener to notify when drawer events occur

See also: DrawerLayout.DrawerListener, DrawerLayout.addDrawerListener(DrawerLayout.DrawerListener), DrawerLayout.removeDrawerListener(DrawerLayout.DrawerListener)

public void addDrawerListener(DrawerLayout.DrawerListener listener)

Adds the specified listener to the list of listeners that will be notified of drawer events.

Parameters:

listener: Listener to notify when drawer events occur.

See also: DrawerLayout.removeDrawerListener(DrawerLayout.DrawerListener)

public void removeDrawerListener(DrawerLayout.DrawerListener listener)

Removes the specified listener from the list of listeners that will be notified of drawer events.

Parameters:

listener: Listener to remove from being notified of drawer events

See also: DrawerLayout.addDrawerListener(DrawerLayout.DrawerListener)

public void setDrawerLockMode(int lockMode)

Enable or disable interaction with all drawers.

This allows the application to restrict the user's ability to open or close any drawer within this layout. DrawerLayout will still respond to calls to DrawerLayout, DrawerLayout and friends if a drawer is locked.

Locking drawers open or closed will implicitly open or close any drawers as appropriate.

Parameters:

lockMode: The new lock mode for the given drawer. One of DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED or DrawerLayout.LOCK_MODE_LOCKED_OPEN.

public void setDrawerLockMode(int lockMode, int edgeGravity)

Enable or disable interaction with the given drawer.

This allows the application to restrict the user's ability to open or close the given drawer. DrawerLayout will still respond to calls to DrawerLayout, DrawerLayout and friends if a drawer is locked.

Locking a drawer open or closed will implicitly open or close that drawer as appropriate.

Parameters:

lockMode: The new lock mode for the given drawer. One of DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED or DrawerLayout.LOCK_MODE_LOCKED_OPEN.
edgeGravity: Gravity.LEFT, RIGHT, START or END. Expresses which drawer to change the mode for.

See also: DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED, DrawerLayout.LOCK_MODE_LOCKED_OPEN

public void setDrawerLockMode(int lockMode, View drawerView)

Enable or disable interaction with the given drawer.

This allows the application to restrict the user's ability to open or close the given drawer. DrawerLayout will still respond to calls to DrawerLayout, DrawerLayout and friends if a drawer is locked.

Locking a drawer open or closed will implicitly open or close that drawer as appropriate.

Parameters:

lockMode: The new lock mode for the given drawer. One of DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED or DrawerLayout.LOCK_MODE_LOCKED_OPEN.
drawerView: The drawer view to change the lock mode for

See also: DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED, DrawerLayout.LOCK_MODE_LOCKED_OPEN

public int getDrawerLockMode(int edgeGravity)

Check the lock mode of the drawer with the given gravity.

Parameters:

edgeGravity: Gravity of the drawer to check

Returns:

one of DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED or DrawerLayout.LOCK_MODE_LOCKED_OPEN.

public int getDrawerLockMode(View drawerView)

Check the lock mode of the given drawer view.

Parameters:

drawerView: Drawer view to check lock mode

Returns:

one of DrawerLayout.LOCK_MODE_UNLOCKED, DrawerLayout.LOCK_MODE_LOCKED_CLOSED or DrawerLayout.LOCK_MODE_LOCKED_OPEN.

public void setDrawerTitle(int edgeGravity, java.lang.CharSequence title)

Sets the title of the drawer with the given gravity.

When accessibility is turned on, this is the title that will be used to identify the drawer to the active accessibility service.

Parameters:

edgeGravity: Gravity.LEFT, RIGHT, START or END. Expresses which drawer to set the title for.
title: The title for the drawer.

public java.lang.CharSequence getDrawerTitle(int edgeGravity)

Returns the title of the drawer with the given gravity.

Parameters:

edgeGravity: Gravity.LEFT, RIGHT, START or END. Expresses which drawer to return the title for.

Returns:

The title of the drawer, or null if none set.

See also: DrawerLayout.setDrawerTitle(int, CharSequence)

protected void onDetachedFromWindow()

protected void onAttachedToWindow()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

protected void onLayout(boolean changed, int l, int t, int r, int b)

public void requestLayout()

public void computeScroll()

public void setStatusBarBackground(Drawable bg)

Set a drawable to draw in the insets area for the status bar. Note that this will only be activated if this DrawerLayout fitsSystemWindows.

Parameters:

bg: Background drawable to draw behind the status bar

public Drawable getStatusBarBackgroundDrawable()

Gets the drawable used to draw in the insets area for the status bar.

Returns:

The status bar background drawable, or null if none set

public void setStatusBarBackgroundColor(int color)

Set a drawable to draw in the insets area for the status bar. Note that this will only be activated if this DrawerLayout fitsSystemWindows.

Parameters:

color: Color to use as a background drawable to draw behind the status bar in 0xAARRGGBB format.

public void onRtlPropertiesChanged(int layoutDirection)

public void onDraw(Canvas c)

protected boolean drawChild(Canvas canvas, View child, long drawingTime)

public boolean onInterceptTouchEvent(MotionEvent ev)

public boolean dispatchGenericMotionEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent ev)

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)

public void closeDrawers()

Close all currently open drawer views by animating them out of view.

public void open()

Open the GravityCompat.START drawer by animating it into view.

public void openDrawer(View drawerView)

Open the specified drawer view by animating it into view.

Parameters:

drawerView: Drawer view to open

public void openDrawer(View drawerView, boolean animate)

Open the specified drawer view.

Parameters:

drawerView: Drawer view to open
animate: Whether opening of the drawer should be animated.

public void close()

Close the GravityCompat.START drawer by animating it out of view.

public void closeDrawer(View drawerView)

Close the specified drawer view by animating it into view.

Parameters:

drawerView: Drawer view to close

public void closeDrawer(View drawerView, boolean animate)

Close the specified drawer view.

Parameters:

drawerView: Drawer view to close
animate: Whether closing of the drawer should be animated.

public boolean isDrawerOpen(View drawer)

Check if the given drawer view is currently in an open state. To be considered "open" the drawer must have settled into its fully visible state. To check for partial visibility use DrawerLayout.

Parameters:

drawer: Drawer view to check

Returns:

true if the given drawer view is in an open state

See also: DrawerLayout

public boolean isOpen()

Check if the GravityCompat.START drawer is currently in an open state. To be considered "open" the drawer must have settled into its fully visible state. If there is no drawer with the given gravity this method will return false.

Returns:

true if the GravityCompat.START drawer is in an open state

public boolean isDrawerVisible(View drawer)

Check if a given drawer view is currently visible on-screen. The drawer may be only peeking onto the screen, fully extended, or anywhere inbetween.

Parameters:

drawer: Drawer view to check

Returns:

true if the given drawer is visible on-screen

See also: DrawerLayout

protected ViewGroup.LayoutParams generateDefaultLayoutParams()

protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)

protected boolean checkLayoutParams(ViewGroup.LayoutParams p)

public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)

public void addFocusables(java.util.ArrayList<View> views, int direction, int focusableMode)

public boolean onKeyDown(int keyCode, KeyEvent event)

public boolean onKeyUp(int keyCode, KeyEvent event)

protected void onRestoreInstanceState(Parcelable state)

protected Parcelable onSaveInstanceState()

public void addView(View child, int index, ViewGroup.LayoutParams params)

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package androidx.drawerlayout.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_DISMISS;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;

import androidx.annotation.ColorInt;
import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import androidx.core.view.accessibility.AccessibilityViewCommand;
import androidx.customview.view.AbsSavedState;
import androidx.customview.widget.Openable;
import androidx.customview.widget.ViewDragHelper;
import androidx.drawerlayout.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * DrawerLayout acts as a top-level container for window content that allows for
 * interactive "drawer" views to be pulled out from one or both vertical edges of the window.
 *
 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code>
 * attribute on child views corresponding to which side of the view you want the drawer
 * to emerge from: left or right (or start/end on platform versions that support layout direction.)
 * Note that you can only have one drawer view for each vertical edge of the window. If your
 * layout configures more than one drawer view per vertical edge of the window, an exception will
 * be thrown at runtime.
 * </p>
 *
 * <p>To use a DrawerLayout, position your primary content view as the first child with
 * width and height of <code>match_parent</code> and no <code>layout_gravity></code>.
 * Add drawers as child views after the main content view and set the <code>layout_gravity</code>
 * appropriately. Drawers commonly use <code>match_parent</code> for height with a fixed width.</p>
 *
 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views.
 * Avoid performing expensive operations such as layout during animation as it can cause
 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
 *
 * <p>As per the <a href="{@docRoot}design/patterns/navigation-drawer.html">Android Design
 * guide</a>, any drawers positioned to the left/start should
 * always contain content for navigating around the application, whereas any drawers
 * positioned to the right/end should always contain actions to take on the current content.
 * This preserves the same navigation left, actions right structure present in the Action Bar
 * and elsewhere.</p>
 *
 * <p>For more information about how to use DrawerLayout, read <a
 * href="{@docRoot}training/implementing-navigation/nav-drawer.html">Creating a Navigation
 * Drawer</a>.</p>
 */
@SuppressWarnings({"unused", "deprecation"})
public class DrawerLayout extends ViewGroup implements Openable {
    private static final String TAG = "DrawerLayout";

    private static final int[] THEME_ATTRS = {
            android.R.attr.colorPrimaryDark
    };

    @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
    @Retention(RetentionPolicy.SOURCE)
    private @interface State {}

    /**
     * Indicates that any drawers are in an idle, settled state. No animation is in progress.
     */
    public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE;

    /**
     * Indicates that a drawer is currently being dragged by the user.
     */
    public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING;

    /**
     * Indicates that a drawer is in the process of settling to a final position.
     */
    public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;

    @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN,
            LOCK_MODE_UNDEFINED})
    @Retention(RetentionPolicy.SOURCE)
    private @interface LockMode {}

    /**
     * The drawer is unlocked.
     */
    public static final int LOCK_MODE_UNLOCKED = 0;

    /**
     * The drawer is locked closed. The user may not open it, though
     * the app may open it programmatically.
     */
    public static final int LOCK_MODE_LOCKED_CLOSED = 1;

    /**
     * The drawer is locked open. The user may not close it, though the app
     * may close it programmatically.
     */
    public static final int LOCK_MODE_LOCKED_OPEN = 2;

    /**
     * The drawer's lock state is reset to default.
     */
    public static final int LOCK_MODE_UNDEFINED = 3;

    @IntDef(value = {Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END,
            Gravity.NO_GRAVITY}, flag = true)
    @Retention(RetentionPolicy.SOURCE)
    private @interface EdgeGravity {}

    private static final int MIN_DRAWER_MARGIN = 64; // dp

    private static final int DEFAULT_SCRIM_COLOR = 0x99000000;

    /**
     * Length of time to delay before peeking the drawer.
     */
    private static final int PEEK_DELAY = 160; // ms

    /**
     * Minimum velocity that will be detected as a fling
     */
    private static final int MIN_FLING_VELOCITY = 400; // dips per second

    /**
     * Experimental feature.
     */
    private static final boolean ALLOW_EDGE_LOCK = false;

    private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;

    private static final float TOUCH_SLOP_SENSITIVITY = 1.f;

    static final int[] LAYOUT_ATTRS = new int[] {
            android.R.attr.layout_gravity
    };

    /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */
    static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19;

    /** Whether the drawer shadow comes from setting elevation on the drawer. */
    private static final boolean SET_DRAWER_SHADOW_FROM_ELEVATION =
            Build.VERSION.SDK_INT >= 21;

    /** Class name may be obfuscated by Proguard. Hardcode the string for accessibility usage. */
    private static final String ACCESSIBILITY_CLASS_NAME =
            "androidx.drawerlayout.widget.DrawerLayout";

    private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
            new ChildAccessibilityDelegate();
    private float mDrawerElevation;

    private final int mMinDrawerMargin;

    private int mScrimColor = DEFAULT_SCRIM_COLOR;
    private float mScrimOpacity;
    private final Paint mScrimPaint = new Paint();

    private final ViewDragHelper mLeftDragger;
    private final ViewDragHelper mRightDragger;
    private final ViewDragCallback mLeftCallback;
    private final ViewDragCallback mRightCallback;
    private int mDrawerState;
    private boolean mInLayout;
    private boolean mFirstLayout = true;

    // The callback handling back events. If this is non-null, the
    // callback has been registered at least once.
    private OnBackInvokedCallback mBackInvokedCallback;

    // The dispatcher on which the callback was registered. If this
    // value is null, the callback is not registered anywhere.
    private OnBackInvokedDispatcher mBackInvokedDispatcher;

    private @LockMode int mLockModeLeft = LOCK_MODE_UNDEFINED;
    private @LockMode int mLockModeRight = LOCK_MODE_UNDEFINED;
    private @LockMode int mLockModeStart = LOCK_MODE_UNDEFINED;
    private @LockMode int mLockModeEnd = LOCK_MODE_UNDEFINED;

    private boolean mChildrenCanceledTouch;

    private @Nullable DrawerListener mListener;
    private List<DrawerListener> mListeners;

    private float mInitialMotionX;
    private float mInitialMotionY;

    private Drawable mStatusBarBackground;
    private Drawable mShadowLeftResolved;
    private Drawable mShadowRightResolved;

    private CharSequence mTitleLeft;
    private CharSequence mTitleRight;

    private WindowInsetsCompat mLastInsets;
    private boolean mDrawStatusBarBackground;

    /** Shadow drawables for different gravity */
    private Drawable mShadowStart = null;
    private Drawable mShadowEnd = null;
    private Drawable mShadowLeft = null;
    private Drawable mShadowRight = null;

    private final ArrayList<View> mNonDrawerViews;

    private Rect mChildHitRect;
    private Matrix mChildInvertedMatrix;

    private static final boolean sEdgeSizeUsingSystemGestureInsets = Build.VERSION.SDK_INT >= 29;

    private final AccessibilityViewCommand mActionDismiss =
            (view, arguments) -> {
                if (isDrawerOpen(view)  && getDrawerLockMode(view) != LOCK_MODE_LOCKED_OPEN) {
                    closeDrawer(view);
                    return true;
                }
                return false;
            };

    /**
     * Listener for monitoring events about drawers.
     */
    public interface DrawerListener {
        /**
         * Called when a drawer's position changes.
         * @param drawerView The child view that was moved
         * @param slideOffset The new offset of this drawer within its range, from 0-1
         */
        void onDrawerSlide(@NonNull View drawerView, float slideOffset);

        /**
         * Called when a drawer has settled in a completely open state.
         * The drawer is interactive at this point.
         *
         * @param drawerView Drawer view that is now open
         */
        void onDrawerOpened(@NonNull View drawerView);

        /**
         * Called when a drawer has settled in a completely closed state.
         *
         * @param drawerView Drawer view that is now closed
         */
        void onDrawerClosed(@NonNull View drawerView);

        /**
         * Called when the drawer motion state changes. The new state will
         * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}.
         *
         * @param newState The new drawer motion state
         */
        void onDrawerStateChanged(@State int newState);
    }

    /**
     * Stub/no-op implementations of all methods of {@link DrawerListener}.
     * Override this if you only care about a few of the available callback methods.
     */
    public abstract static class SimpleDrawerListener implements DrawerListener {
        @Override
        public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
        }

        @Override
        public void onDrawerOpened(@NonNull View drawerView) {
        }

        @Override
        public void onDrawerClosed(@NonNull View drawerView) {
        }

        @Override
        public void onDrawerStateChanged(int newState) {
        }
    }

    public DrawerLayout(@NonNull Context context) {
        this(context, null);
    }

    public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, R.attr.drawerLayoutStyle);
    }

    public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        final float density = getResources().getDisplayMetrics().density;
        mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
        final float minVel = MIN_FLING_VELOCITY * density;

        mLeftCallback = new ViewDragCallback(Gravity.LEFT);
        mRightCallback = new ViewDragCallback(Gravity.RIGHT);

        mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
        mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
        mLeftDragger.setMinVelocity(minVel);
        mLeftCallback.setDragger(mLeftDragger);

        mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
        mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
        mRightDragger.setMinVelocity(minVel);
        mRightCallback.setDragger(mRightDragger);

        // So that we can catch the back button
        setFocusableInTouchMode(true);

        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);

        ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
        setMotionEventSplittingEnabled(false);
        if (ViewCompat.getFitsSystemWindows(this)) {
            if (Build.VERSION.SDK_INT >= 21) {
                ViewCompat.setOnApplyWindowInsetsListener(this,
                        (view, insets) -> {
                            final DrawerLayout drawerLayout = (DrawerLayout) view;
                            drawerLayout.setChildInsets(insets,
                                    insets.getSystemWindowInsets().top > 0);
                            return insets.consumeSystemWindowInsets();
                        });
                setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                @SuppressLint("ResourceType")
                final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
                try {
                    mStatusBarBackground = a.getDrawable(0);
                } finally {
                    a.recycle();
                }
            } else {
                mStatusBarBackground = null;
            }
        }

        final TypedArray a = context
                .obtainStyledAttributes(attrs, R.styleable.DrawerLayout, defStyleAttr, 0);
        try {
            if (a.hasValue(R.styleable.DrawerLayout_elevation)) {
                mDrawerElevation = a.getDimension(R.styleable.DrawerLayout_elevation, 0);
            } else {
                mDrawerElevation = getResources().getDimension(R.dimen.def_drawer_elevation);
            }
        } finally {
            a.recycle();
        }

        mNonDrawerViews = new ArrayList<>();
    }

    /**
     * Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the
     * elevation change is only supported in API 21 and above.
     *
     * @param elevation The base depth position of the view, in pixels.
     */
    public void setDrawerElevation(float elevation) {
        mDrawerElevation = elevation;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (isDrawerView(child)) {
                ViewCompat.setElevation(child, mDrawerElevation);
            }
        }
    }

    /**
     * The base elevation of the drawer(s) relative to the parent, in pixels. Note that the
     * elevation change is only supported in API 21 and above. For unsupported API levels, 0 will
     * be returned as the elevation.
     *
     * @return The base depth position of the view, in pixels.
     */
    public float getDrawerElevation() {
        if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
            return mDrawerElevation;
        }
        return 0f;
    }

    /**
     * @hide Internal use only; called to apply window insets when configured
     * with fitsSystemWindows="true"
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public void setChildInsets(@Nullable WindowInsetsCompat insets, boolean draw) {
        mLastInsets = insets;
        mDrawStatusBarBackground = draw;
        setWillNotDraw(!draw && getBackground() == null);
        requestLayout();
    }

    /**
     * Set a simple drawable used for the left or right shadow. The drawable provided must have a
     * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
     * instead of using the provided shadow drawable.
     *
     * <p>Note that for better support for both left-to-right and right-to-left layout
     * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
     * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity
     * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can
     * auto-mirrored such that the drawable will be mirrored in RTL layout.</p>
     *
     * @param shadowDrawable Shadow drawable to use at the edge of a drawer
     * @param gravity Which drawer the shadow should apply to
     */
    public void setDrawerShadow(@Nullable Drawable shadowDrawable, @EdgeGravity int gravity) {
        /*
         * TODO Someone someday might want to set more complex drawables here.
         * They're probably nuts, but we might want to consider registering callbacks,
         * setting states, etc. properly.
         */
        if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
            // No op. Drawer shadow will come from setting an elevation on the drawer.
            return;
        }
        if ((gravity & GravityCompat.START) == GravityCompat.START) {
            mShadowStart = shadowDrawable;
        } else if ((gravity & GravityCompat.END) == GravityCompat.END) {
            mShadowEnd = shadowDrawable;
        } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
            mShadowLeft = shadowDrawable;
        } else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
            mShadowRight = shadowDrawable;
        } else {
            return;
        }
        resolveShadowDrawables();
        invalidate();
    }

    /**
     * Set a simple drawable used for the left or right shadow. The drawable provided must have a
     * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
     * instead of using the provided shadow drawable.
     *
     * <p>Note that for better support for both left-to-right and right-to-left layout
     * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
     * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity
     * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can
     * auto-mirrored such that the drawable will be mirrored in RTL layout.</p>
     *
     * @param resId Resource id of a shadow drawable to use at the edge of a drawer
     * @param gravity Which drawer the shadow should apply to
     */
    public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) {
        setDrawerShadow(ContextCompat.getDrawable(getContext(), resId), gravity);
    }

    /**
     * Set a color to use for the scrim that obscures primary content while a drawer is open.
     *
     * @param color Color to use in 0xAARRGGBB format.
     */
    public void setScrimColor(@ColorInt int color) {
        mScrimColor = color;
        invalidate();
    }

    /**
     * Set a listener to be notified of drawer events. Note that this method is deprecated
     * and you should use {@link #addDrawerListener(DrawerListener)} to add a listener and
     * {@link #removeDrawerListener(DrawerListener)} to remove a registered listener.
     *
     * @param listener Listener to notify when drawer events occur
     * @deprecated Use {@link #addDrawerListener(DrawerListener)}
     * @see DrawerListener
     * @see #addDrawerListener(DrawerListener)
     * @see #removeDrawerListener(DrawerListener)
     */
    @Deprecated
    public void setDrawerListener(DrawerListener listener) {
        // The logic in this method emulates what we had before support for multiple
        // registered listeners.
        if (mListener != null) {
            removeDrawerListener(mListener);
        }
        if (listener != null) {
            addDrawerListener(listener);
        }
        // Update the deprecated field so that we can remove the passed listener the next
        // time we're called
        mListener = listener;
    }

    /**
     * Adds the specified listener to the list of listeners that will be notified of drawer events.
     *
     * @param listener Listener to notify when drawer events occur.
     * @see #removeDrawerListener(DrawerListener)
     */
    public void addDrawerListener(@NonNull DrawerListener listener) {
        if (mListeners == null) {
            mListeners = new ArrayList<>();
        }
        mListeners.add(listener);
    }

    /**
     * Removes the specified listener from the list of listeners that will be notified of drawer
     * events.
     *
     * @param listener Listener to remove from being notified of drawer events
     * @see #addDrawerListener(DrawerListener)
     */
    public void removeDrawerListener(@NonNull DrawerListener listener) {
        if (mListeners == null) {
            // This can happen if this method is called before the first call to addDrawerListener
            return;
        }
        mListeners.remove(listener);
    }

    /**
     * Enable or disable interaction with all drawers.
     *
     * <p>This allows the application to restrict the user's ability to open or close
     * any drawer within this layout. DrawerLayout will still respond to calls to
     * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
     *
     * <p>Locking drawers open or closed will implicitly open or close
     * any drawers as appropriate.</p>
     *
     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
     */
    public void setDrawerLockMode(@LockMode int lockMode) {
        setDrawerLockMode(lockMode, Gravity.LEFT);
        setDrawerLockMode(lockMode, Gravity.RIGHT);
    }

    /**
     * Enable or disable interaction with the given drawer.
     *
     * <p>This allows the application to restrict the user's ability to open or close
     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
     *
     * <p>Locking a drawer open or closed will implicitly open or close
     * that drawer as appropriate.</p>
     *
     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
     * @param edgeGravity Gravity.LEFT, RIGHT, START or END.
     *                    Expresses which drawer to change the mode for.
     *
     * @see #LOCK_MODE_UNLOCKED
     * @see #LOCK_MODE_LOCKED_CLOSED
     * @see #LOCK_MODE_LOCKED_OPEN
     */
    public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
        final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
                ViewCompat.getLayoutDirection(this));

        switch (edgeGravity) {
            case Gravity.LEFT:
                mLockModeLeft = lockMode;
                break;
            case Gravity.RIGHT:
                mLockModeRight = lockMode;
                break;
            case GravityCompat.START:
                mLockModeStart = lockMode;
                break;
            case GravityCompat.END:
                mLockModeEnd = lockMode;
                break;
        }

        if (lockMode != LOCK_MODE_UNLOCKED) {
            // Cancel interaction in progress
            final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger;
            helper.cancel();
        }
        switch (lockMode) {
            case LOCK_MODE_LOCKED_OPEN:
                final View toOpen = findDrawerWithGravity(absGravity);
                if (toOpen != null) {
                    openDrawer(toOpen);
                }
                break;
            case LOCK_MODE_LOCKED_CLOSED:
                final View toClose = findDrawerWithGravity(absGravity);
                if (toClose != null) {
                    closeDrawer(toClose);
                }
                break;
            // default: do nothing
        }
    }

    /**
     * Enable or disable interaction with the given drawer.
     *
     * <p>This allows the application to restrict the user's ability to open or close
     * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)},
     * {@link #closeDrawer(int)} and friends if a drawer is locked.</p>
     *
     * <p>Locking a drawer open or closed will implicitly open or close
     * that drawer as appropriate.</p>
     *
     * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
     *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
     * @param drawerView The drawer view to change the lock mode for
     *
     * @see #LOCK_MODE_UNLOCKED
     * @see #LOCK_MODE_LOCKED_CLOSED
     * @see #LOCK_MODE_LOCKED_OPEN
     */
    public void setDrawerLockMode(@LockMode int lockMode, @NonNull View drawerView) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a "
                    + "drawer with appropriate layout_gravity");
        }
        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
        setDrawerLockMode(lockMode, gravity);
    }

    /**
     * Check the lock mode of the drawer with the given gravity.
     *
     * @param edgeGravity Gravity of the drawer to check
     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
     *         {@link #LOCK_MODE_LOCKED_OPEN}.
     */
    @LockMode
    public int getDrawerLockMode(@EdgeGravity int edgeGravity) {
        int layoutDirection = ViewCompat.getLayoutDirection(this);

        switch (edgeGravity) {
            case Gravity.LEFT:
                if (mLockModeLeft != LOCK_MODE_UNDEFINED) {
                    return mLockModeLeft;
                }
                int leftLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
                        ? mLockModeStart : mLockModeEnd;
                if (leftLockMode != LOCK_MODE_UNDEFINED) {
                    return leftLockMode;
                }
                break;
            case Gravity.RIGHT:
                if (mLockModeRight != LOCK_MODE_UNDEFINED) {
                    return mLockModeRight;
                }
                int rightLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
                        ? mLockModeEnd : mLockModeStart;
                if (rightLockMode != LOCK_MODE_UNDEFINED) {
                    return rightLockMode;
                }
                break;
            case GravityCompat.START:
                if (mLockModeStart != LOCK_MODE_UNDEFINED) {
                    return mLockModeStart;
                }
                int startLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
                        ? mLockModeLeft : mLockModeRight;
                if (startLockMode != LOCK_MODE_UNDEFINED) {
                    return startLockMode;
                }
                break;
            case GravityCompat.END:
                if (mLockModeEnd != LOCK_MODE_UNDEFINED) {
                    return mLockModeEnd;
                }
                int endLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR)
                        ? mLockModeRight : mLockModeLeft;
                if (endLockMode != LOCK_MODE_UNDEFINED) {
                    return endLockMode;
                }
                break;
        }

        return LOCK_MODE_UNLOCKED;
    }

    /**
     * Check the lock mode of the given drawer view.
     *
     * @param drawerView Drawer view to check lock mode
     * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
     *         {@link #LOCK_MODE_LOCKED_OPEN}.
     */
    @LockMode
    public int getDrawerLockMode(@NonNull View drawerView) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
        }
        final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
        return getDrawerLockMode(drawerGravity);
    }

    /**
     * Sets the title of the drawer with the given gravity.
     * <p>
     * When accessibility is turned on, this is the title that will be used to
     * identify the drawer to the active accessibility service.
     *
     * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
     *            drawer to set the title for.
     * @param title The title for the drawer.
     */
    public void setDrawerTitle(@EdgeGravity int edgeGravity, @Nullable CharSequence title) {
        final int absGravity = GravityCompat.getAbsoluteGravity(
                edgeGravity, ViewCompat.getLayoutDirection(this));
        if (absGravity == Gravity.LEFT) {
            mTitleLeft = title;
        } else if (absGravity == Gravity.RIGHT) {
            mTitleRight = title;
        }
    }

    /**
     * Returns the title of the drawer with the given gravity.
     *
     * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which
     *            drawer to return the title for.
     * @return The title of the drawer, or null if none set.
     * @see #setDrawerTitle(int, CharSequence)
     */
    @Nullable
    public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) {
        final int absGravity = GravityCompat.getAbsoluteGravity(
                edgeGravity, ViewCompat.getLayoutDirection(this));
        if (absGravity == Gravity.LEFT) {
            return mTitleLeft;
        } else if (absGravity == Gravity.RIGHT) {
            return mTitleRight;
        }
        return null;
    }

    /**
     * Returns true if x and y coord in DrawerLayout's coordinate space are inside the bounds of the
     * child's coordinate space.
     */
    private boolean isInBoundsOfChild(float x, float y, View child) {
        if (mChildHitRect == null) {
            mChildHitRect = new Rect();
        }
        child.getHitRect(mChildHitRect);
        return mChildHitRect.contains((int) x, (int) y);
    }

    /**
     * Copied from ViewGroup#dispatchTransformedGenericPointerEvent(MotionEvent, View) then modified
     * in order to make calls that are otherwise too visibility restricted to make.
     */
    private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) {
        boolean handled;
        final Matrix childMatrix = child.getMatrix();
        if (!childMatrix.isIdentity()) {
            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
            handled = child.dispatchGenericMotionEvent(transformedEvent);
            transformedEvent.recycle();
        } else {
            final float offsetX = getScrollX() - child.getLeft();
            final float offsetY = getScrollY() - child.getTop();
            event.offsetLocation(offsetX, offsetY);
            handled = child.dispatchGenericMotionEvent(event);
            event.offsetLocation(-offsetX, -offsetY);
        }
        return handled;
    }

    /**
     * Copied from ViewGroup#getTransformedMotionEvent(MotionEvent, View) then  modified in order to
     * make calls that are otherwise too visibility restricted to make.
     */
    private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
        final float offsetX = getScrollX() - child.getLeft();
        final float offsetY = getScrollY() - child.getTop();
        final MotionEvent transformedEvent = MotionEvent.obtain(event);
        transformedEvent.offsetLocation(offsetX, offsetY);
        final Matrix childMatrix = child.getMatrix();
        if (!childMatrix.isIdentity()) {
            if (mChildInvertedMatrix == null) {
                mChildInvertedMatrix = new Matrix();
            }
            childMatrix.invert(mChildInvertedMatrix);
            transformedEvent.transform(mChildInvertedMatrix);
        }
        return transformedEvent;
    }

    /**
     * Resolve the shared state of all drawers from the component ViewDragHelpers.
     * Should be called whenever a ViewDragHelper's state changes.
     */
    void updateDrawerState(@State int activeState, View activeDrawer) {
        final int leftState = mLeftDragger.getViewDragState();
        final int rightState = mRightDragger.getViewDragState();

        final int state;
        if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) {
            state = STATE_DRAGGING;
        } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) {
            state = STATE_SETTLING;
        } else {
            state = STATE_IDLE;
        }

        if (activeDrawer != null && activeState == STATE_IDLE) {
            final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
            if (lp.onScreen == 0) {
                dispatchOnDrawerClosed(activeDrawer);
            } else if (lp.onScreen == 1) {
                dispatchOnDrawerOpened(activeDrawer);
            }
        }

        if (state != mDrawerState) {
            mDrawerState = state;

            if (mListeners != null) {
                // Notify the listeners. Do that from the end of the list so that if a listener
                // removes itself as the result of being called, it won't mess up with our iteration
                int listenerCount = mListeners.size();
                for (int i = listenerCount - 1; i >= 0; i--) {
                    mListeners.get(i).onDrawerStateChanged(state);
                }
            }
        }
    }

    void dispatchOnDrawerClosed(View drawerView) {
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
            lp.openState = 0;

            if (mListeners != null) {
                // Notify the listeners. Do that from the end of the list so that if a listener
                // removes itself as the result of being called, it won't mess up with our iteration
                int listenerCount = mListeners.size();
                for (int i = listenerCount - 1; i >= 0; i--) {
                    mListeners.get(i).onDrawerClosed(drawerView);
                }
            }

            updateChildrenImportantForAccessibility(drawerView, false);
            updateChildAccessibilityAction(drawerView);
            updateBackInvokedCallbackState();

            // Only send WINDOW_STATE_CHANGE if the host has window focus. This
            // may change if support for multiple foreground windows (e.g. IME)
            // improves.
            if (hasWindowFocus()) {
                final View rootView = getRootView();
                if (rootView != null) {
                    rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                }
            }
        }
    }

    void dispatchOnDrawerOpened(View drawerView) {
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) {
            lp.openState = LayoutParams.FLAG_IS_OPENED;
            if (mListeners != null) {
                // Notify the listeners. Do that from the end of the list so that if a listener
                // removes itself as the result of being called, it won't mess up with our iteration
                int listenerCount = mListeners.size();
                for (int i = listenerCount - 1; i >= 0; i--) {
                    mListeners.get(i).onDrawerOpened(drawerView);
                }
            }

            updateChildrenImportantForAccessibility(drawerView, true);
            updateChildAccessibilityAction(drawerView);
            updateBackInvokedCallbackState();

            // Only send WINDOW_STATE_CHANGE if the host has window focus.
            if (hasWindowFocus()) {
                sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
            }
        }
    }

    private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if ((!isDrawerOpen && !isDrawerView(child)) || (isDrawerOpen && child == drawerView)) {
                // Drawer is closed and this is a content view or this is an
                // open drawer view, so it should be visible.
                ViewCompat.setImportantForAccessibility(child,
                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
            } else {
                ViewCompat.setImportantForAccessibility(child,
                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
            }
        }
    }

    private void updateChildAccessibilityAction(View child) {
        ViewCompat.removeAccessibilityAction(child, ACTION_DISMISS.getId());
        if (isDrawerOpen(child)  && getDrawerLockMode(child) != LOCK_MODE_LOCKED_OPEN) {
            ViewCompat.replaceAccessibilityAction(child, ACTION_DISMISS, null, mActionDismiss);
        }
    }

    void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
        if (mListeners != null) {
            // Notify the listeners. Do that from the end of the list so that if a listener
            // removes itself as the result of being called, it won't mess up with our iteration
            int listenerCount = mListeners.size();
            for (int i = listenerCount - 1; i >= 0; i--) {
                mListeners.get(i).onDrawerSlide(drawerView, slideOffset);
            }
        }
    }

    void setDrawerViewOffset(View drawerView, float slideOffset) {
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        if (slideOffset == lp.onScreen) {
            return;
        }

        lp.onScreen = slideOffset;
        dispatchOnDrawerSlide(drawerView, slideOffset);
    }

    float getDrawerViewOffset(View drawerView) {
        return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
    }

    /**
     * @return the absolute gravity of the child drawerView, resolved according
     *         to the current layout direction
     */
    int getDrawerViewAbsoluteGravity(View drawerView) {
        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
        return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
    }

    boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) {
        final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
        return (absGravity & checkFor) == checkFor;
    }

    View findOpenDrawer() {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams childLp = (LayoutParams) child.getLayoutParams();
            if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
                return child;
            }
        }
        return null;
    }

    void moveDrawerToOffset(View drawerView, float slideOffset) {
        final float oldOffset = getDrawerViewOffset(drawerView);
        final int width = drawerView.getWidth();
        final int oldPos = (int) (width * oldOffset);
        final int newPos = (int) (width * slideOffset);
        final int dx = newPos - oldPos;

        drawerView.offsetLeftAndRight(
                checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
        setDrawerViewOffset(drawerView, slideOffset);
    }

    /**
     * @param gravity the gravity of the child to return. If specified as a
     *            relative value, it will be resolved according to the current
     *            layout direction.
     * @return the drawer with the specified gravity
     */
    View findDrawerWithGravity(int gravity) {
        final int absHorizGravity = GravityCompat.getAbsoluteGravity(
                gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
            if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) {
                return child;
            }
        }
        return null;
    }

    /**
     * Simple gravity to string - only supports LEFT and RIGHT for debugging output.
     *
     * @param gravity Absolute gravity value
     * @return LEFT or RIGHT as appropriate, or a hex string
     */
    static String gravityToString(@EdgeGravity int gravity) {
        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
            return "LEFT";
        }
        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
            return "RIGHT";
        }
        return Integer.toHexString(gravity);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mFirstLayout = true;

        updateBackInvokedCallbackState();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mFirstLayout = true;

        updateBackInvokedCallbackState();
    }

    @SuppressLint("WrongConstant")
    // Remove deprecation suppression once b/120984242 is resolved.
    @SuppressWarnings("deprecation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            if (isInEditMode()) {
                // Don't crash the layout editor. Consume all of the space if specified
                // or pick a magic number from thin air otherwise.
                // TODO Better communication with tools of this bogus state.
                // It will crash on a real device.
                if (widthMode == MeasureSpec.UNSPECIFIED) {
                    widthSize = 300;
                }
                if (heightMode == MeasureSpec.UNSPECIFIED) {
                    heightSize = 300;
                }
            } else {
                throw new IllegalArgumentException(
                        "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
            }
        }

        setMeasuredDimension(widthSize, heightSize);

        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
        final int layoutDirection = ViewCompat.getLayoutDirection(this);

        // Only one drawer is permitted along each vertical edge (left / right). These two booleans
        // are tracking the presence of the edge drawers.
        boolean hasDrawerOnLeftEdge = false;
        boolean hasDrawerOnRightEdge = false;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (applyInsets) {
                final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection);
                if (ViewCompat.getFitsSystemWindows(child)) {
                    if (Build.VERSION.SDK_INT >= 21) {
                        WindowInsetsCompat wi = mLastInsets;
                        if (cgrav == Gravity.LEFT) {
                            wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
                                    wi.getSystemWindowInsetTop(), 0,
                                    wi.getSystemWindowInsetBottom());
                        } else if (cgrav == Gravity.RIGHT) {
                            wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
                                    wi.getSystemWindowInsetRight(),
                                    wi.getSystemWindowInsetBottom());
                        }
                        ViewCompat.dispatchApplyWindowInsets(child, wi);
                    }
                } else {
                    if (Build.VERSION.SDK_INT >= 21) {
                        WindowInsetsCompat wi = mLastInsets;
                        if (cgrav == Gravity.LEFT) {
                            wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(),
                                    wi.getSystemWindowInsetTop(), 0,
                                    wi.getSystemWindowInsetBottom());
                        } else if (cgrav == Gravity.RIGHT) {
                            wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(),
                                    wi.getSystemWindowInsetRight(),
                                    wi.getSystemWindowInsetBottom());
                        }
                        lp.leftMargin = wi.getSystemWindowInsetLeft();
                        lp.topMargin = wi.getSystemWindowInsetTop();
                        lp.rightMargin = wi.getSystemWindowInsetRight();
                        lp.bottomMargin = wi.getSystemWindowInsetBottom();
                    }
                }
            }

            if (isContentView(child)) {
                // Content views get measured at exactly the layout's size.
                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
                child.measure(contentWidthSpec, contentHeightSpec);
            } else if (isDrawerView(child)) {
                if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
                    if (ViewCompat.getElevation(child) != mDrawerElevation) {
                        ViewCompat.setElevation(child, mDrawerElevation);
                    }
                }
                final @EdgeGravity int childGravity =
                        getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
                // Note that the isDrawerView check guarantees that childGravity here is either
                // LEFT or RIGHT
                boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT);
                if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge)
                        || (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) {
                    throw new IllegalStateException("Child drawer has absolute gravity "
                            + gravityToString(childGravity) + " but this " + TAG + " already has a "
                            + "drawer view along that edge");
                }
                if (isLeftEdgeDrawer) {
                    hasDrawerOnLeftEdge = true;
                } else {
                    hasDrawerOnRightEdge = true;
                }
                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                        mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
                        lp.width);
                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
                child.measure(drawerWidthSpec, drawerHeightSpec);
            } else {
                throw new IllegalStateException("Child " + child + " at index " + i
                        + " does not have a valid layout_gravity - must be Gravity.LEFT, "
                        + "Gravity.RIGHT or Gravity.NO_GRAVITY");
            }
        }
    }

    private void resolveShadowDrawables() {
        if (SET_DRAWER_SHADOW_FROM_ELEVATION) {
            return;
        }
        mShadowLeftResolved = resolveLeftShadow();
        mShadowRightResolved = resolveRightShadow();
    }

    private Drawable resolveLeftShadow() {
        int layoutDirection = ViewCompat.getLayoutDirection(this);
        // Prefer shadows defined with start/end gravity over left and right.
        if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
            if (mShadowStart != null) {
                // Correct drawable layout direction, if needed.
                mirror(mShadowStart, layoutDirection);
                return mShadowStart;
            }
        } else {
            if (mShadowEnd != null) {
                // Correct drawable layout direction, if needed.
                mirror(mShadowEnd, layoutDirection);
                return mShadowEnd;
            }
        }
        return mShadowLeft;
    }

    private Drawable resolveRightShadow() {
        int layoutDirection = ViewCompat.getLayoutDirection(this);
        if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
            if (mShadowEnd != null) {
                // Correct drawable layout direction, if needed.
                mirror(mShadowEnd, layoutDirection);
                return mShadowEnd;
            }
        } else {
            if (mShadowStart != null) {
                // Correct drawable layout direction, if needed.
                mirror(mShadowStart, layoutDirection);
                return mShadowStart;
            }
        }
        return mShadowRight;
    }

    /**
     * Change the layout direction of the given drawable.
     */
    @SuppressWarnings("PointlessNullCheck") // incorrect
    private void mirror(@Nullable Drawable drawable, int layoutDirection) {
        if (drawable != null && DrawableCompat.isAutoMirrored(drawable)) {
            DrawableCompat.setLayoutDirection(drawable, layoutDirection);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mInLayout = true;
        final int width = r - l;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (isContentView(child)) {
                child.layout(lp.leftMargin, lp.topMargin,
                        lp.leftMargin + child.getMeasuredWidth(),
                        lp.topMargin + child.getMeasuredHeight());
            } else { // Drawer, if it wasn't onMeasure would have thrown an exception.
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int childLeft;

                final float newOffset;
                if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
                    childLeft = -childWidth + (int) (childWidth * lp.onScreen);
                    newOffset = (float) (childWidth + childLeft) / childWidth;
                } else { // Right; onMeasure checked for us.
                    childLeft = width - (int) (childWidth * lp.onScreen);
                    newOffset = (float) (width - childLeft) / childWidth;
                }

                final boolean changeOffset = newOffset != lp.onScreen;

                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (vgrav) {
                    default:
                    case Gravity.TOP: {
                        child.layout(childLeft, lp.topMargin, childLeft + childWidth,
                                lp.topMargin + childHeight);
                        break;
                    }

                    case Gravity.BOTTOM: {
                        final int height = b - t;
                        child.layout(childLeft,
                                height - lp.bottomMargin - child.getMeasuredHeight(),
                                childLeft + childWidth,
                                height - lp.bottomMargin);
                        break;
                    }

                    case Gravity.CENTER_VERTICAL: {
                        final int height = b - t;
                        int childTop = (height - childHeight) / 2;

                        // Offset for margins. If things don't fit right because of
                        // bad measurement before, oh well.
                        if (childTop < lp.topMargin) {
                            childTop = lp.topMargin;
                        } else if (childTop + childHeight > height - lp.bottomMargin) {
                            childTop = height - lp.bottomMargin - childHeight;
                        }
                        child.layout(childLeft, childTop, childLeft + childWidth,
                                childTop + childHeight);
                        break;
                    }
                }

                if (changeOffset) {
                    setDrawerViewOffset(child, newOffset);
                }

                final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
                if (child.getVisibility() != newVisibility) {
                    child.setVisibility(newVisibility);
                }
            }
        }

        if (sEdgeSizeUsingSystemGestureInsets) {
            // Update the ViewDragHelper edge sizes to match the gesture insets
            WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(this);
            if (rootInsets != null) {
                Insets gestureInsets = rootInsets.getSystemGestureInsets();

                // We use Math.max() here since the gesture insets will be 0 if the device
                // does not have gesture navigation enabled
                mLeftDragger.setEdgeSize(
                        Math.max(mLeftDragger.getDefaultEdgeSize(), gestureInsets.left));
                mRightDragger.setEdgeSize(
                        Math.max(mRightDragger.getDefaultEdgeSize(), gestureInsets.right));
            }
        }

        mInLayout = false;
        mFirstLayout = false;
    }

    @Override
    public void requestLayout() {
        if (!mInLayout) {
            super.requestLayout();
        }
    }

    @Override
    public void computeScroll() {
        final int childCount = getChildCount();
        float scrimOpacity = 0;
        for (int i = 0; i < childCount; i++) {
            final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen;
            scrimOpacity = Math.max(scrimOpacity, onscreen);
        }
        mScrimOpacity = scrimOpacity;

        boolean leftDraggerSettling = mLeftDragger.continueSettling(true);
        boolean rightDraggerSettling = mRightDragger.continueSettling(true);
        if (leftDraggerSettling || rightDraggerSettling) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    private static boolean hasOpaqueBackground(View v) {
        final Drawable bg = v.getBackground();
        if (bg != null) {
            return bg.getOpacity() == PixelFormat.OPAQUE;
        }
        return false;
    }

    /**
     * Set a drawable to draw in the insets area for the status bar.
     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
     *
     * @param bg Background drawable to draw behind the status bar
     */
    public void setStatusBarBackground(@Nullable Drawable bg) {
        mStatusBarBackground = bg;
        invalidate();
    }

    /**
     * Gets the drawable used to draw in the insets area for the status bar.
     *
     * @return The status bar background drawable, or null if none set
     */
    @Nullable
    public Drawable getStatusBarBackgroundDrawable() {
        return mStatusBarBackground;
    }

    /**
     * Set a drawable to draw in the insets area for the status bar.
     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
     *
     * @param resId Resource id of a background drawable to draw behind the status bar
     */
    public void setStatusBarBackground(int resId) {
        mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null;
        invalidate();
    }

    /**
     * Set a drawable to draw in the insets area for the status bar.
     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
     *
     * @param color Color to use as a background drawable to draw behind the status bar
     *              in 0xAARRGGBB format.
     */
    public void setStatusBarBackgroundColor(@ColorInt int color) {
        mStatusBarBackground = new ColorDrawable(color);
        invalidate();
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        resolveShadowDrawables();
    }

    @Override
    public void onDraw(@NonNull Canvas c) {
        super.onDraw(c);
        if (mDrawStatusBarBackground && mStatusBarBackground != null) {
            final int inset;
            if (Build.VERSION.SDK_INT >= 21) {
                inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
            } else {
                inset = 0;
            }
            if (inset > 0) {
                mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
                mStatusBarBackground.draw(c);
            }
        }
    }

    @Override
    protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
        final int height = getHeight();
        final boolean drawingContent = isContentView(child);
        int clipLeft = 0, clipRight = getWidth();

        final int restoreCount = canvas.save();
        if (drawingContent) {
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View v = getChildAt(i);
                if (v == child || v.getVisibility() != VISIBLE
                        || !hasOpaqueBackground(v) || !isDrawerView(v)
                        || v.getHeight() < height) {
                    continue;
                }

                if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
                    final int vright = v.getRight();
                    if (vright > clipLeft) clipLeft = vright;
                } else {
                    final int vleft = v.getLeft();
                    if (vleft < clipRight) clipRight = vleft;
                }
            }
            canvas.clipRect(clipLeft, 0, clipRight, getHeight());
        }
        final boolean result = super.drawChild(canvas, child, drawingTime);
        canvas.restoreToCount(restoreCount);

        if (mScrimOpacity > 0 && drawingContent) {
            final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
            final int imag = (int) (baseAlpha * mScrimOpacity);
            final int color = imag << 24 | (mScrimColor & 0xffffff);
            mScrimPaint.setColor(color);

            canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint);
        } else if (mShadowLeftResolved != null
                &&  checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
            final int shadowWidth = mShadowLeftResolved.getIntrinsicWidth();
            final int childRight = child.getRight();
            final int drawerPeekDistance = mLeftDragger.getEdgeSize();
            final float alpha =
                    Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
            mShadowLeftResolved.setBounds(childRight, child.getTop(),
                    childRight + shadowWidth, child.getBottom());
            mShadowLeftResolved.setAlpha((int) (0xff * alpha));
            mShadowLeftResolved.draw(canvas);
        } else if (mShadowRightResolved != null
                &&  checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) {
            final int shadowWidth = mShadowRightResolved.getIntrinsicWidth();
            final int childLeft = child.getLeft();
            final int showing = getWidth() - childLeft;
            final int drawerPeekDistance = mRightDragger.getEdgeSize();
            final float alpha =
                    Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f));
            mShadowRightResolved.setBounds(childLeft - shadowWidth, child.getTop(),
                    childLeft, child.getBottom());
            mShadowRightResolved.setAlpha((int) (0xff * alpha));
            mShadowRightResolved.draw(canvas);
        }
        return result;
    }

    boolean isContentView(View child) {
        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
    }

    boolean isDrawerView(View child) {
        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
        final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
                ViewCompat.getLayoutDirection(child));
        if ((absGravity & Gravity.LEFT) != 0) {
            // This child is a left-edge drawer
            return true;
        }
        // This child is a right-edge drawer
        return (absGravity & Gravity.RIGHT) != 0;
    }

    @SuppressWarnings("ShortCircuitBoolean")
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getActionMasked();

        // "|" used deliberately here; both methods should be invoked.
        final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev)
                | mRightDragger.shouldInterceptTouchEvent(ev);

        boolean interceptForTap = false;

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mInitialMotionX = x;
                mInitialMotionY = y;
                if (mScrimOpacity > 0) {
                    final View child = mLeftDragger.findTopChildUnder((int) x, (int) y);
                    if (child != null && isContentView(child)) {
                        interceptForTap = true;
                    }
                }
                mChildrenCanceledTouch = false;
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                // If we cross the touch slop, don't perform the delayed peek for an edge touch.
                if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) {
                    mLeftCallback.removeCallbacks();
                    mRightCallback.removeCallbacks();
                }
                break;
            }

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
                closeDrawers(true);
                mChildrenCanceledTouch = false;
            }
        }

        return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
    }

    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {

        // If this is not a pointer event, or if this is an hover exit, or we are not displaying
        // that the content view can't be interacted with, then don't override and do anything
        // special.
        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0
                || event.getAction() == MotionEvent.ACTION_HOVER_EXIT
                || mScrimOpacity <= 0) {
            return super.dispatchGenericMotionEvent(event);
        }

        final int childrenCount = getChildCount();
        if (childrenCount != 0) {
            final float x = event.getX();
            final float y = event.getY();

            // Walk through children from top to bottom.
            for (int i = childrenCount - 1; i >= 0; i--) {
                final View child = getChildAt(i);

                // If the event is out of bounds or the child is the content view, don't dispatch
                // to it.
                if (!isInBoundsOfChild(x, y, child) || isContentView(child)) {
                    continue;
                }

                // If a child handles it, return true.
                if (dispatchTransformedGenericPointerEvent(event, child)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mLeftDragger.processTouchEvent(ev);
        mRightDragger.processTouchEvent(ev);

        final int action = ev.getAction();

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mInitialMotionX = x;
                mInitialMotionY = y;
                mChildrenCanceledTouch = false;
                break;
            }

            case MotionEvent.ACTION_UP: {
                final float x = ev.getX();
                final float y = ev.getY();
                boolean peekingOnly = true;
                final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
                if (touchedView != null && isContentView(touchedView)) {
                    final float dx = x - mInitialMotionX;
                    final float dy = y - mInitialMotionY;
                    final int slop = mLeftDragger.getTouchSlop();
                    if (dx * dx + dy * dy < slop * slop) {
                        // Taps close a dimmed open drawer but only if it isn't locked open.
                        final View openDrawer = findOpenDrawer();
                        if (openDrawer != null) {
                            peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN;
                        }
                    }
                }
                closeDrawers(peekingOnly);
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                closeDrawers(true);
                mChildrenCanceledTouch = false;
                break;
            }
        }

        return true;
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (CHILDREN_DISALLOW_INTERCEPT
                || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT)
                        && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) {
            // If we have an edge touch we want to skip this and track it for later instead.
            super.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
        if (disallowIntercept) {
            closeDrawers(true);
        }
    }

    /**
     * Close all currently open drawer views by animating them out of view.
     */
    public void closeDrawers() {
        closeDrawers(false);
    }

    void closeDrawers(boolean peekingOnly) {
        boolean needsInvalidate = false;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {
                continue;
            }

            final int childWidth = child.getWidth();

            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
                needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,
                        -childWidth, child.getTop());
            } else {
                needsInvalidate |= mRightDragger.smoothSlideViewTo(child,
                        getWidth(), child.getTop());
            }

            lp.isPeeking = false;
        }

        mLeftCallback.removeCallbacks();
        mRightCallback.removeCallbacks();

        if (needsInvalidate) {
            invalidate();
        }
    }

    /**
     * Open the {@link GravityCompat#START} drawer by animating it into view.
     */
    @Override
    public void open() {
        openDrawer(GravityCompat.START);
    }

    /**
     * Open the specified drawer view by animating it into view.
     *
     * @param drawerView Drawer view to open
     */
    public void openDrawer(@NonNull View drawerView) {
        openDrawer(drawerView, true);
    }

    /**
     * Open the specified drawer view.
     *
     * @param drawerView Drawer view to open
     * @param animate Whether opening of the drawer should be animated.
     */
    public void openDrawer(@NonNull View drawerView, boolean animate) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
        }

        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        if (mFirstLayout) {
            lp.onScreen = 1.f;
            lp.openState = LayoutParams.FLAG_IS_OPENED;

            updateChildrenImportantForAccessibility(drawerView, true);
            updateChildAccessibilityAction(drawerView);
            updateBackInvokedCallbackState();
        } else if (animate) {
            lp.openState |= LayoutParams.FLAG_IS_OPENING;

            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
                mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
            } else {
                mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
                        drawerView.getTop());
            }
        } else {
            moveDrawerToOffset(drawerView, 1.f);
            updateDrawerState(STATE_IDLE, drawerView);
            drawerView.setVisibility(VISIBLE);
        }
        invalidate();
    }

    /**
     * Open the specified drawer by animating it out of view.
     *
     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
     *                GravityCompat.START or GravityCompat.END may also be used.
     */
    public void openDrawer(@EdgeGravity int gravity) {
        openDrawer(gravity, true);
    }

    /**
     * Open the specified drawer.
     *
     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
     *                GravityCompat.START or GravityCompat.END may also be used.
     * @param animate Whether opening of the drawer should be animated.
     */
    public void openDrawer(@EdgeGravity int gravity, boolean animate) {
        final View drawerView = findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity "
                    + gravityToString(gravity));
        }
        openDrawer(drawerView, animate);
    }

    /**
     * Close the {@link GravityCompat#START} drawer by animating it out of view.
     */
    @Override
    public void close() {
        closeDrawer(GravityCompat.START);
    }

    /**
     * Close the specified drawer view by animating it into view.
     *
     * @param drawerView Drawer view to close
     */
    public void closeDrawer(@NonNull View drawerView) {
        closeDrawer(drawerView, true);
    }

    /**
     * Close the specified drawer view.
     *
     * @param drawerView Drawer view to close
     * @param animate Whether closing of the drawer should be animated.
     */
    public void closeDrawer(@NonNull View drawerView, boolean animate) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
        }

        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        if (mFirstLayout) {
            lp.onScreen = 0.f;
            lp.openState = 0;
        } else if (animate) {
            lp.openState |= LayoutParams.FLAG_IS_CLOSING;

            if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
                mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
                        drawerView.getTop());
            } else {
                mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
            }
        } else {
            moveDrawerToOffset(drawerView, 0.f);
            updateDrawerState(STATE_IDLE, drawerView);
            drawerView.setVisibility(INVISIBLE);
        }
        invalidate();
    }

    /**
     * Close the specified drawer by animating it out of view.
     *
     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
     *                GravityCompat.START or GravityCompat.END may also be used.
     */
    public void closeDrawer(@EdgeGravity int gravity) {
        closeDrawer(gravity, true);
    }

    /**
     * Close the specified drawer.
     *
     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
     *                GravityCompat.START or GravityCompat.END may also be used.
     * @param animate Whether closing of the drawer should be animated.
     */
    public void closeDrawer(@EdgeGravity int gravity, boolean animate) {
        final View drawerView = findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity "
                    + gravityToString(gravity));
        }
        closeDrawer(drawerView, animate);
    }

    /**
     * Check if the given drawer view is currently in an open state.
     * To be considered "open" the drawer must have settled into its fully
     * visible state. To check for partial visibility use
     * {@link #isDrawerVisible(android.view.View)}.
     *
     * @param drawer Drawer view to check
     * @return true if the given drawer view is in an open state
     * @see #isDrawerVisible(android.view.View)
     */
    public boolean isDrawerOpen(@NonNull View drawer) {
        if (!isDrawerView(drawer)) {
            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
        }
        LayoutParams drawerLp = (LayoutParams) drawer.getLayoutParams();
        return (drawerLp.openState & LayoutParams.FLAG_IS_OPENED) == 1;
    }

    /**
     * Check if the {@link GravityCompat#START} drawer is currently in an open state.
     * To be considered "open" the drawer must have settled into its fully
     * visible state. If there is no drawer with the given gravity this method
     * will return false.
     *
     * @return true if the {@link GravityCompat#START} drawer is in an open state
     */
    @Override
    public boolean isOpen() {
        return isDrawerOpen(GravityCompat.START);
    }

    /**
     * Check if the given drawer view is currently in an open state.
     * To be considered "open" the drawer must have settled into its fully
     * visible state. If there is no drawer with the given gravity this method
     * will return false.
     *
     * @param drawerGravity Gravity of the drawer to check
     * @return true if the given drawer view is in an open state
     */
    public boolean isDrawerOpen(@EdgeGravity int drawerGravity) {
        final View drawerView = findDrawerWithGravity(drawerGravity);
        if (drawerView != null) {
            return isDrawerOpen(drawerView);
        }
        return false;
    }

    /**
     * Check if a given drawer view is currently visible on-screen. The drawer
     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
     *
     * @param drawer Drawer view to check
     * @return true if the given drawer is visible on-screen
     * @see #isDrawerOpen(android.view.View)
     */
    public boolean isDrawerVisible(@NonNull View drawer) {
        if (!isDrawerView(drawer)) {
            throw new IllegalArgumentException("View " + drawer + " is not a drawer");
        }
        return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
    }

    /**
     * Check if a given drawer view is currently visible on-screen. The drawer
     * may be only peeking onto the screen, fully extended, or anywhere in between.
     * If there is no drawer with the given gravity this method will return false.
     *
     * @param drawerGravity Gravity of the drawer to check
     * @return true if the given drawer is visible on-screen
     */
    public boolean isDrawerVisible(@EdgeGravity int drawerGravity) {
        final View drawerView = findDrawerWithGravity(drawerGravity);
        if (drawerView != null) {
            return isDrawerVisible(drawerView);
        }
        return false;
    }

    private boolean hasPeekingDrawer() {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
            if (lp.isPeeking) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams
                ? new LayoutParams((LayoutParams) p)
                : p instanceof ViewGroup.MarginLayoutParams
                ? new LayoutParams((MarginLayoutParams) p)
                : new LayoutParams(p);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams && super.checkLayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
        if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
            return;
        }

        // Only the views in the open drawers are focusables. Add normal child views when
        // no drawers are opened.
        final int childCount = getChildCount();
        boolean isDrawerOpen = false;
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (isDrawerView(child)) {
                if (isDrawerOpen(child)) {
                    isDrawerOpen = true;
                    child.addFocusables(views, direction, focusableMode);
                }
            } else {
                mNonDrawerViews.add(child);
            }
        }

        if (!isDrawerOpen) {
            final int nonDrawerViewsCount = mNonDrawerViews.size();
            for (int i = 0; i < nonDrawerViewsCount; ++i) {
                final View child = mNonDrawerViews.get(i);
                if (child.getVisibility() == View.VISIBLE) {
                    child.addFocusables(views, direction, focusableMode);
                }
            }
        }

        mNonDrawerViews.clear();
    }

    private boolean hasVisibleDrawer() {
        return findVisibleDrawer() != null;
    }

    View findVisibleDrawer() {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (isDrawerView(child) && isDrawerVisible(child)) {
                return child;
            }
        }
        return null;
    }

    void cancelChildViewTouch() {
        // Cancel child touches
        if (!mChildrenCanceledTouch) {
            final long now = SystemClock.uptimeMillis();
            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).dispatchTouchEvent(cancelEvent);
            }
            cancelEvent.recycle();
            mChildrenCanceledTouch = true;
        }
    }

    /**
     * Call this method whenever a property changes that affects whether the view will handle a
     * back press, which is the combination of properties inspected in {@link #closeDrawers()} and
     * properties that affect whether this view would normally receive key press events.
     */
    void updateBackInvokedCallbackState() {
        if (Build.VERSION.SDK_INT >= 33) {
            View visibleDrawer = findVisibleDrawer();
            OnBackInvokedDispatcher currentDispatcher = Api33Impl.findOnBackInvokedDispatcher(this);
            boolean shouldBeRegistered = visibleDrawer != null
                    && currentDispatcher != null
                    && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED
                    && ViewCompat.isAttachedToWindow(this);

            if (shouldBeRegistered && mBackInvokedDispatcher == null) {
                if (mBackInvokedCallback == null) {
                    mBackInvokedCallback = Api33Impl.newOnBackInvokedCallback(this::closeDrawers);
                }
                Api33Impl.tryRegisterOnBackInvokedCallback(
                        currentDispatcher, mBackInvokedCallback);
                mBackInvokedDispatcher = currentDispatcher;
            } else if (!shouldBeRegistered && mBackInvokedDispatcher != null) {
                Api33Impl.tryUnregisterOnBackInvokedCallback(
                        mBackInvokedDispatcher, mBackInvokedCallback);
                mBackInvokedDispatcher = null;
            }
        }
    }

    @VisibleForTesting
    boolean isBackInvokedCallbackRegistered() {
        return mBackInvokedDispatcher != null;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
            event.startTracking();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            final View visibleDrawer = findVisibleDrawer();
            if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) {
                closeDrawers();
            }
            return visibleDrawer != null;
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        final SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
            final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
            if (toOpen != null) {
                openDrawer(toOpen);
            }
        }

        if (ss.lockModeLeft != LOCK_MODE_UNDEFINED) {
            setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
        }
        if (ss.lockModeRight != LOCK_MODE_UNDEFINED) {
            setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
        }
        if (ss.lockModeStart != LOCK_MODE_UNDEFINED) {
            setDrawerLockMode(ss.lockModeStart, GravityCompat.START);
        }
        if (ss.lockModeEnd != LOCK_MODE_UNDEFINED) {
            setDrawerLockMode(ss.lockModeEnd, GravityCompat.END);
        }
    }

    @NonNull
    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        final SavedState ss = new SavedState(superState);

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // Is the current child fully opened (that is, not closing)?
            boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED);
            // Is the current child opening?
            boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING);
            if (isOpenedAndNotClosing || isClosedAndOpening) {
                // If one of the conditions above holds, save the child's gravity
                // so that we open that child during state restore.
                ss.openDrawerGravity = lp.gravity;
                break;
            }
        }

        ss.lockModeLeft = mLockModeLeft;
        ss.lockModeRight = mLockModeRight;
        ss.lockModeStart = mLockModeStart;
        ss.lockModeEnd = mLockModeEnd;

        return ss;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        super.addView(child, index, params);

        final View openDrawer = findOpenDrawer();
        if (openDrawer != null || isDrawerView(child)) {
            // A drawer is already open or the new view is a drawer, so the
            // new view should start out hidden.
            ViewCompat.setImportantForAccessibility(child,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
        } else {
            // Otherwise this is a content view and no drawer is open, so the
            // new view should start out visible.
            ViewCompat.setImportantForAccessibility(child,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        // We only need a delegate here if the framework doesn't understand
        // NO_HIDE_DESCENDANTS importance.
        if (!CAN_HIDE_DESCENDANTS) {
            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
        }
    }

    static boolean includeChildForAccessibility(View child) {
        // If the child is not important for accessibility we make
        // sure this hides the entire subtree rooted at it as the
        // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not
        // supported on older platforms but we want to hide the entire
        // content and not opened drawers if a drawer is opened.
        return ViewCompat.getImportantForAccessibility(child)
                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                    && ViewCompat.getImportantForAccessibility(child)
                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
    }

    /**
     * State persisted across instances
     */
    protected static class SavedState extends AbsSavedState {
        int openDrawerGravity = Gravity.NO_GRAVITY;
        @LockMode int lockModeLeft;
        @LockMode int lockModeRight;
        @LockMode int lockModeStart;
        @LockMode int lockModeEnd;

        public SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) {
            super(in, loader);
            openDrawerGravity = in.readInt();
            lockModeLeft = in.readInt();
            lockModeRight = in.readInt();
            lockModeStart = in.readInt();
            lockModeEnd = in.readInt();
        }

        public SavedState(@NonNull Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(openDrawerGravity);
            dest.writeInt(lockModeLeft);
            dest.writeInt(lockModeRight);
            dest.writeInt(lockModeStart);
            dest.writeInt(lockModeEnd);
        }

        public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in, ClassLoader loader) {
                return new SavedState(in, loader);
            }

            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in, null);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    private class ViewDragCallback extends ViewDragHelper.Callback {
        private final int mAbsGravity;
        private ViewDragHelper mDragger;

        private final Runnable mPeekRunnable = this::peekDrawer;

        ViewDragCallback(int gravity) {
            mAbsGravity = gravity;
        }

        public void setDragger(ViewDragHelper dragger) {
            mDragger = dragger;
        }

        public void removeCallbacks() {
            DrawerLayout.this.removeCallbacks(mPeekRunnable);
        }

        @Override
        public boolean tryCaptureView(@NonNull View child, int pointerId) {
            // Only capture views where the gravity matches what we're looking for.
            // This lets us use two ViewDragHelpers, one for each side drawer.
            return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity)
                    && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
        }

        @Override
        public void onViewDragStateChanged(int state) {
            updateDrawerState(state, mDragger.getCapturedView());
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            float offset;
            final int childWidth = changedView.getWidth();

            // This reverses the positioning shown in onLayout.
            if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) {
                offset = (float) (childWidth + left) / childWidth;
            } else {
                final int width = getWidth();
                offset = (float) (width - left) / childWidth;
            }
            setDrawerViewOffset(changedView, offset);
            changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
            invalidate();
        }

        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
            lp.isPeeking = false;

            closeOtherDrawer();
        }

        private void closeOtherDrawer() {
            final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
            final View toClose = findDrawerWithGravity(otherGrav);
            if (toClose != null) {
                closeDrawer(toClose);
            }
        }

        @Override
        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
            // Offset is how open the drawer is, therefore left/right values
            // are reversed from one another.
            final float offset = getDrawerViewOffset(releasedChild);
            final int childWidth = releasedChild.getWidth();

            int left;
            if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) {
                left = xvel > 0 || (xvel == 0 && offset > 0.5f) ? 0 : -childWidth;
            } else {
                final int width = getWidth();
                left = xvel < 0 || (xvel == 0 && offset > 0.5f) ? width - childWidth : width;
            }

            mDragger.settleCapturedViewAt(left, releasedChild.getTop());
            invalidate();
        }

        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            postDelayed(mPeekRunnable, PEEK_DELAY);
        }

        void peekDrawer() {
            final View toCapture;
            final int childLeft;
            final int peekDistance = mDragger.getEdgeSize();
            final boolean leftEdge = mAbsGravity == Gravity.LEFT;
            if (leftEdge) {
                toCapture = findDrawerWithGravity(Gravity.LEFT);
                childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance;
            } else {
                toCapture = findDrawerWithGravity(Gravity.RIGHT);
                childLeft = getWidth() - peekDistance;
            }
            // Only peek if it would mean making the drawer more visible and the drawer isn't locked
            if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft)
                    || (!leftEdge && toCapture.getLeft() > childLeft))
                    && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
                final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();
                mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());
                lp.isPeeking = true;
                invalidate();

                closeOtherDrawer();

                cancelChildViewTouch();
            }
        }

        @Override
        public boolean onEdgeLock(int edgeFlags) {
            if (ALLOW_EDGE_LOCK) {
                final View drawer = findDrawerWithGravity(mAbsGravity);
                if (drawer != null && !isDrawerOpen(drawer)) {
                    closeDrawer(drawer);
                }
                return true;
            }
            return false;
        }

        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            final View toCapture;
            if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) {
                toCapture = findDrawerWithGravity(Gravity.LEFT);
            } else {
                toCapture = findDrawerWithGravity(Gravity.RIGHT);
            }

            if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {
                mDragger.captureChildView(toCapture, pointerId);
            }
        }

        @Override
        public int getViewHorizontalDragRange(@NonNull View child) {
            return isDrawerView(child) ? child.getWidth() : 0;
        }

        @Override
        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
            if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
                return Math.max(-child.getWidth(), Math.min(left, 0));
            } else {
                final int width = getWidth();
                return Math.max(width - child.getWidth(), Math.min(left, width));
            }
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return child.getTop();
        }
    }

    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        private static final int FLAG_IS_OPENED = 0x1;
        private static final int FLAG_IS_OPENING = 0x2;
        private static final int FLAG_IS_CLOSING = 0x4;

        @EdgeGravity
        public int gravity = Gravity.NO_GRAVITY;
        float onScreen;
        boolean isPeeking;
        int openState;

        public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
            super(c, attrs);

            final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
            this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity) {
            this(width, height);
            this.gravity = gravity;
        }

        @SuppressWarnings("CopyConstructorMissesField") // incorrect
        public LayoutParams(@NonNull LayoutParams source) {
            super(source);
            this.gravity = source.gravity;
        }

        public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
            super(source);
        }
    }

    class AccessibilityDelegate extends AccessibilityDelegateCompat {
        private final Rect mTmpRect = new Rect();

        @Override
        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
            if (CAN_HIDE_DESCENDANTS) {
                super.onInitializeAccessibilityNodeInfo(host, info);
            } else {
                // Obtain a node for the host, then manually generate the list
                // of children to only include non-obscured views.
                final AccessibilityNodeInfoCompat superNode =
                        AccessibilityNodeInfoCompat.obtain(info);
                super.onInitializeAccessibilityNodeInfo(host, superNode);

                info.setSource(host);
                final ViewParent parent = ViewCompat.getParentForAccessibility(host);
                if (parent instanceof View) {
                    info.setParent((View) parent);
                }
                copyNodeInfoNoChildren(info, superNode);
                superNode.recycle();

                addChildrenForAccessibility(info, (ViewGroup) host);
            }

            info.setClassName(ACCESSIBILITY_CLASS_NAME);

            // This view reports itself as focusable so that it can intercept
            // the back button, but we should prevent this view from reporting
            // itself as focusable to accessibility services.
            info.setFocusable(false);
            info.setFocused(false);
            info.removeAction(AccessibilityActionCompat.ACTION_FOCUS);
            info.removeAction(AccessibilityActionCompat.ACTION_CLEAR_FOCUS);
        }

        @Override
        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
            super.onInitializeAccessibilityEvent(host, event);

            event.setClassName(ACCESSIBILITY_CLASS_NAME);
        }

        @Override
        public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
            // Special case to handle window state change events. As far as
            // accessibility services are concerned, state changes from
            // DrawerLayout invalidate the entire contents of the screen (like
            // an Activity or Dialog) and they should announce the title of the
            // new content.
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
                final List<CharSequence> eventText = event.getText();
                final View visibleDrawer = findVisibleDrawer();
                if (visibleDrawer != null) {
                    final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer);
                    final CharSequence title = getDrawerTitle(edgeGravity);
                    if (title != null) {
                        eventText.add(title);
                    }
                }

                return true;
            }

            return super.dispatchPopulateAccessibilityEvent(host, event);
        }

        @Override
        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
                AccessibilityEvent event) {
            if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
                return super.onRequestSendAccessibilityEvent(host, child, event);
            }
            return false;
        }

        private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) {
            final int childCount = v.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = v.getChildAt(i);
                if (includeChildForAccessibility(child)) {
                    info.addChild(child);
                }
            }
        }

        /**
         * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
         * seem to be a few elements that are not easily cloneable using the underlying API.
         * Leave it private here as it's not general-purpose useful.
         */
        private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
                AccessibilityNodeInfoCompat src) {
            final Rect rect = mTmpRect;

            src.getBoundsInScreen(rect);
            dest.setBoundsInScreen(rect);

            dest.setVisibleToUser(src.isVisibleToUser());
            dest.setPackageName(src.getPackageName());
            dest.setClassName(src.getClassName());
            dest.setContentDescription(src.getContentDescription());

            dest.setEnabled(src.isEnabled());
            dest.setFocused(src.isFocused());
            dest.setAccessibilityFocused(src.isAccessibilityFocused());
            dest.setSelected(src.isSelected());

            dest.addAction(src.getActions());
        }
    }

    static final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat {
        @Override
        public void onInitializeAccessibilityNodeInfo(View child,
                AccessibilityNodeInfoCompat info) {
            super.onInitializeAccessibilityNodeInfo(child, info);

            if (!includeChildForAccessibility(child)) {
                // If we are ignoring the sub-tree rooted at the child,
                // break the connection to the rest of the node tree.
                // For details refer to includeChildForAccessibility.
                info.setParent(null);
            }
        }
    }

    @RequiresApi(33)
    static class Api33Impl {
        private Api33Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static void tryRegisterOnBackInvokedCallback(@NonNull Object dispatcherObj,
                @NonNull Object callback) {
            OnBackInvokedDispatcher dispatcher = (OnBackInvokedDispatcher) dispatcherObj;
            dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_OVERLAY,
                    (OnBackInvokedCallback) callback);
        }

        @DoNotInline
        static void tryUnregisterOnBackInvokedCallback(@NonNull Object dispatcherObj,
                @NonNull Object callbackObj) {
            OnBackInvokedDispatcher dispatcher = (OnBackInvokedDispatcher) dispatcherObj;
            dispatcher.unregisterOnBackInvokedCallback((OnBackInvokedCallback) callbackObj);
        }

        @Nullable
        @DoNotInline
        static OnBackInvokedDispatcher findOnBackInvokedDispatcher(@NonNull DrawerLayout view) {
            return view.findOnBackInvokedDispatcher();
        }

        @NonNull
        @DoNotInline
        static OnBackInvokedCallback newOnBackInvokedCallback(@NonNull Runnable action) {
            return action::run;
        }
    }
}