diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 6728c00af51b9..d9ac76e833d87 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.view.Surface; import android.view.SurfaceControl; @@ -361,22 +362,26 @@ public class PipAnimationController { } void setColorContentOverlay(Context context) { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - if (mContentOverlay != null) { - mContentOverlay.detach(tx); - } - mContentOverlay = new PipContentOverlay.PipColorOverlay(context); - mContentOverlay.attach(tx, mLeash); + reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context)); } void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { + reattachContentOverlay( + new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint)); + } + + void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) { + reattachContentOverlay( + new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo)); + } + + private void reattachContentOverlay(PipContentOverlay overlay) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } - mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint); + mContentOverlay = overlay; mContentOverlay.attach(tx, mLeash); } @@ -570,8 +575,9 @@ public class PipAnimationController { final Rect base = getBaseValue(); final Rect start = getStartValue(); final Rect end = getEndValue(); + Rect bounds = mRectEvaluator.evaluate(fraction, start, end); if (mContentOverlay != null) { - mContentOverlay.onAnimationUpdate(tx, fraction); + mContentOverlay.onAnimationUpdate(tx, bounds, fraction); } if (rotatedEndRect != null) { // Animate the bounds in a different orientation. It only happens when @@ -579,7 +585,6 @@ public class PipAnimationController { applyRotation(tx, leash, fraction, start, end); return; } - Rect bounds = mRectEvaluator.evaluate(fraction, start, end); float angle = (1.0f - fraction) * startingAngle; setCurrentValue(bounds); if (inScaleTransition() || sourceHintRect == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 283b1ec0f7527..480bf93b2ddb9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -16,11 +16,21 @@ package com.android.wm.shell.pip; +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; 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 android.view.SurfaceSession; import android.window.TaskSnapshot; @@ -51,9 +61,11 @@ public abstract class PipContentOverlay { * Animates the internal {@link #mLeash} by a given fraction. * @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 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, float fraction); + public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction); /** * Callback when reaches the end of animation on the internal {@link #mLeash}. @@ -66,13 +78,15 @@ public abstract class PipContentOverlay { /** A {@link PipContentOverlay} uses solid color. */ public static final class PipColorOverlay extends PipContentOverlay { + private static final String TAG = PipColorOverlay.class.getSimpleName(); + private final Context mContext; public PipColorOverlay(Context context) { mContext = context; mLeash = new SurfaceControl.Builder(new SurfaceSession()) - .setCallsite("PipAnimation") - .setName(PipColorOverlay.class.getSimpleName()) + .setCallsite(TAG) + .setName(TAG) .setColorLayer() .build(); } @@ -88,7 +102,8 @@ public abstract class PipContentOverlay { } @Override - public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) { + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); } @@ -114,6 +129,8 @@ public abstract class PipContentOverlay { /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */ public static final class PipSnapshotOverlay extends PipContentOverlay { + private static final String TAG = PipSnapshotOverlay.class.getSimpleName(); + private final TaskSnapshot mSnapshot; private final Rect mSourceRectHint; @@ -121,8 +138,8 @@ public abstract class PipContentOverlay { mSnapshot = snapshot; mSourceRectHint = new Rect(sourceRectHint); mLeash = new SurfaceControl.Builder(new SurfaceSession()) - .setCallsite("PipAnimation") - .setName(PipSnapshotOverlay.class.getSimpleName()) + .setCallsite(TAG) + .setName(TAG) .build(); } @@ -143,7 +160,8 @@ public abstract class PipContentOverlay { } @Override - public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) { + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { // Do nothing. Keep the snapshot till animation ends. } @@ -152,4 +170,113 @@ public abstract class PipContentOverlay { atomicTx.remove(mLeash); } } + + /** A {@link PipContentOverlay} shows app icon on solid color background. */ + public static final class PipAppIconOverlay extends PipContentOverlay { + private static final String TAG = PipAppIconOverlay.class.getSimpleName(); + private static final int APP_ICON_SIZE_DP = 48; + + private final Context mContext; + private final int mAppIconSizePx; + private final Rect mAppBounds; + private final Matrix mTmpTransform = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + + private Bitmap mBitmap; + + public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) { + mContext = context; + mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP, + context.getResources().getDisplayMetrics()); + mAppBounds = new Rect(appBounds); + mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(), + Bitmap.Config.ARGB_8888); + prepareAppIconOverlay(activityInfo); + mLeash = new SurfaceControl.Builder(new SurfaceSession()) + .setCallsite(TAG) + .setName(TAG) + .build(); + } + + @Override + public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { + tx.show(mLeash); + tx.setLayer(mLeash, Integer.MAX_VALUE); + tx.setBuffer(mLeash, mBitmap.getHardwareBuffer()); + tx.reparent(mLeash, parentLeash); + tx.apply(); + } + + @Override + public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, + Rect currentBounds, float fraction) { + mTmpTransform.reset(); + // Scale back the bitmap with the pivot point at center. + mTmpTransform.postScale( + (float) mAppBounds.width() / currentBounds.width(), + (float) mAppBounds.height() / currentBounds.height(), + mAppBounds.centerX(), + mAppBounds.centerY()); + atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9) + .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); + } + + @Override + public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { + atomicTx.remove(mLeash); + } + + @Override + public void detach(SurfaceControl.Transaction tx) { + super.detach(tx); + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + } + + private void prepareAppIconOverlay(ActivityInfo activityInfo) { + 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 Drawable appIcon = loadActivityInfoIcon(activityInfo, + mContext.getResources().getConfiguration().densityDpi); + final Rect appIconBounds = new Rect( + mAppBounds.centerX() - mAppIconSizePx / 2, + mAppBounds.centerY() - mAppIconSizePx / 2, + mAppBounds.centerX() + mAppIconSizePx / 2, + mAppBounds.centerY() + mAppIconSizePx / 2); + appIcon.setBounds(appIconBounds); + appIcon.draw(canvas); + mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */); + } + + // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon + private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) { + final int iconRes = ai.getIconResource(); + Drawable icon = null; + // Get the preferred density icon from the app's resources + if (density != 0 && iconRes != 0) { + try { + final Resources resources = mContext.getPackageManager() + .getResourcesForApplication(ai.applicationInfo); + icon = resources.getDrawableForDensity(iconRes, density); + } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { } + } + // Get the default density icon + if (icon == null) { + icon = ai.loadIcon(mContext.getPackageManager()); + } + return icon; + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 8ba2583757cda..aad27b991db98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -62,6 +62,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; import android.view.Display; @@ -1568,7 +1569,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Similar to auto-enter-pip transition, we use content overlay when there is no // source rect hint to enter PiP use bounds animation. if (sourceHintRect == null) { - animator.setColorContentOverlay(mContext); + if (SystemProperties.getBoolean( + "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + animator.setAppIconContentOverlay( + mContext, currentBounds, mTaskInfo.topActivityInfo); + } else { + animator.setColorContentOverlay(mContext); + } } else { final TaskSnapshot snapshot = PipUtils.getTaskSnapshot( mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 83158ffafa7e9..d9d10099e53a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -50,6 +50,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; +import android.os.SystemProperties; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; @@ -792,7 +793,13 @@ public class PipTransition extends PipTransitionController { if (sourceHintRect == null) { // We use content overlay when there is no source rect hint to enter PiP use bounds // animation. - animator.setColorContentOverlay(mContext); + if (SystemProperties.getBoolean( + "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + animator.setAppIconContentOverlay( + mContext, currentBounds, taskInfo.topActivityInfo); + } else { + animator.setColorContentOverlay(mContext); + } } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,