android P/Q/R/S 9/10/11/12 multi task gesture animation OverviewInputConsumer - Section 2

android P/Q/R/S 9/10/11/12 multi task gesture animation OverviewInputConsumer - Section 2

hi, multitasking gesture analyzes OtherActivity. In this section, we will analyze the source code and situation analysis of multitasking when the desktop itself is the foreground.
First, let's take a look at the two processes of multitasking on the native aosp:

Key phenomena:[

[introductory course, practical course, cross process project, input project]( https://ke.qq.com/course/package/51285?tuin=7d4eb354)
ps needs to study in-depth framework courses and course discounts
Please join qq group: 422901085 (get demo source code)

1. Slide your fingers slowly, and the workspace as a whole will also slide slowly


This process is the process of drawing on the bottom of our fingers. It is better to use a self drawn figure to show some:

That is to say, when the bottom of the finger is scratched up, the whole workspace will be scratched up slowly

2. When the upper stroke reaches a certain critical value, there is a direct animation process to enter the multi task

This process is relatively simple and easy to understand. There is no need to add additional pictures to explain a multi task interface

Key source code analysis:

1. workspace slowly up the process

First, the global touch listener listens to touch events. In this case, the OverviewInputConsumer listens to touch events
The specific path is in the following classes of the Launcher Code:
com/android/quickstep/inputconsumers/OverviewInputConsumer.java

 public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
            boolean startingInActivityBounds) {
        mActivity = activity;
        mInputMonitor = inputMonitor;
        mStartingInActivityBounds = startingInActivityBounds;

        mTarget = activity.getDragLayer();
        if (startingInActivityBounds) {
            mEventReceiver = mTarget::dispatchTouchEvent;
            mProxyTouch = true;
        } else {
            // Only proxy touches to controllers if we are starting touch from nav bar.
            mEventReceiver = mTarget::proxyTouchEvent;//proxyTouchEvent is the key processing point here
            mTarget.getLocationOnScreen(mLocationOnScreen);
            mProxyTouch = mTarget.prepareProxyEventStarting();
        }
    }

    @Override
    public int getType() {
        return TYPE_OVERVIEW;
    }

    @Override
    public boolean allowInterceptByParent() {
        return !mTargetHandledTouch;
    }

    @Override
    public void onMotionEvent(MotionEvent ev) {
        if (!mProxyTouch) {
            return;
        }

        int flags = ev.getEdgeFlags();
        if (!mStartingInActivityBounds) {
            ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
        }
        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
        boolean handled = mEventReceiver.test(ev);//This will trigger the call to proxyTouchEvent
        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
        ev.setEdgeFlags(flags);

   //ellipsis
    }

//ellipsis
}

After the onMotionEvent method is called, the proxyTouchEvent method is called to process
packages/apps/Trebuchet/src/com/android/launcher3/views/BaseDragLayer.java

    /**
     * Proxies the touch events to the gesture handlers
     */
    public boolean proxyTouchEvent(MotionEvent ev) {
        boolean handled;
        if (mProxyTouchController != null) {//Must be null at first
            handled = mProxyTouchController.onControllerTouchEvent(ev);
        } else {
            mProxyTouchController = findControllerToHandleTouch(ev);//Need to traverse to find the appropriate TouchController
            handled = mProxyTouchController != null;
        }
        int action = ev.getAction();
        if (action == ACTION_UP || action == ACTION_CANCEL) {
            mProxyTouchController = null;
            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
        }
        return handled;
    }
//Here's a look at the findControllerToHandleTouch method
        private TouchController findControllerToHandleTouch(MotionEvent ev) {
        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
            return topView;
        }

        for (TouchController controller : mControllers) {
        //Traverse to see which TouchController will be interested in this event and return who
            if (controller.onControllerInterceptTouchEvent(ev)) {
                return controller;
            }
        }
        return null;
    }

Here we mControllers are actually
packages/apps/Trebuchet/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java

public static TouchController[] createTouchControllers(Launcher launcher) {
    ArrayList<TouchController> list = new ArrayList<>();
    list.add(launcher.getDragController());

    if (launcher.getDeviceProfile().isVerticalBarLayout()) {
        list.add(new LandscapeStatesTouchController(launcher));
        list.add(new LandscapeEdgeSwipeController(launcher));
    } else {
        boolean allowDragToOverview = SysUINavigationMode.INSTANCE.get(launcher)
                .getMode().hasGestures;
        list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
    }
    if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
            && !launcher.getDeviceProfile().isMultiWindowMode
            && !launcher.getDeviceProfile().isVerticalBarLayout()) {
        list.add(new StatusBarTouchController(launcher));
    }
    return list.toArray(new TouchController[list.size()]);
}

It can be seen here that we actually have a portaitstatestouchcontroller, so we consider entering its processing. However, the portaitstatestouchcontroller itself does not, but the AbstractStateChangeTouchController does:
packages/apps/Trebuchet/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java

    @Override
    public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mNoIntercept = !canInterceptTouch(ev);
            if (mNoIntercept) {
                return false;
            }

            // Now figure out which direction scroll events the controller will start
            // calling the callbacks.
            final int directionsToDetectScroll;
            boolean ignoreSlopWhenSettling = false;

            if (mCurrentAnimation != null) {
                directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
                ignoreSlopWhenSettling = true;
            } else {
                directionsToDetectScroll = getSwipeDirection();
                if (directionsToDetectScroll == 0) {
                    mNoIntercept = true;
                    return false;
                }
            }
            mDetector.setDetectableScrollConditions(
                    directionsToDetectScroll, ignoreSlopWhenSettling);
        }

        if (mNoIntercept) {
            return false;
        }

        onControllerTouchEvent(ev);//Here processing is the key
        return mDetector.isDraggingOrSettling();//Returns whether it is in the DRAGGING state
    }
    
       @Override
    public final boolean onControllerTouchEvent(MotionEvent ev) {
        return mDetector.onTouchEvent(ev);//onTouchEvent of mDetector called
    }

The final processing will call mDetector Ontouchevent, where mDetector is BaseSwipeDetector:
com/android/launcher3/touch/BaseSwipeDetector.java

The BaseSwipeDetector is the key point of the real touch event logic

   public boolean onTouchEvent(MotionEvent ev) {
        int actionMasked = ev.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
            mVelocityTracker.clear();
        }
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN://At first, down only did some initialization related work and recorded coordinates
                mActivePointerId = ev.getPointerId(0);
                mDownPos.set(ev.getX(), ev.getY());
                mLastPos.set(mDownPos);
                mLastDisplacement.set(0, 0);
                mDisplacement.set(0, 0);

                if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
                    setState(ScrollState.DRAGGING);
                }
                break;
           //Omitted part
            case MotionEvent.ACTION_MOVE://Start moving
                int pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex == INVALID_POINTER_ID) {
                    break;
                }
                mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
                        ev.getY(pointerIndex) - mDownPos.y);//Record moving point
                if (mIsRtl) {
                    mDisplacement.x = -mDisplacement.x;
                }

                // handle state and listener calls.
                if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {//Start to see if it is possible to trigger the driving
                    setState(ScrollState.DRAGGING);
                }
                if (mState == ScrollState.DRAGGING) {
                    reportDragging(ev);//If you are already in DRAGGING and start sliding, you need to call reportDragging
                }
                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // These are synthetic events and there is no need to update internal values.
                if (mState == ScrollState.DRAGGING) {
                    setState(ScrollState.SETTLING);//When lifted, it becomes the completed state
                }
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                break;
            default:
                break;
        }
        return true;
    }

Here is the key method setState:

  private void setState(ScrollState newState) {
        if (newState == ScrollState.DRAGGING) {//Set to DRAGGING
            initializeDragging();
            if (mState == ScrollState.IDLE) {
                reportDragStart(false /* recatch */);//Call reportDragStart
            } else if (mState == ScrollState.SETTLING) {
                reportDragStart(true /* recatch */);
            }
        }
        if (newState == ScrollState.SETTLING) {
            reportDragEnd();//Drag end
        }

        mState = newState;
    }
    private void reportDragStart(boolean recatch) {
        reportDragStartInternal(recatch);
    }

reportDragStartInternal is actually a method implemented by the subclass SingleAxisSwipeDetector:
com/android/launcher3/touch/SingleAxisSwipeDetector.java

@Override
protected void reportDragStartInternal(boolean recatch) {
    mListener.onDragStart(!recatch);
}

There's a transfer here
com/android/launcher3/touch/AbstractStateChangeTouchController.java

@Override
    public void onDragStart(boolean start) {
        mStartState = mLauncher.getStateManager().getState();
        mIsLogContainerSet = false;
        if (mCurrentAnimation == null) {
            mFromState = mStartState;
            mToState = null;
            cancelAnimationControllers();
            reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());//Start to initialize the animation, which is complex, mainly to set the Workspace animation
            mDisplacementShift = 0;
        } else {
            mCurrentAnimation.pause();
            mStartProgress = mCurrentAnimation.getProgressFraction();

            mAtomicAnimAutoPlayInfo = null;
            if (mAtomicComponentsController != null) {
                mAtomicComponentsController.pause();
            }
        }
        mCanBlockFling = mFromState == NORMAL;
        mFlingBlockCheck.unblockFling();
    }

The above analysis has completed the DragStart situation. What about the finger sliding process?
Then go back to reportDragging in onTouchEvent of BaseSwipeDetector

 if (mState == ScrollState.DRAGGING) {
                    reportDragging(ev);
                    //If you are already in DRAGGING and start sliding, you need to call reportDragging
                }

Look here

private void reportDragging(MotionEvent event) {
    if (mDisplacement != mLastDisplacement) {
        if (DBG) {
            Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
        }

        mLastDisplacement.set(mDisplacement);
        sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
                mDisplacement.y - mSubtractDisplacement.y);//Transfer the specific sliding distance, and finally convert it to progress
        reportDraggingInternal(sTempPoint, event);
    }
}

Here we go back to the reportDraggingInternal(sTempPoint, event) method, which calls the SingleAxisSwipeDetector
reportDraggingInternal:

 @Override
    protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
        mListener.onDrag(mDir.extractDirection(displacement), event);
    }

Here we call com/android/launcher3/touch/abstractstatechangetouchcontroller java

  @Override
    public boolean onDrag(float displacement, MotionEvent ev) {
        if (!mIsLogContainerSet) {
            if (mStartState == ALL_APPS) {
                mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
            } else if (mStartState == NORMAL) {
                mStartContainerType = getLogContainerTypeForNormalState(ev);
            } else if (mStartState == OVERVIEW) {
                mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
            }
            mIsLogContainerSet = true;
        }
        return onDrag(displacement);//Finally, the corresponding overloaded method onDrag will be called
    }
     @Override
    public boolean onDrag(float displacement) {
        float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
        float progress = deltaProgress + mStartProgress;
        updateProgress(progress);//Here is to update the progress
        boolean isDragTowardPositive = mSwipeDirection.isPositive(
                displacement - mDisplacementShift);
        if (progress <= 0) {
            if (reinitCurrentAnimation(false, isDragTowardPositive)) {
                mDisplacementShift = displacement;
                if (mCanBlockFling) {
                    mFlingBlockCheck.blockFling();
                }
            }
        } else if (progress >= 1) {
            if (reinitCurrentAnimation(true, isDragTowardPositive)) {
                mDisplacementShift = displacement;
                if (mCanBlockFling) {
                    mFlingBlockCheck.blockFling();
                }
            }
        } else {
            mFlingBlockCheck.onEvent();
        }

        return true;
    }
    protected void updateProgress(float fraction) {
        mCurrentAnimation.setPlayFraction(fraction);//In fact, the setPlayFraction of mCurrentAnimation animation is set here
        if (mAtomicComponentsController != null) {
            // Make sure we don't divide by 0, and have at least a small runway.
            float start = Math.min(mAtomicComponentsStartProgress, 0.9f);
            mAtomicComponentsController.setPlayFraction((fraction - start) / (1 - start));
        }
        maybeUpdateAtomicAnim(mFromState, mToState, fraction);
    }

Mcurrentanimation setPlayFraction will finally call the setPlayFraction method of AnimatorPlaybackController
com/android/launcher3/anim/AnimatorPlaybackController.java

@Override
public void setPlayFraction(float fraction) {
    mCurrentFraction = fraction;
    // Let the animator report the progress but don't apply the progress to child
    // animations if it has been cancelled.
    if (mTargetCancelled) {
        return;
    }
    long playPos = clampDuration(fraction);
    for (ValueAnimator anim : mChildAnimations) {
        anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
    }
}

The mChildAnimations animation set is set by reinitCurrentAnimation in the previous onDragStart. The set contains several animations, including the mobile animation of the workspace. It will be traversed here, and then the animation time can be set to complete the animation of the workspace..
Does it feel good? If we write code, are we sure that we can directly call the ui to set some properties of the View at this time

2. Start the animation to enter multi task after the threshold value of progress is reached
Before analyzing updateProgress, there was also a maybeUpdateAtomicAnim method

private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
            float progress) {
        if (!goingBetweenNormalAndOverview(fromState, toState)) {
            return;
        }
        float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
                : 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
        boolean passedThreshold = progress >= threshold;//Judge whether the progress has reached the threshold progress requirements
        if (passedThreshold != mPassedOverviewAtomicThreshold) {//Arrival progress
            LauncherState atomicFromState = passedThreshold ? fromState: toState;
            LauncherState atomicToState = passedThreshold ? toState : fromState;
            mPassedOverviewAtomicThreshold = passedThreshold;
            if (mAtomicAnim != null) {
                mAtomicAnim.cancel();
            }
            mAtomicAnim = createAtomicAnimForState(atomicFromState, atomicToState, ATOMIC_DURATION);//Create an atomic animation from NORMAL -- overviews
           //Omitted part
            mAtomicAnim.start();//Turn on atomic animation
            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
        }
    }

Tags: Java Android animation Framework

Posted by moise on Thu, 02 Jun 2022 23:37:53 +0530