content: /*
   * Copyright (C) 2013 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 allen.town.focus.twitter.views.widgets;
  
  import android.app.Activity;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.*;
  import android.graphics.drawable.Drawable;
  import android.os.Build;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.SystemClock;
  import androidx.core.view.*;
  import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
  import android.util.AttributeSet;
  import android.view.*;
  import android.view.accessibility.AccessibilityEvent;
  
  import com.klinker.android.peekview.util.DensityUtils;
  import allen.town.focus.twitter.views.ViewDragHelper;
  
  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 the edge of the window.
   * <p/>
   * <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.)
   * </p>
   * <p/>
   * <p>To use a DrawerLayout, position your primary content view as the first child with
   * a width and height of <code>match_parent</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/>
   * <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/>
   * <p>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.</p>
   */
  public class NotificationDrawerLayout extends ViewGroup {
      private static final String TAG = "DrawerLayout";
  
      /**
       * 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;
  
      /**
       * 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;
  
      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 int[] LAYOUT_ATTRS = new int[]{
              android.R.attr.layout_gravity
      };
  
      private int mMinDrawerMargin;
  
      private int mScrimColor = DEFAULT_SCRIM_COLOR;
      private float mScrimOpacity;
      private 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;
      private int mLockModeLeft;
      private int mLockModeRight;
      private boolean mDisallowInterceptRequested;
      private boolean mChildrenCanceledTouch;
  
      private ActionBarDrawerToggle mListener;
  
      private float mInitialMotionX;
      private float mInitialMotionY;
  
      private Drawable mShadowLeft;
      private Drawable mShadowRight;
  
      private List<Rect> exclusionRects = new ArrayList<>();
  
      /**
       * 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
           */
          public void onDrawerSlide(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
           */
          public void onDrawerOpened(View drawerView);
  
          /**
           * Called when a drawer has settled in a completely closed state.
           *
           * @param drawerView Drawer view that is now closed
           */
          public void onDrawerClosed(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
           */
          public void onDrawerStateChanged(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 static abstract class SimpleDrawerListener implements DrawerListener {
          @Override
          public void onDrawerSlide(View drawerView, float slideOffset) {
          }
  
          @Override
          public void onDrawerOpened(View drawerView) {
          }
  
          @Override
          public void onDrawerClosed(View drawerView) {
          }
  
          @Override
          public void onDrawerStateChanged(int newState) {
          }
      }
  
      public NotificationDrawerLayout(Context context) {
          this(context, null);
      }
  
      public NotificationDrawerLayout(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public NotificationDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
  
          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, 0.5f, mLeftCallback);
          mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
          mLeftDragger.setMinVelocity(minVel);
          mLeftCallback.setDragger(mLeftDragger);
  
          mRightDragger = ViewDragHelper.create(this, 0.5f, mRightCallback);
          mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
          mRightDragger.setMinVelocity(minVel);
          mRightCallback.setDragger(mRightDragger);
  
          // So that we can catch the back button
          setFocusableInTouchMode(true);
  
          ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
          ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
  
          exclusionRects.add(new Rect(0, 0, DensityUtils.toDp(getContext(), 100), 0));
      }
  
      /**
       * Set a simple drawable used for the left or right shadow.
       * The drawable provided must have a nonzero intrinsic width.
       *
       * @param shadowDrawable Shadow drawable to use at the edge of a drawer
       * @param gravity        Which drawer the shadow should apply to
       */
      public void setDrawerShadow(Drawable shadowDrawable, 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.
           */
  
          final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
                  ViewCompat.getLayoutDirection(this));
          if ((absGravity & Gravity.LEFT) == Gravity.LEFT) {
              mShadowLeft = shadowDrawable;
              invalidate();
          }
          if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) {
              mShadowRight = shadowDrawable;
              invalidate();
          }
      }
  
      /**
       * Set a simple drawable used for the left or right shadow.
       * The drawable provided must have a nonzero intrinsic width.
       *
       * @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(int resId, int gravity) {
          setDrawerShadow(getResources().getDrawable(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(int color) {
          mScrimColor = color;
          invalidate();
      }
  
      /**
       * Set a listener to be notified of drawer events.
       *
       * @param listener Listener to notify when drawer events occur
       * @see DrawerListener
       */
      public void setDrawerListener(ActionBarDrawerToggle listener) {
          mListener = listener;
      }
  
      /**
       * Enable or disable interaction with all drawers.
       * <p/>
       * <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/>
       * <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(int lockMode) {
          setDrawerLockMode(lockMode, Gravity.LEFT);
          setDrawerLockMode(lockMode, Gravity.RIGHT);
      }
  
      /**
       * Enable or disable interaction with the given drawer.
       * <p/>
       * <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/>
       * <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(int lockMode, int edgeGravity) {
          final int absGrav = GravityCompat.getAbsoluteGravity(edgeGravity,
                  ViewCompat.getLayoutDirection(this));
          if (absGrav == Gravity.LEFT) {
              mLockModeLeft = lockMode;
          } else if (absGrav == Gravity.RIGHT) {
              mLockModeRight = lockMode;
          }
          if (lockMode != LOCK_MODE_UNLOCKED) {
              // Cancel interaction in progress
              final ViewDragHelper helper = absGrav == Gravity.LEFT ? mLeftDragger : mRightDragger;
              helper.cancel();
          }
          switch (lockMode) {
              case LOCK_MODE_LOCKED_OPEN:
                  final View toOpen = findDrawerWithGravity(absGrav);
                  if (toOpen != null) {
                      openDrawer(toOpen);
                  }
                  break;
              case LOCK_MODE_LOCKED_CLOSED:
                  final View toClose = findDrawerWithGravity(absGrav);
                  if (toClose != null) {
                      closeDrawer(toClose);
                  }
                  break;
              // default: do nothing
          }
      }
  
      /**
       * Enable or disable interaction with the given drawer.
       * <p/>
       * <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/>
       * <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(int lockMode, View drawerView) {
          if (!isDrawerView(drawerView)) {
              throw new IllegalArgumentException("View " + drawerView + " is not a " +
                      "drawer with appropriate layout_gravity");
          }
          setDrawerLockMode(lockMode, getDrawerViewGravity(drawerView));
      }
  
      /**
       * 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}.
       */
      public int getDrawerLockMode(int edgeGravity) {
          final int absGrav = GravityCompat.getAbsoluteGravity(edgeGravity,
                  ViewCompat.getLayoutDirection(this));
          if (absGrav == Gravity.LEFT) {
              return mLockModeLeft;
          } else if (absGrav == Gravity.RIGHT) {
              return mLockModeRight;
          }
          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}.
       */
      public int getDrawerLockMode(View drawerView) {
          final int gravity = getDrawerViewGravity(drawerView);
          if (gravity == Gravity.LEFT) {
              return mLockModeLeft;
          } else if (gravity == Gravity.RIGHT) {
              return mLockModeRight;
          }
  
          return LOCK_MODE_UNLOCKED;
      }
  
      /**
       * Resolve the shared state of all drawers from the component ViewDragHelpers.
       * Should be called whenever a ViewDragHelper's state changes.
       */
      void updateDrawerState(int forGravity, 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 (mListener != null) {
                  mListener.onDrawerStateChanged(state);
              }
          }
      }
  
      void dispatchOnDrawerClosed(View drawerView) {
          final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
          if (lp.knownOpen) {
              lp.knownOpen = false;
              if (mListener != null) {
                  mListener.onDrawerClosed(drawerView);
              }
              sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
          }
      }
  
      void dispatchOnDrawerOpened(View drawerView) {
          final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
          if (!lp.knownOpen) {
              lp.knownOpen = true;
              if (mListener != null) {
                  mListener.onDrawerOpened(drawerView);
              }
              drawerView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
          }
      }
  
      void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
          if (mListener != null) {
              mListener.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;
      }
  
      int getDrawerViewGravity(View drawerView) {
          final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
          return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerView));
      }
  
      boolean checkDrawerViewGravity(View drawerView, int checkFor) {
          final int absGrav = getDrawerViewGravity(drawerView);
          return (absGrav & checkFor) == checkFor;
      }
  
      View findOpenDrawer() {
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View child = getChildAt(i);
              if (((LayoutParams) child.getLayoutParams()).knownOpen) {
                  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(checkDrawerViewGravity(drawerView, Gravity.LEFT) ? dx : -dx);
          setDrawerViewOffset(drawerView, slideOffset);
      }
  
      View findDrawerWithGravity(int gravity) {
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              final View child = getChildAt(i);
              final int childGravity = getDrawerViewGravity(child);
              if ((childGravity & Gravity.HORIZONTAL_GRAVITY_MASK) ==
                      (gravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
                  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(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;
      }
  
      @Override
      protected void onAttachedToWindow() {
          super.onAttachedToWindow();
          mFirstLayout = true;
      }
  
      @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.AT_MOST) {
                      widthMode = MeasureSpec.EXACTLY;
                  } else if (widthMode == MeasureSpec.UNSPECIFIED) {
                      widthMode = MeasureSpec.EXACTLY;
                      widthSize = 300;
                  }
                  if (heightMode == MeasureSpec.AT_MOST) {
                      heightMode = MeasureSpec.EXACTLY;
                  } else if (heightMode == MeasureSpec.UNSPECIFIED) {
                      heightMode = MeasureSpec.EXACTLY;
                      heightSize = 300;
                  }
              } else {
                  throw new IllegalArgumentException(
                          "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
              }
          }
  
          setMeasuredDimension(widthSize, heightSize);
  
          // Gravity value for each drawer we've seen. Only one of each permitted.
          int foundDrawers = 0;
          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)) {
                  // 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)) {
                  final int childGravity =
                          getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
                  if ((foundDrawers & childGravity) != 0) {
                      throw new IllegalStateException("Child drawer has absolute gravity " +
                              gravityToString(childGravity) + " but this " + TAG + " already has a " +
                              "drawer view along that edge");
                  }
                  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");
              }
          }
      }
  
      @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
              exclusionRects.get(0).bottom = b;
              setSystemGestureExclusionRects(exclusionRects);
          }
  
          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)) {
                  try {
                      child.layout(lp.leftMargin, lp.topMargin,
                              lp.leftMargin + child.getMeasuredWidth(),
                              lp.topMargin + child.getMeasuredHeight());
                  } catch (Exception e) {
  
                  }
              } 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 (checkDrawerViewGravity(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, 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);
                  }
              }
          }
          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;
  
          // "|" used on purpose; both need to run.
          if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) {
              ViewCompat.postInvalidateOnAnimation(this);
          }
      }
  
      private static boolean hasOpaqueBackground(View v) {
          final Drawable bg = v.getBackground();
          if (bg != null) {
              return bg.getOpacity() == PixelFormat.OPAQUE;
          }
          return false;
      }
  
      @Override
      protected boolean drawChild(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 (checkDrawerViewGravity(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 (mShadowLeft != null && checkDrawerViewGravity(child, Gravity.LEFT)) {
              final int shadowWidth = mShadowLeft.getIntrinsicWidth();
              final int childRight = child.getRight();
              final int drawerPeekDistance = mLeftDragger.getEdgeSize();
              final float alpha =
                      Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f));
              mShadowLeft.setBounds(childRight, child.getTop(),
                      childRight + shadowWidth, child.getBottom());
              mShadowLeft.setAlpha((int) (0xff * alpha));
              mShadowLeft.draw(canvas);
          } else if (mShadowRight != null && checkDrawerViewGravity(child, Gravity.RIGHT)) {
              final int shadowWidth = mShadowRight.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));
              mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(),
                      childLeft, child.getBottom());
              mShadowRight.setAlpha((int) (0xff * alpha));
              mShadowRight.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));
          return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
      }
  
      @Override
      public boolean onInterceptTouchEvent(MotionEvent ev) {
          final int action = MotionEventCompat.getActionMasked(ev);
  
          // "|" used deliberately here; both methods should be invoked.
          try {
              final boolean interceptForDrag = isDrawerOpen(Gravity.RIGHT) ? mRightDragger.shouldInterceptTouchEvent(ev) :
                      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 &&
                              isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) {
                          interceptForTap = true;
                      }
                      mDisallowInterceptRequested = false;
                      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);
                      mDisallowInterceptRequested = false;
                      mChildrenCanceledTouch = false;
                  }
              }
  
              return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch;
          } catch (Exception e) {
              return false;
          }
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          try {
              mLeftDragger.processTouchEvent(ev);
          } catch (Exception e) {
  
          }
  
          try {
              mRightDragger.processTouchEvent(ev);
          } catch (Exception e) {
  
          }
  
          final int action = ev.getAction();
          boolean wantTouchEvents = true;
  
          switch (action & MotionEventCompat.ACTION_MASK) {
              case MotionEvent.ACTION_DOWN: {
                  final float x = ev.getX();
                  final float y = ev.getY();
                  mInitialMotionX = x;
                  mInitialMotionY = y;
                  mDisallowInterceptRequested = false;
                  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);
                  mDisallowInterceptRequested = false;
                  break;
              }
  
              case MotionEvent.ACTION_CANCEL: {
                  closeDrawers(true);
                  mDisallowInterceptRequested = false;
                  mChildrenCanceledTouch = false;
                  break;
              }
          }
  
          return wantTouchEvents;
      }
  
      public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
          try {
              if (!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);
              }
              mDisallowInterceptRequested = disallowIntercept;
              if (disallowIntercept) {
                  closeDrawers(true);
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      /**
       * 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 (checkDrawerViewGravity(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 specified drawer view by animating it into view.
       *
       * @param drawerView Drawer view to open
       */
      public void openDrawer(View drawerView) {
          if (!isDrawerView(drawerView)) {
              throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
          }
  
          if (mFirstLayout) {
              final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
              lp.onScreen = 1.f;
              lp.knownOpen = true;
          } else {
              if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) {
                  mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
              } else {
                  mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
                          drawerView.getTop());
              }
          }
          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(int gravity) {
          final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
                  ViewCompat.getLayoutDirection(this));
          final View drawerView = findDrawerWithGravity(absGravity);
  
          if (drawerView == null) {
              throw new IllegalArgumentException("No drawer view found with absolute gravity " +
                      gravityToString(absGravity));
          }
          openDrawer(drawerView);
      }
  
      /**
       * Close the specified drawer view by animating it into view.
       *
       * @param drawerView Drawer view to close
       */
      public void closeDrawer(View drawerView) {
          if (!isDrawerView(drawerView)) {
              throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
          }
  
          if (mFirstLayout) {
              final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
              lp.onScreen = 0.f;
              lp.knownOpen = false;
          } else {
              if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) {
                  mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(),
                          drawerView.getTop());
              } else {
                  mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
              }
          }
          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(int gravity) {
          final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
                  ViewCompat.getLayoutDirection(this));
          final View drawerView = findDrawerWithGravity(absGravity);
  
          if (drawerView == null) {
              throw new IllegalArgumentException("No drawer view found with absolute gravity " +
                      gravityToString(absGravity));
          }
          closeDrawer(drawerView);
      }
  
      /**
       * 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(View drawer) {
          if (!isDrawerView(drawer)) {
              throw new IllegalArgumentException("View " + drawer + " is not a drawer");
          }
          return ((LayoutParams) drawer.getLayoutParams()).knownOpen;
      }
  
      /**
       * 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(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(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 inbetween.
       * 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(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.FILL_PARENT, LayoutParams.FILL_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);
      }
  
      private boolean hasVisibleDrawer() {
          return findVisibleDrawer() != null;
      }
  
      private 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;
          }
      }
  
      @Override
      public boolean onKeyDown(int keyCode, KeyEvent event) {
          if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
              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) {
          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);
              }
          }
  
          setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT);
          setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT);
      }
  
      @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);
              if (!isDrawerView(child)) {
                  continue;
              }
  
              final LayoutParams lp = (LayoutParams) child.getLayoutParams();
              if (lp.knownOpen) {
                  ss.openDrawerGravity = lp.gravity;
                  // Only one drawer can be open at a time.
                  break;
              }
          }
  
          ss.lockModeLeft = mLockModeLeft;
          ss.lockModeRight = mLockModeRight;
  
          return ss;
      }
  
      /**
       * State persisted across instances
       */
      protected static class SavedState extends BaseSavedState {
          int openDrawerGravity = Gravity.NO_GRAVITY;
          int lockModeLeft = LOCK_MODE_UNLOCKED;
          int lockModeRight = LOCK_MODE_UNLOCKED;
  
          public SavedState(Parcel in) {
              super(in);
              openDrawerGravity = in.readInt();
          }
  
          public SavedState(Parcelable superState) {
              super(superState);
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              super.writeToParcel(dest, flags);
              dest.writeInt(openDrawerGravity);
          }
  
          public static final Parcelable.Creator<SavedState> CREATOR =
                  new Parcelable.Creator<SavedState>() {
                      @Override
                      public SavedState createFromParcel(Parcel source) {
                          return new SavedState(source);
                      }
  
                      @Override
                      public SavedState[] newArray(int size) {
                          return new SavedState[size];
                      }
                  };
      }
  
      private class ViewDragCallback extends ViewDragHelper.Callback {
          private final int mGravity;
          private ViewDragHelper mDragger;
  
          private final Runnable mPeekRunnable = new Runnable() {
              @Override
              public void run() {
                  peekDrawer();
              }
          };
  
          public ViewDragCallback(int gravity) {
              mGravity = gravity;
          }
  
          public void setDragger(ViewDragHelper dragger) {
              mDragger = dragger;
          }
  
          public void removeCallbacks() {
              NotificationDrawerLayout.this.removeCallbacks(mPeekRunnable);
          }
  
          @Override
          public boolean tryCaptureView(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) && checkDrawerViewGravity(child, mGravity) &&
                      getDrawerLockMode(child) == LOCK_MODE_UNLOCKED;
          }
  
          @Override
          public void onViewDragStateChanged(int state) {
              updateDrawerState(mGravity, 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 (checkDrawerViewGravity(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 = mGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
              final View toClose = findDrawerWithGravity(otherGrav);
              if (toClose != null) {
                  closeDrawer(toClose);
              }
          }
  
          @Override
          public void onViewReleased(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 (checkDrawerViewGravity(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);
          }
  
          private void peekDrawer() {
              final View toCapture;
              final int childLeft;
              final int peekDistance = mDragger.getEdgeSize();
              final boolean leftEdge = mGravity == 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(mGravity);
                  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(View child) {
              return child.getWidth();
          }
  
          @Override
          public int clampViewPositionHorizontal(View child, int left, int dx) {
              if (checkDrawerViewGravity(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 {
  
          public int gravity = Gravity.NO_GRAVITY;
          float onScreen;
          boolean isPeeking;
          boolean knownOpen;
  
          public LayoutParams(Context c, 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;
          }
  
          public LayoutParams(LayoutParams source) {
              super(source);
              this.gravity = source.gravity;
          }
  
          public LayoutParams(ViewGroup.LayoutParams source) {
              super(source);
          }
  
          public LayoutParams(ViewGroup.MarginLayoutParams source) {
              super(source);
          }
      }
  
      class AccessibilityDelegate extends AccessibilityDelegateCompat {
          private final Rect mTmpRect = new Rect();
  
          @Override
          public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
              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();
  
              final int childCount = getChildCount();
              for (int i = 0; i < childCount; i++) {
                  final View child = getChildAt(i);
                  if (!filter(child)) {
                      info.addChild(child);
                  }
              }
          }
  
          @Override
          public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
                                                         AccessibilityEvent event) {
              if (!filter(child)) {
                  return super.onRequestSendAccessibilityEvent(host, child, event);
              }
              return false;
          }
  
          public boolean filter(View child) {
              final View openDrawer = findOpenDrawer();
              return openDrawer != null && openDrawer != 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.getBoundsInParent(rect);
              dest.setBoundsInParent(rect);
  
              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.setClickable(src.isClickable());
              dest.setFocusable(src.isFocusable());
              dest.setFocused(src.isFocused());
              dest.setAccessibilityFocused(src.isAccessibilityFocused());
              dest.setSelected(src.isSelected());
              dest.setLongClickable(src.isLongClickable());
  
              dest.addAction(src.getActions());
          }
      }
  
      public void setDrawerRightEdgeSize(Activity activity, float displayWidthPercentage) {
          Point displaySize = new Point();
          activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
          mRightDragger.setEdgeSize((int) (displaySize.x * displayWidthPercentage));
      }
  }