Loading libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java +13 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +57 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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(); Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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(); } } libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * 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 */); } } libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +8 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +44 −38 Original line number Diff line number Diff line Loading @@ -30,9 +30,6 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_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; Loading Loading @@ -362,31 +359,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, Loading @@ -405,15 +388,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 Loading Loading @@ -441,14 +427,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(); Loading Loading @@ -696,9 +704,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. Loading Loading
libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java +13 −2 Original line number Diff line number Diff line Loading @@ -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 { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +57 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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(); Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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(); } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipAppIconOverlay.java 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * 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 */); } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +8 −1 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +44 −38 Original line number Diff line number Diff line Loading @@ -30,9 +30,6 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_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; Loading Loading @@ -362,31 +359,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, Loading @@ -405,15 +388,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 Loading Loading @@ -441,14 +427,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(); Loading Loading @@ -696,9 +704,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. Loading