docs: void select(ViewHolder selected, int actionState) {
          if (selected == mSelected && actionState == mActionState) {
              return;
          }
          mDragScrollStartTimeInMs = Long.MIN_VALUE;
          final int prevActionState = mActionState;
          // prevent duplicate animations
          endRecoverAnimation(selected, true);
          mActionState = actionState;
          if (actionState == ACTION_STATE_DRAG) {
              // we remove after animation is complete. this means we only elevate the last drag
              // child but that should perform good enough as it is very hard to start dragging a
              // new child before the previous one settles.
              mOverdrawChild = selected.itemView;
              addChildDrawingOrderCallback();
          }
          int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState))
                  - 1;
          boolean preventLayout = false;
          if (mSelected != null) {
              final ViewHolder prevSelected = mSelected;
              if (prevSelected.itemView.getParent() != null) {
                  final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
                          : swipeIfNecessary(prevSelected);
                  releaseVelocityTracker();
                  // find where we should animate to
                  final float targetTranslateX, targetTranslateY;
                  int animationType;
                  switch (swipeDir) {
                      case LEFT:
                      case RIGHT:
                      case START:
                      case END:
                          targetTranslateY = 0;
                          targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
                          break;
                      case UP:
                      case DOWN:
                          targetTranslateX = 0;
                          targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
                          break;
                      default:
                          targetTranslateX = 0;
                          targetTranslateY = 0;
                  }
                  if (prevActionState == ACTION_STATE_DRAG) {
                      animationType = ANIMATION_TYPE_DRAG;
                  } else if (swipeDir > 0) {
                      animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
                  } else {
                      animationType = ANIMATION_TYPE_SWIPE_CANCEL;
                  }
                  getSelectedDxDy(mTmpPosition);
                  final float currentTranslateX = mTmpPosition[0];
                  final float currentTranslateY = mTmpPosition[1];
                  final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
                          prevActionState, currentTranslateX, currentTranslateY,
                          targetTranslateX, targetTranslateY) {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          super.onAnimationEnd(animation);
                          if (this.mOverridden) {
                              return;
                          }
                          if (swipeDir <= 0) {
                              // this is a drag or failed swipe. recover immediately
                              mCallback.clearView(mRecyclerView, prevSelected);
                              // full cleanup will happen on onDrawOver
                          } else {
                              // wait until remove animation is complete.
                              mPendingCleanup.add(prevSelected.itemView);
                              mIsPendingCleanup = true;
                              if (swipeDir > 0) {
                                  // Animation might be ended by other animators during a layout.
                                  // We defer callback to avoid editing adapter during a layout.
                                  postDispatchSwipe(this, swipeDir);
                              }
                          }
                          // removed from the list after it is drawn for the last time
                          if (mOverdrawChild == prevSelected.itemView) {
                              removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
                          }
                      }
                  };
                  final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
                          targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
                  rv.setDuration(duration);
                  mRecoverAnimations.add(rv);
                  rv.start();
                  preventLayout = true;
              } else {
                  removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
                  mCallback.clearView(mRecyclerView, prevSelected);
              }
              mSelected = null;
          }
          if (selected != null) {
              mSelectedFlags =
                      (mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
                              >> (mActionState * DIRECTION_FLAG_COUNT);
              mSelectedStartX = selected.itemView.getLeft();
              mSelectedStartY = selected.itemView.getTop();
              mSelected = selected;
              if (actionState == ACTION_STATE_DRAG) {
                  mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
              }
          }
          final ViewParent rvParent = mRecyclerView.getParent();
          if (rvParent != null) {
              rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
          }
          if (!preventLayout) {
              mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
          }
          mCallback.onSelectedChanged(mSelected, mActionState);
          mRecyclerView.invalidate();
      }