Merge "Apply fixed rotation animation for pip to fullscreen" into sc-dev am: ca7f10993f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13425308 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I64eee759eff3ea5147d45dc431a45cfbb7470df7
This commit is contained in:
@@ -5685,6 +5685,13 @@ public final class ActivityThread extends ClientTransactionHandler {
|
||||
final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
|
||||
amOverrideConfig, contextThemeWrapperOverrideConfig);
|
||||
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
|
||||
final Resources res = activity.getResources();
|
||||
if (res.hasOverrideDisplayAdjustments()) {
|
||||
// If fixed rotation is applied while the activity is visible (e.g. PiP), the rotated
|
||||
// configuration of activity may be sent later than the adjustments. In this case, the
|
||||
// adjustments need to be updated for the consistency of display info.
|
||||
res.getDisplayAdjustments().getConfiguration().updateFrom(finalOverrideConfig);
|
||||
}
|
||||
|
||||
activity.mConfigChangeFlags = 0;
|
||||
activity.mCurrentConfig = new Configuration(newConfig);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.wm.shell.pip;
|
||||
|
||||
import static android.view.Surface.ROTATION_270;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import android.animation.AnimationHandler;
|
||||
import android.animation.Animator;
|
||||
import android.animation.RectEvaluator;
|
||||
@@ -24,11 +27,13 @@ import android.annotation.IntDef;
|
||||
import android.app.TaskInfo;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
|
||||
import com.android.wm.shell.animation.Interpolators;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -132,15 +137,21 @@ public class PipAnimationController {
|
||||
* leash bounds before transformation/any animation. This is so when we try to construct
|
||||
* the different transformation matrices for the animation, we are constructing this based off
|
||||
* the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
|
||||
*
|
||||
* If non-zero {@param rotationDelta} is given, it means that the display will be rotated by
|
||||
* leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
|
||||
* rotation change.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
|
||||
Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
|
||||
@PipAnimationController.TransitionDirection int direction, float startingAngle) {
|
||||
@PipAnimationController.TransitionDirection int direction, float startingAngle,
|
||||
@Surface.Rotation int rotationDelta) {
|
||||
if (mCurrentAnimator == null) {
|
||||
mCurrentAnimator = setupPipTransitionAnimator(
|
||||
PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
|
||||
endBounds, sourceHintRect, direction, 0 /* startingAngle */));
|
||||
endBounds, sourceHintRect, direction, 0 /* startingAngle */,
|
||||
rotationDelta));
|
||||
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
|
||||
&& mCurrentAnimator.isRunning()) {
|
||||
// If we are still animating the fade into pip, then just move the surface and ensure
|
||||
@@ -156,7 +167,7 @@ public class PipAnimationController {
|
||||
mCurrentAnimator.cancel();
|
||||
mCurrentAnimator = setupPipTransitionAnimator(
|
||||
PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
|
||||
endBounds, sourceHintRect, direction, startingAngle));
|
||||
endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
|
||||
}
|
||||
return mCurrentAnimator;
|
||||
}
|
||||
@@ -410,7 +421,8 @@ public class PipAnimationController {
|
||||
|
||||
static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
|
||||
Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
|
||||
@PipAnimationController.TransitionDirection int direction, float startingAngle) {
|
||||
@PipAnimationController.TransitionDirection int direction, float startingAngle,
|
||||
@Surface.Rotation int rotationDelta) {
|
||||
// Just for simplicity we'll interpolate between the source rect hint insets and empty
|
||||
// insets to calculate the window crop
|
||||
final Rect initialSourceValue;
|
||||
@@ -431,6 +443,16 @@ public class PipAnimationController {
|
||||
}
|
||||
final Rect sourceInsets = new Rect(0, 0, 0, 0);
|
||||
|
||||
final Rect rotatedEndRect;
|
||||
if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
|
||||
// Rotate the end bounds according to the rotation delta because the display will
|
||||
// be rotated to the same orientation.
|
||||
rotatedEndRect = new Rect(endValue);
|
||||
DisplayLayout.rotateBounds(rotatedEndRect, endValue, rotationDelta);
|
||||
} else {
|
||||
rotatedEndRect = null;
|
||||
}
|
||||
|
||||
// construct new Rect instances in case they are recycled
|
||||
return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
|
||||
endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
|
||||
@@ -444,6 +466,12 @@ public class PipAnimationController {
|
||||
final Rect base = getBaseValue();
|
||||
final Rect start = getStartValue();
|
||||
final Rect end = getEndValue();
|
||||
if (rotatedEndRect != null) {
|
||||
// Animate the bounds in a different orientation. It only happens when
|
||||
// leaving PiP to fullscreen.
|
||||
applyRotation(tx, leash, fraction, start, end, rotatedEndRect);
|
||||
return;
|
||||
}
|
||||
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
|
||||
float angle = (1.0f - fraction) * startingAngle;
|
||||
setCurrentValue(bounds);
|
||||
@@ -469,6 +497,25 @@ public class PipAnimationController {
|
||||
tx.apply();
|
||||
}
|
||||
|
||||
private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
|
||||
float fraction, Rect start, Rect end, Rect rotatedEndRect) {
|
||||
final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect);
|
||||
setCurrentValue(bounds);
|
||||
final float degree, x, y;
|
||||
if (rotationDelta == ROTATION_90) {
|
||||
degree = 90 * fraction;
|
||||
x = fraction * (end.right - start.left) + start.left;
|
||||
y = fraction * (end.top - start.top) + start.top;
|
||||
} else {
|
||||
degree = -90 * fraction;
|
||||
x = fraction * (end.left - start.left) + start.left;
|
||||
y = fraction * (end.bottom - start.top) + start.top;
|
||||
}
|
||||
getSurfaceTransactionHelper().rotateAndScaleWithCrop(tx, leash, bounds,
|
||||
rotatedEndRect, degree, x, y);
|
||||
tx.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
|
||||
getSurfaceTransactionHelper()
|
||||
|
||||
@@ -126,6 +126,32 @@ public class PipSurfaceTransactionHelper {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Operates the rotation according to the given degrees and scale (setMatrix) according to the
|
||||
* source bounds and rotated destination bounds. The crop will be the unscaled source bounds.
|
||||
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
|
||||
*/
|
||||
public PipSurfaceTransactionHelper rotateAndScaleWithCrop(SurfaceControl.Transaction tx,
|
||||
SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees,
|
||||
float positionX, float positionY) {
|
||||
mTmpDestinationRect.set(sourceBounds);
|
||||
final int dw = destinationBounds.width();
|
||||
final int dh = destinationBounds.height();
|
||||
// Scale by the short side so there won't be empty area if the aspect ratio of source and
|
||||
// destination are different.
|
||||
final float scale = dw <= dh
|
||||
? (float) sourceBounds.width() / dw
|
||||
: (float) sourceBounds.height() / dh;
|
||||
// Inverse scale for crop to fit in screen coordinates.
|
||||
mTmpDestinationRect.scale(1 / scale);
|
||||
mTmpTransform.setRotate(degrees);
|
||||
mTmpTransform.postScale(scale, scale);
|
||||
mTmpTransform.postTranslate(positionX, positionY);
|
||||
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
|
||||
.setWindowCrop(leash, mTmpDestinationRect.width(), mTmpDestinationRect.height());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the scale (setMatrix) on a given transaction and leash if there's any
|
||||
*
|
||||
|
||||
@@ -48,13 +48,12 @@ import android.app.TaskInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.TaskOrganizer;
|
||||
import android.window.WindowContainerToken;
|
||||
@@ -72,8 +71,6 @@ import com.android.wm.shell.pip.phone.PipMotionHelper;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -134,7 +131,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
private final PipUiEventLogger mPipUiEventLoggerLogger;
|
||||
private final int mEnterExitAnimationDuration;
|
||||
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
|
||||
private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
|
||||
private final Optional<LegacySplitScreen> mSplitScreenOptional;
|
||||
protected final ShellTaskOrganizer mTaskOrganizer;
|
||||
protected final ShellExecutor mMainExecutor;
|
||||
@@ -191,6 +187,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
*/
|
||||
private boolean mWaitForFixedRotation;
|
||||
|
||||
/**
|
||||
* The rotation that the display will apply after expanding PiP to fullscreen. This is only
|
||||
* meaningful if {@link #mWaitForFixedRotation} is true.
|
||||
*/
|
||||
private @Surface.Rotation int mNextRotation;
|
||||
|
||||
/**
|
||||
* If set to {@code true}, no entering PiP transition would be kicked off and most likely
|
||||
* it's due to the fact that Launcher is handling the transition directly when swiping
|
||||
@@ -313,61 +315,40 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
return;
|
||||
}
|
||||
|
||||
final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
|
||||
if (initialConfig == null) {
|
||||
Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken);
|
||||
return;
|
||||
}
|
||||
mPipUiEventLoggerLogger.log(
|
||||
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
|
||||
final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
|
||||
!= mPipBoundsState.getDisplayLayout().rotation();
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
final Rect destinationBounds = initialConfig.windowConfiguration.getBounds();
|
||||
final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
|
||||
final int direction = syncWithSplitScreenBounds(destinationBounds)
|
||||
? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
|
||||
: TRANSITION_DIRECTION_LEAVE_PIP;
|
||||
if (orientationDiffers) {
|
||||
mState = State.EXITING_PIP;
|
||||
// Send started callback though animation is ignored.
|
||||
sendOnPipTransitionStarted(direction);
|
||||
// Don't bother doing an animation if the display rotation differs or if it's in
|
||||
// a non-supported windowing mode
|
||||
applyWindowingModeChangeOnExit(wct, direction);
|
||||
mTaskOrganizer.applyTransaction(wct);
|
||||
// Send finished callback though animation is ignored.
|
||||
sendOnPipTransitionFinished(direction);
|
||||
} else {
|
||||
final SurfaceControl.Transaction tx =
|
||||
mSurfaceControlTransactionFactory.getTransaction();
|
||||
mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
|
||||
mPipBoundsState.getBounds());
|
||||
tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
|
||||
// We set to fullscreen here for now, but later it will be set to UNDEFINED for
|
||||
// the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
|
||||
wct.setActivityWindowingMode(mToken,
|
||||
direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
|
||||
? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
|
||||
: WINDOWING_MODE_FULLSCREEN);
|
||||
wct.setBounds(mToken, destinationBounds);
|
||||
wct.setBoundsChangeTransaction(mToken, tx);
|
||||
mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
|
||||
@Override
|
||||
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
|
||||
mMainExecutor.execute(() -> {
|
||||
t.apply();
|
||||
// Make sure to grab the latest source hint rect as it could have been
|
||||
// updated right after applying the windowing mode change.
|
||||
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
|
||||
mPictureInPictureParams, destinationBounds);
|
||||
scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
|
||||
0 /* startingAngle */, sourceHintRect, direction,
|
||||
animationDurationMs, null /* updateBoundsCallback */);
|
||||
mState = State.EXITING_PIP;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
|
||||
mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mPipBoundsState.getBounds());
|
||||
tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
|
||||
// We set to fullscreen here for now, but later it will be set to UNDEFINED for
|
||||
// the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
|
||||
wct.setActivityWindowingMode(mToken,
|
||||
direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
|
||||
? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
|
||||
: WINDOWING_MODE_FULLSCREEN);
|
||||
wct.setBounds(mToken, destinationBounds);
|
||||
wct.setBoundsChangeTransaction(mToken, tx);
|
||||
mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
|
||||
@Override
|
||||
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
|
||||
mMainExecutor.execute(() -> {
|
||||
t.apply();
|
||||
// Make sure to grab the latest source hint rect as it could have been
|
||||
// updated right after applying the windowing mode change.
|
||||
final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
|
||||
mPictureInPictureParams, destinationBounds);
|
||||
scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
|
||||
0 /* startingAngle */, sourceHintRect, direction,
|
||||
animationDurationMs, null /* updateBoundsCallback */);
|
||||
mState = State.EXITING_PIP;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
|
||||
@@ -399,7 +380,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
.setPipAnimationCallback(mPipAnimationCallback)
|
||||
.setDuration(mEnterExitAnimationDuration)
|
||||
.start();
|
||||
mInitialState.remove(mToken.asBinder());
|
||||
mState = State.EXITING_PIP;
|
||||
}
|
||||
|
||||
@@ -424,7 +404,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
mToken = mTaskInfo.token;
|
||||
mState = State.TASK_APPEARED;
|
||||
mLeash = leash;
|
||||
mInitialState.put(mToken.asBinder(), new Configuration(mTaskInfo.configuration));
|
||||
mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
|
||||
setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
|
||||
mTaskInfo.topActivityInfo);
|
||||
@@ -606,6 +585,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
|
||||
@Override
|
||||
public void onFixedRotationStarted(int displayId, int newRotation) {
|
||||
mNextRotation = newRotation;
|
||||
mWaitForFixedRotation = true;
|
||||
}
|
||||
|
||||
@@ -645,7 +625,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
mPipAnimationController.getCurrentAnimator();
|
||||
if (animator == null || !animator.isRunning()
|
||||
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
|
||||
if (mState.isInPip() && fromRotation) {
|
||||
if (mState.isInPip() && fromRotation && !mWaitForFixedRotation) {
|
||||
// Update bounds state to final destination first. It's important to do this
|
||||
// before finishing & cancelling the transition animation so that the MotionHelper
|
||||
// bounds are synchronized to the destination bounds when the animation ends.
|
||||
@@ -1052,11 +1032,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
Log.w(TAG, "Abort animation, invalid leash");
|
||||
return;
|
||||
}
|
||||
final int rotationDelta = mWaitForFixedRotation
|
||||
? ((mNextRotation - mPipBoundsState.getDisplayLayout().rotation()) + 4) % 4
|
||||
: Surface.ROTATION_0;
|
||||
Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
|
||||
? mPipBoundsState.getBounds() : currentBounds;
|
||||
mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
|
||||
sourceHintRect, direction, startingAngle)
|
||||
sourceHintRect, direction, startingAngle, rotationDelta)
|
||||
.setTransitionDirection(direction)
|
||||
.setPipAnimationCallback(mPipAnimationCallback)
|
||||
.setDuration(durationMs)
|
||||
@@ -1101,11 +1084,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
|
||||
pw.println(innerPrefix + "mState=" + mState);
|
||||
pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
|
||||
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
|
||||
pw.println(innerPrefix + "mInitialState:");
|
||||
for (Map.Entry<IBinder, Configuration> e : mInitialState.entrySet()) {
|
||||
pw.println(innerPrefix + " binder=" + e.getKey()
|
||||
+ " winConfig=" + e.getValue().windowConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.app.TaskInfo;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.TransitionInfo;
|
||||
import android.window.TransitionRequestInfo;
|
||||
@@ -112,7 +113,7 @@ public class PipTransition extends PipTransitionController {
|
||||
taskInfo.pictureInPictureParams, currentBounds);
|
||||
animator = mPipAnimationController.getAnimator(taskInfo, leash,
|
||||
currentBounds, currentBounds, destinationBounds, sourceHintRect,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, Surface.ROTATION_0);
|
||||
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
|
||||
t.setAlpha(leash, 0f);
|
||||
t.apply();
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.wm.shell.pip;
|
||||
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
import static android.view.Surface.ROTATION_90;
|
||||
|
||||
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
|
||||
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
|
||||
|
||||
@@ -34,6 +37,7 @@ import android.view.SurfaceControl;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -84,7 +88,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
public void getAnimator_withBounds_returnBoundsAnimator() {
|
||||
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
|
||||
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
|
||||
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
|
||||
@@ -98,13 +102,13 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
final Rect endValue2 = new Rect(200, 200, 300, 300);
|
||||
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
|
||||
oldAnimator.start();
|
||||
|
||||
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
|
||||
assertEquals("getAnimator with same type returns same animator",
|
||||
oldAnimator, newAnimator);
|
||||
@@ -127,6 +131,21 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
animator.getTransitionDirection(), TRANSITION_DIRECTION_LEAVE_PIP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pipTransitionAnimator_rotatedEndValue() {
|
||||
final Rect startBounds = new Rect(200, 700, 400, 800);
|
||||
final Rect endBounds = new Rect(0, 0, 500, 1000);
|
||||
final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_90);
|
||||
// Apply fraction 1 to compute the end value.
|
||||
animator.applySurfaceControlTransaction(mLeash, new DummySurfaceControlTx(), 1);
|
||||
final Rect rotatedEndBounds = new Rect(endBounds);
|
||||
DisplayLayout.rotateBounds(rotatedEndBounds, endBounds, ROTATION_90);
|
||||
|
||||
assertEquals("Expect 90 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void pipTransitionAnimator_updateEndValue() {
|
||||
@@ -136,7 +155,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
final Rect endValue2 = new Rect(200, 200, 300, 300);
|
||||
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
|
||||
animator.updateEndValue(endValue2);
|
||||
|
||||
@@ -150,7 +169,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
|
||||
final Rect endValue = new Rect(100, 100, 200, 200);
|
||||
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
|
||||
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
|
||||
TRANSITION_DIRECTION_TO_PIP, 0);
|
||||
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
|
||||
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
|
||||
|
||||
animator.setPipAnimationCallback(mPipAnimationCallback);
|
||||
|
||||
@@ -7005,7 +7005,32 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onConfigurationChanged(newParentConfig);
|
||||
|
||||
final DisplayContent display = mDisplayContent;
|
||||
if (inPinnedWindowingMode() && attachedToProcess() && display != null) {
|
||||
// If the PIP activity is changing to fullscreen with display orientation change, the
|
||||
// fixed rotation will take effect that requires to send fixed rotation adjustments
|
||||
// before the process configuration (if the process is a configuration listener of the
|
||||
// activity). So when performing process configuration on client side, it can apply
|
||||
// the adjustments (see WindowToken#onFixedRotationStatePrepared).
|
||||
try {
|
||||
app.pauseConfigurationDispatch();
|
||||
super.onConfigurationChanged(newParentConfig);
|
||||
if (mVisibleRequested && !inMultiWindowMode()) {
|
||||
final int rotation = display.rotationForActivityInDifferentOrientation(this);
|
||||
if (rotation != ROTATION_UNDEFINED) {
|
||||
app.resumeConfigurationDispatch();
|
||||
display.setFixedRotationLaunchingApp(this, rotation);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (app.resumeConfigurationDispatch()) {
|
||||
app.dispatchConfiguration(app.getConfiguration());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.onConfigurationChanged(newParentConfig);
|
||||
}
|
||||
|
||||
// Configuration's equality doesn't consider seq so if only seq number changes in resolved
|
||||
// override configuration. Therefore ConfigurationContainer doesn't change merged override
|
||||
@@ -7014,7 +7039,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
onMergedOverrideConfigurationChanged();
|
||||
}
|
||||
|
||||
final DisplayContent display = mDisplayContent;
|
||||
if (display == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1603,7 +1603,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
&& mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isFixedRotationLaunchingApp(ActivityRecord r) {
|
||||
return mFixedRotationLaunchingApp == r;
|
||||
}
|
||||
|
||||
@@ -809,6 +809,16 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
// false.
|
||||
private boolean mDeferTaskAppear;
|
||||
|
||||
/**
|
||||
* Forces this task to be unorganized. Currently it is used for deferring the control of
|
||||
* organizer when windowing mode is changing from PiP to fullscreen with orientation change.
|
||||
* It is true only during Task#setWindowingMode ~ DisplayRotation#continueRotation.
|
||||
*
|
||||
* TODO(b/179235349): Remove this field by making surface operations from task organizer sync
|
||||
* with display rotation.
|
||||
*/
|
||||
private boolean mForceNotOrganized;
|
||||
|
||||
/**
|
||||
* This task was created by the task organizer which has the following implementations.
|
||||
* <ul>
|
||||
@@ -2246,6 +2256,21 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
|
||||
if (pipChanging) {
|
||||
mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(true);
|
||||
// If the top activity is using fixed rotation, it should be changing from PiP to
|
||||
// fullscreen with display orientation change. Do not notify fullscreen task organizer
|
||||
// because the restoration of task surface and the transformation of activity surface
|
||||
// need to be done synchronously.
|
||||
final ActivityRecord r = topRunningActivity();
|
||||
if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
|
||||
mForceNotOrganized = true;
|
||||
}
|
||||
} else if (mForceNotOrganized) {
|
||||
// If the display orientation change is done, let the corresponding task organizer take
|
||||
// back the control of this task.
|
||||
final ActivityRecord r = topRunningActivity();
|
||||
if (r == null || !mDisplayContent.isFixedRotationLaunchingApp(r)) {
|
||||
mForceNotOrganized = false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// We have 2 reasons why we need to report orientation change here.
|
||||
@@ -2835,6 +2860,9 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
|
||||
windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
|
||||
}
|
||||
// Commit the resolved windowing mode so the canSpecifyOrientation won't get the old
|
||||
// mode that may cause the bounds to be miscalculated, e.g. letterboxed.
|
||||
getConfiguration().windowConfiguration.setWindowingMode(windowingMode);
|
||||
Rect outOverrideBounds =
|
||||
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
|
||||
|
||||
@@ -4533,6 +4561,9 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
pw.print(" mSupportsPictureInPicture="); pw.print(mSupportsPictureInPicture);
|
||||
pw.print(" isResizeable="); pw.println(isResizeable());
|
||||
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
|
||||
if (mForceNotOrganized) {
|
||||
pw.print(prefix); pw.println("mForceNotOrganized=true");
|
||||
}
|
||||
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
|
||||
}
|
||||
|
||||
@@ -4977,6 +5008,9 @@ class Task extends WindowContainer<WindowContainer> {
|
||||
}
|
||||
|
||||
private boolean canBeOrganized() {
|
||||
if (mForceNotOrganized) {
|
||||
return false;
|
||||
}
|
||||
// All root tasks can be organized
|
||||
if (isRootTask()) {
|
||||
return true;
|
||||
|
||||
@@ -1329,6 +1329,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
|
||||
mHasPendingConfigurationChange = true;
|
||||
return;
|
||||
}
|
||||
dispatchConfiguration(config);
|
||||
}
|
||||
|
||||
void dispatchConfiguration(Configuration config) {
|
||||
mHasPendingConfigurationChange = false;
|
||||
if (mThread == null) {
|
||||
if (Build.IS_DEBUGGABLE && mHasImeService) {
|
||||
@@ -1367,8 +1371,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
|
||||
mPauseConfigurationDispatchCount++;
|
||||
}
|
||||
|
||||
void resumeConfigurationDispatch() {
|
||||
/** Returns {@code true} if the configuration change is pending to dispatch. */
|
||||
boolean resumeConfigurationDispatch() {
|
||||
if (mPauseConfigurationDispatchCount == 0) {
|
||||
return false;
|
||||
}
|
||||
mPauseConfigurationDispatchCount--;
|
||||
return mHasPendingConfigurationChange;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -630,9 +630,14 @@ class WindowToken extends WindowContainer<WindowState> {
|
||||
void updateSurfacePosition(SurfaceControl.Transaction t) {
|
||||
super.updateSurfacePosition(t);
|
||||
if (isFixedRotationTransforming()) {
|
||||
// The window is layouted in a simulated rotated display but the real display hasn't
|
||||
// rotated, so here transforms its surface to fit in the real display.
|
||||
mFixedRotationTransformState.transform(this);
|
||||
final ActivityRecord r = asActivityRecord();
|
||||
final Task rootTask = r != null ? r.getRootTask() : null;
|
||||
// Don't transform the activity in PiP because the PiP task organizer will handle it.
|
||||
if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
|
||||
// The window is laid out in a simulated rotated display but the real display hasn't
|
||||
// rotated, so here transforms its surface to fit in the real display.
|
||||
mFixedRotationTransformState.transform(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1379,7 +1379,7 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFixedRotationWithPip() {
|
||||
public void testFixedRotationWithPip() {
|
||||
final DisplayContent displayContent = mDefaultDisplay;
|
||||
unblockDisplayRotation(displayContent);
|
||||
// Make resume-top really update the activity state.
|
||||
@@ -1406,15 +1406,20 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
assertEquals(homeConfigOrientation, displayConfig.orientation);
|
||||
|
||||
clearInvocations(mWm);
|
||||
// Leave PiP to fullscreen. The orientation can be updated from
|
||||
// ActivityRecord#reportDescendantOrientationChangeIfNeeded.
|
||||
pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
||||
// Leave PiP to fullscreen. Simulate the step of PipTaskOrganizer that sets the activity
|
||||
// to fullscreen, so fixed rotation will apply on it.
|
||||
pinnedActivity.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
||||
homeActivity.setState(Task.ActivityState.STOPPED, "test");
|
||||
|
||||
assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
|
||||
verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
|
||||
assertEquals(pinnedConfigOrientation, displayConfig.orientation);
|
||||
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
|
||||
verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
|
||||
assertNotEquals(pinnedConfigOrientation, displayConfig.orientation);
|
||||
|
||||
// Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task.
|
||||
pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
||||
displayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
|
||||
assertFalse(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging());
|
||||
assertEquals(pinnedConfigOrientation, displayConfig.orientation);
|
||||
|
||||
clearInvocations(mWm);
|
||||
// Enter PiP from fullscreen. The orientation can be updated from
|
||||
|
||||
Reference in New Issue
Block a user