Merge "Improve PIP enter transition w/ gesture nav (3/N)"

This commit is contained in:
Hongwei Wang 2020-10-23 16:09:31 +00:00 committed by Android (Google) Code Review
commit fd2dac816b
5 changed files with 290 additions and 17 deletions

View File

@ -45,6 +45,7 @@ import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHO
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@ -92,8 +93,10 @@ import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
@ -229,6 +232,10 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
private static final long SWIPE_PIP_TO_HOME_DURATION = 425;
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
@ -1046,25 +1053,44 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
if (mGestureState.getEndTarget() == HOME) {
mTaskViewSimulator.setDrawsBelowRecents(false);
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
: null;
HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
&& runningTaskTarget != null
&& runningTaskTarget.pictureInPictureParams != null
&& runningTaskTarget.pictureInPictureParams.isAutoEnterEnabled()
&& runningTaskTarget.pictureInPictureParams.getSourceRectHint() != null;
if (mIsSwipingPipToHome) {
mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
homeAnimFactory, runningTaskTarget);
mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
mSwipePipToHomeAnimator.setInterpolator(interpolator);
mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
mSwipePipToHomeAnimator.start();
mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
} else {
mSwipePipToHomeAnimator = null;
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsAnimationController == null) {
// If the recents animation is interrupted, we still end the running
// animation (not canceled) so this is still called. In that case, we can
// skip doing any future work here for the current gesture.
// animation (not canceled) so this is still called. In that case,
// we can skip doing any future work here for the current gesture.
return;
}
// Finalize the state and notify of the change
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
windowAnim.start(mContext, velocityPxPerMs);
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
}
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
mLauncherTransitionController = null;
} else {
ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
@ -1108,6 +1134,46 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
}
}
private SwipePipToHomeAnimator getSwipePipToHomeAnimator(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
.startSwipePipToHome(taskInfo.topActivity, taskInfo.topActivityInfo,
runningTaskTarget.pictureInPictureParams,
orientationState.getRecentsActivityRotation(),
mDp.hotseatBarSizePx);
final SwipePipToHomeAnimator swipePipToHomeAnimator = new SwipePipToHomeAnimator(
runningTaskTarget.taskId,
taskInfo.topActivity,
runningTaskTarget.leash.getSurfaceControl(),
runningTaskTarget.pictureInPictureParams.getSourceRectHint(),
taskInfo.configuration.windowConfiguration.getBounds(),
destinationBounds);
swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// Ensure Launcher ends in NORMAL state, we intentionally skip other callbacks
// since they are not relevant in this swipe-pip-to-home case.
homeAnimFactory.createActivityAnimationToHome().dispatchOnStart();
}
@Override
public void onAnimationEnd(Animator animation) {
if (mRecentsAnimationController == null) {
// If the recents animation is interrupted, we still end the running
// animation (not canceled) so this is still called. In that case, we can
// skip doing any future work here for the current gesture.
return;
}
// Finalize the state and notify of the change
mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
}
});
return swipePipToHomeAnimator;
}
private void computeRecentsScrollIfInvisible() {
if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
// Views typically don't compute scroll when invisible as an optimization,
@ -1356,6 +1422,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
maybeFinishSwipePipToHome();
finishRecentsControllerToHome(
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
@ -1363,6 +1430,22 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends
doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
}
/**
* Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
* if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
*/
private void maybeFinishSwipePipToHome() {
if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds());
mRecentsAnimationController.setFinishTaskBounds(
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getDestinationBounds());
mIsSwipingPipToHome = false;
}
}
protected abstract void finishRecentsControllerToHome(Runnable callback);
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {

View File

@ -56,6 +56,7 @@ public class LauncherSwipeHandlerV2 extends
final TaskView runningTaskView = mRecentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null
&& !mIsSwipingPipToHome
&& runningTaskView.getTask().key.getComponent() != null) {
workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
@ -140,5 +141,10 @@ public class LauncherSwipeHandlerV2 extends
new StaggeredWorkspaceAnim(mActivity, velocity,
true /* animateOverviewScrim */).start();
}
@Override
public boolean supportSwipePipToHome() {
return true;
}
}
}

View File

@ -18,6 +18,8 @@ package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
@ -155,6 +157,18 @@ public class RecentsAnimationController {
});
}
/**
* Sets the final bounds on a Task. This is used by Launcher to notify the system that
* animating Activity to PiP has completed and the associated task surface should be updated
* accordingly. This should be called before `finish`
* @param taskId for which the leash should be updated
* @param destinationBounds bounds of the final PiP window
*/
public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
UI_HELPER_EXECUTOR.execute(
() -> mController.setFinishTaskBounds(taskId, destinationBounds));
}
/**
* Enables the input consumer to start intercepting touches in the app window.
*/

View File

@ -159,6 +159,14 @@ public abstract class SwipeUpAnimationLogic {
public void update(RectF currentRect, float progress, float radius) { }
public void onCancel() { }
/**
* @return {@code true} if this factory supports animating an Activity to PiP window on
* swiping up to home.
*/
public boolean supportSwipePipToHome() {
return false;
}
}
/**

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2020 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 com.android.quickstep.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
/**
* An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
* swiping up (in gesture navigation mode). Note that this class is derived from
* {@link com.android.wm.shell.pip.PipAnimationController.PipTransitionAnimator}.
*
* TODO: consider sharing this class including the animator and leash operations between
* Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
* PiP window, which would ideally be on SysUI side as well.
*/
public class SwipePipToHomeAnimator extends ValueAnimator implements
ValueAnimator.AnimatorUpdateListener {
private final int mTaskId;
private final ComponentName mComponentName;
private final SurfaceControl mLeash;
private final Rect mStartBounds = new Rect();
private final Rect mDestinationBounds = new Rect();
private final SurfaceTransactionHelper mSurfaceTransactionHelper;
/** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
private final Rect mSourceHintRectInsets = new Rect();
private final Rect mSourceInsets = new Rect();
/**
* Flag to avoid the double-end problem since the leash would have been released
* after the first end call and any further operations upon it would lead to NPE.
*/
private boolean mHasAnimationEnded;
public SwipePipToHomeAnimator(int taskId,
@NonNull ComponentName componentName,
@NonNull SurfaceControl leash,
@NonNull Rect sourceRectHint,
@NonNull Rect startBounds,
@NonNull Rect destinationBounds) {
mTaskId = taskId;
mComponentName = componentName;
mLeash = leash;
mStartBounds.set(startBounds);
mDestinationBounds.set(destinationBounds);
mSurfaceTransactionHelper = new SurfaceTransactionHelper();
mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
sourceRectHint.top - startBounds.top,
startBounds.right - sourceRectHint.right,
startBounds.bottom - sourceRectHint.bottom);
addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
SwipePipToHomeAnimator.this.onAnimationEnd();
}
});
addUpdateListener(this);
}
@Override
public void onAnimationUpdate(ValueAnimator animator) {
if (mHasAnimationEnded) return;
final float fraction = animator.getAnimatedFraction();
final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds, mDestinationBounds);
final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
mSourceHintRectInsets);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
tx.setCornerRadius(mLeash, 0).apply();
}
public int getTaskId() {
return mTaskId;
}
public ComponentName getComponentName() {
return mComponentName;
}
public Rect getDestinationBounds() {
return mDestinationBounds;
}
private void onAnimationEnd() {
if (mHasAnimationEnded) return;
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
mSurfaceTransactionHelper.resetScale(tx, mLeash, mDestinationBounds);
mSurfaceTransactionHelper.crop(tx, mLeash, mDestinationBounds);
tx.setCornerRadius(mLeash, 0).apply();
mHasAnimationEnded = true;
}
/**
* Slim version of {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}
*/
private static final class SurfaceTransactionHelper {
private final Matrix mTmpTransform = new Matrix();
private final float[] mTmpFloat9 = new float[9];
private final RectF mTmpSourceRectF = new RectF();
private final Rect mTmpDestinationRect = new Rect();
private void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds, Rect insets) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
// Scale by the shortest edge and offset such that the top/left of the scaled inset
// source rect aligns with the top/left of the destination bounds
final float scale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceBounds.width()
: (float) destinationBounds.height() / sourceBounds.height();
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
mTmpTransform.setScale(scale, scale);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
.setWindowCrop(leash, mTmpDestinationRect)
.setPosition(leash, left, top);
}
private void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect destinationBounds) {
tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
.setPosition(leash, destinationBounds.left, destinationBounds.top);
}
private void crop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect destinationBounds) {
tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
.setPosition(leash, destinationBounds.left, destinationBounds.top);
}
}
}