summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ikram Gabiyev <gabiyev@google.com> 2024-10-25 10:47:23 -0700
committer Ikram Gabiyev <gabiyev@google.com> 2024-10-25 10:53:36 -0700
commitcf5da718513fc85c391673c0632a1d1058927ab8 (patch)
treeb66dab1106aef93276fdc8333a285b50c612d2c2
parentc7f83672456247d1401f97fc2fa1679f12f0b478 (diff)
Implement content overlay in PiP2
Make sure the direct enter PiP and auto-enter PiP with invalid source-rect-hint use app icon overlay during the enter-animation. Once the enter animation is done, we start fading the overlay out. Note: with PiP2, we can finish the transition and set the state to ENTERED_PIP right away even before overlay fadeout is done. This allows PiP interactions earlier than in PiP1. Bug: 373946868 Flag: com.android.wm.shell.enable_pip2 Test: auto-enter PiP in btn-nav with invalid src-rect-hint Change-Id: I2156b7890243c13f278d6023802e41e69db09683
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java82
5 files changed, 266 insertions, 44 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index 6c83d88032df..eb7ef1478a90 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -63,8 +63,19 @@ public abstract class PipContentOverlay {
* @param currentBounds {@link Rect} of the current animation bounds.
* @param fraction progress of the animation ranged from 0f to 1f.
*/
- public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
- Rect currentBounds, float fraction);
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {}
+
+ /**
+ * Animates the internal {@link #mLeash} by a given fraction for a config-at-end transition.
+ * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
+ * call apply on this transaction, it should be applied on the caller side.
+ * @param scale scaling to apply onto the overlay.
+ * @param fraction progress of the animation ranged from 0f to 1f.
+ * @param endBounds the final bounds PiP is animating into.
+ */
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ float scale, float fraction, Rect endBounds) {}
/** A {@link PipContentOverlay} uses solid color. */
public static final class PipColorOverlay extends PipContentOverlay {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index 5381a626ddcf..740b9af56144 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -23,6 +23,7 @@ import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -33,10 +34,13 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
/**
* Animator that handles bounds animations for entering PIP.
@@ -59,6 +63,10 @@ public class PipEnterAnimator extends ValueAnimator
private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
+ Matrix mTransformTensor = new Matrix();
+ final float[] mMatrixTmp = new float[9];
+ @Nullable private PipContentOverlay mContentOverlay;
+
// Internal state representing initial transform - cached to avoid recalculation.
private final PointF mInitScale = new PointF();
@@ -67,9 +75,6 @@ public class PipEnterAnimator extends ValueAnimator
private final PointF mInitActivityScale = new PointF();
private final PointF mInitActivityPos = new PointF();
- Matrix mTransformTensor = new Matrix();
- final float[] mMatrixTmp = new float[9];
-
public PipEnterAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
@@ -161,10 +166,15 @@ public class PipEnterAnimator extends ValueAnimator
mRectEvaluator.evaluate(fraction, initCrop, endCrop);
tx.setCrop(mLeash, mAnimatedRect);
+ mTransformTensor.reset();
mTransformTensor.setScale(scaleX, scaleY);
mTransformTensor.postTranslate(posX, posY);
mTransformTensor.postRotate(degrees);
tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp);
+
+ if (mContentOverlay != null) {
+ mContentOverlay.onAnimationUpdate(tx, 1f / scaleX, fraction, mEndBounds);
+ }
}
// no-ops
@@ -200,4 +210,48 @@ public class PipEnterAnimator extends ValueAnimator
}
PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop);
}
+
+ /**
+ * Initializes and attaches an app icon overlay on top of the PiP layer.
+ */
+ public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int appIconSizePx) {
+ reattachAppIconOverlay(
+ new PipAppIconOverlay(context, appBounds, destinationBounds,
+ new IconProvider(context).getIcon(activityInfo), appIconSizePx));
+ }
+
+ private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ if (mContentOverlay != null) {
+ mContentOverlay.detach(tx);
+ }
+ mContentOverlay = overlay;
+ mContentOverlay.attach(tx, mLeash);
+ }
+
+ /**
+ * Clears the {@link #mContentOverlay}, this should be done after the content overlay is
+ * faded out.
+ */
+ public void clearAppIconOverlay() {
+ if (mContentOverlay == null) {
+ return;
+ }
+ SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+ mContentOverlay.detach(tx);
+ mContentOverlay = null;
+ }
+
+ /**
+ * @return the app icon overlay leash; null if no overlay is attached.
+ */
+ @Nullable
+ public SurfaceControl getContentOverlayLeash() {
+ if (mContentOverlay == null) {
+ return null;
+ }
+ return mContentOverlay.getLeash();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
new file mode 100644
index 000000000000..b4cf8905d02e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 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.wm.shell.pip2.phone;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.shared.pip.PipContentOverlay;
+
+/** A {@link PipContentOverlay} shows app icon on solid color background. */
+public final class PipAppIconOverlay extends PipContentOverlay {
+ private static final String TAG = PipAppIconOverlay.class.getSimpleName();
+ // The maximum size for app icon in pixel.
+ private static final int MAX_APP_ICON_SIZE_DP = 72;
+
+ private final Context mContext;
+ private final int mAppIconSizePx;
+ private final Rect mAppBounds;
+ private final int mOverlayHalfSize;
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+
+ private Bitmap mBitmap;
+
+ public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
+ Drawable appIcon, int appIconSizePx) {
+ mContext = context;
+ final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+ MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
+ mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
+
+ final int overlaySize = getOverlaySize(appBounds, destinationBounds);
+ mOverlayHalfSize = overlaySize >> 1;
+
+ // When the activity is in the secondary split, make sure the scaling center is not
+ // offset.
+ mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
+
+ mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
+ prepareAppIconOverlay(appIcon);
+ mLeash = new SurfaceControl.Builder()
+ .setCallsite(TAG)
+ .setName(LAYER_NAME)
+ .build();
+ }
+
+ /**
+ * Returns the size of the app icon overlay.
+ *
+ * In order to have the overlay always cover the pip window during the transition,
+ * the overlay will be drawn with the max size of the start and end bounds in different
+ * rotation.
+ */
+ public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+
+ return Math.max(Math.max(appWidth, appHeight),
+ Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
+ }
+
+ @Override
+ public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+ tx.show(mLeash);
+ tx.setLayer(mLeash, Integer.MAX_VALUE);
+ tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+ tx.setAlpha(mLeash, 0f);
+ tx.reparent(mLeash, parentLeash);
+ tx.apply();
+ }
+
+ @Override
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ float scale, float fraction, Rect endBounds) {
+ mTmpTransform.reset();
+ // Scale back the bitmap with the pivot at parent origin
+ mTmpTransform.setScale(scale, scale);
+ // We are negative-cropping away from the final bounds crop in config-at-end enter PiP;
+ // this means that the overlay shift depends on the final bounds.
+ // Note: translation is also dependent on the scaling of the parent.
+ mTmpTransform.postTranslate(endBounds.width() / 2f - mOverlayHalfSize * scale,
+ endBounds.height() / 2f - mOverlayHalfSize * scale);
+ atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
+ .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
+ }
+
+
+
+ @Override
+ public void detach(SurfaceControl.Transaction tx) {
+ super.detach(tx);
+ if (mBitmap != null && !mBitmap.isRecycled()) {
+ mBitmap.recycle();
+ }
+ }
+
+ private void prepareAppIconOverlay(Drawable appIcon) {
+ final Canvas canvas = new Canvas();
+ canvas.setBitmap(mBitmap);
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ android.R.attr.colorBackground });
+ try {
+ int colorAccent = ta.getColor(0, 0);
+ canvas.drawRGB(
+ Color.red(colorAccent),
+ Color.green(colorAccent),
+ Color.blue(colorAccent));
+ } finally {
+ ta.recycle();
+ }
+ final Rect appIconBounds = new Rect(
+ mOverlayHalfSize - mAppIconSizePx / 2,
+ mOverlayHalfSize - mAppIconSizePx / 2,
+ mOverlayHalfSize + mAppIconSizePx / 2,
+ mOverlayHalfSize + mAppIconSizePx / 2);
+ appIcon.setBounds(appIconBounds);
+ appIcon.draw(canvas);
+ mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 0427294579dc..9a93371621c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -456,6 +456,10 @@ public class PipController implements ConfigurationChangeListener,
}
}
+ private void setLauncherAppIconSize(int iconSizePx) {
+ mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
+ }
+
/**
* The interface for calls from outside the Shell, within the host process.
*/
@@ -571,7 +575,10 @@ public class PipController implements ConfigurationChangeListener,
}
@Override
- public void setLauncherAppIconSize(int iconSizePx) {}
+ public void setLauncherAppIconSize(int iconSizePx) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize",
+ (controller) -> controller.setLauncherAppIconSize(iconSizePx));
+ }
@Override
public void setPipAnimationListener(IPipAnimationListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index def76a0d0819..e7fb5f24401a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -29,9 +29,6 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
@@ -361,31 +358,17 @@ public class PipTransition extends PipTransitionController implements
animator.setEnterStartState(pipChange, pipActivityChange);
animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
startTransaction.apply();
- finishInner();
- return true;
- }
- private void startOverlayFadeoutAnimation() {
- ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
- animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
+ if (swipePipToHomeOverlay != null) {
+ // fadeout the overlay if needed.
+ startOverlayFadeoutAnimation(swipePipToHomeOverlay, () -> {
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.remove(mPipTransitionState.getSwipePipToHomeOverlay());
+ tx.remove(swipePipToHomeOverlay);
tx.apply();
-
- // We have fully completed enter-PiP animation after the overlay is gone.
- mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
- }
- });
- animator.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply();
- });
- animator.start();
+ });
+ }
+ finishInner();
+ return true;
}
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@@ -404,15 +387,18 @@ public class PipTransition extends PipTransitionController implements
return false;
}
- Rect endBounds = pipChange.getEndAbsBounds();
- SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
- Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+ final Rect startBounds = pipChange.getStartAbsBounds();
+ final Rect endBounds = pipChange.getEndAbsBounds();
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
- }
+ final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
+ final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
+
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
+ endBounds);
+
+ final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+ final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
+ : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
// For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
// make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
@@ -440,14 +426,36 @@ public class PipTransition extends PipTransitionController implements
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, sourceRectHint, delta);
+ startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+ if (sourceRectHint == null) {
+ // update the src-rect-hint in params in place, to set up initial animator transform.
+ params.getSourceRectHint().set(adjustedSourceRectHint);
+ animator.setAppIconContentOverlay(
+ mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
+ mPipBoundsState.getLauncherState().getAppIconSizePx());
+ }
animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange,
pipActivityChange));
- animator.setAnimationEndCallback(this::finishInner);
+ animator.setAnimationEndCallback(() -> {
+ if (animator.getContentOverlayLeash() != null) {
+ startOverlayFadeoutAnimation(animator.getContentOverlayLeash(),
+ animator::clearAppIconOverlay);
+ }
+ finishInner();
+ });
animator.start();
return true;
}
+ private void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
+ @NonNull Runnable onAnimationEnd) {
+ PipAlphaAnimator animator = new PipAlphaAnimator(mContext, overlayLeash,
+ null /* startTx */, PipAlphaAnimator.FADE_OUT);
+ animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
+ animator.setAnimationEndCallback(onAnimationEnd);
+ animator.start();
+ }
+
private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
TransitionInfo.Change pipActivityChange, int endRotation) {
final Rect endBounds = pipTaskChange.getEndAbsBounds();
@@ -692,9 +700,7 @@ public class PipTransition extends PipTransitionController implements
private void finishInner() {
finishTransition(null /* tx */);
- if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
- startOverlayFadeoutAnimation();
- } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
+ if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
// If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
// and then we get a signal on client finishing its draw after the transition
// has ended, then we have fully entered PiP.