Merge "Improve PIP enter transition w/ gesture nav (3/N)"
This commit is contained in:
commit
fd2dac816b
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue