Merge "Implement app icon overlay when entering PiP" into tm-qpr-dev

This commit is contained in:
Hongwei Wang
2023-02-04 03:10:59 +00:00
committed by Android (Google) Code Review
4 changed files with 165 additions and 19 deletions

View File

@@ -28,6 +28,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.TaskInfo; import android.app.TaskInfo;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect; import android.graphics.Rect;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
@@ -361,22 +362,26 @@ public class PipAnimationController {
} }
void setColorContentOverlay(Context context) { void setColorContentOverlay(Context context) {
final SurfaceControl.Transaction tx = reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
mContentOverlay = new PipContentOverlay.PipColorOverlay(context);
mContentOverlay.attach(tx, mLeash);
} }
void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { 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 = final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction(); mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) { if (mContentOverlay != null) {
mContentOverlay.detach(tx); mContentOverlay.detach(tx);
} }
mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint); mContentOverlay = overlay;
mContentOverlay.attach(tx, mLeash); mContentOverlay.attach(tx, mLeash);
} }
@@ -570,8 +575,9 @@ public class PipAnimationController {
final Rect base = getBaseValue(); final Rect base = getBaseValue();
final Rect start = getStartValue(); final Rect start = getStartValue();
final Rect end = getEndValue(); final Rect end = getEndValue();
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
if (mContentOverlay != null) { if (mContentOverlay != null) {
mContentOverlay.onAnimationUpdate(tx, fraction); mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
} }
if (rotatedEndRect != null) { if (rotatedEndRect != null) {
// Animate the bounds in a different orientation. It only happens when // Animate the bounds in a different orientation. It only happens when
@@ -579,7 +585,6 @@ public class PipAnimationController {
applyRotation(tx, leash, fraction, start, end); applyRotation(tx, leash, fraction, start, end);
return; return;
} }
Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
float angle = (1.0f - fraction) * startingAngle; float angle = (1.0f - fraction) * startingAngle;
setCurrentValue(bounds); setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) { if (inScaleTransition() || sourceHintRect == null) {

View File

@@ -16,11 +16,21 @@
package com.android.wm.shell.pip; package com.android.wm.shell.pip;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; 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.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceSession; import android.view.SurfaceSession;
import android.window.TaskSnapshot; import android.window.TaskSnapshot;
@@ -51,9 +61,11 @@ public abstract class PipContentOverlay {
* Animates the internal {@link #mLeash} by a given fraction. * Animates the internal {@link #mLeash} by a given fraction.
* @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
* call apply on this transaction, it should be applied on the caller side. * 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. * @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}. * 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. */ /** A {@link PipContentOverlay} uses solid color. */
public static final class PipColorOverlay extends PipContentOverlay { public static final class PipColorOverlay extends PipContentOverlay {
private static final String TAG = PipColorOverlay.class.getSimpleName();
private final Context mContext; private final Context mContext;
public PipColorOverlay(Context context) { public PipColorOverlay(Context context) {
mContext = context; mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession()) mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite("PipAnimation") .setCallsite(TAG)
.setName(PipColorOverlay.class.getSimpleName()) .setName(TAG)
.setColorLayer() .setColorLayer()
.build(); .build();
} }
@@ -88,7 +102,8 @@ public abstract class PipContentOverlay {
} }
@Override @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); 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}. */ /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
public static final class PipSnapshotOverlay extends PipContentOverlay { public static final class PipSnapshotOverlay extends PipContentOverlay {
private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
private final TaskSnapshot mSnapshot; private final TaskSnapshot mSnapshot;
private final Rect mSourceRectHint; private final Rect mSourceRectHint;
@@ -121,8 +138,8 @@ public abstract class PipContentOverlay {
mSnapshot = snapshot; mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint); mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession()) mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite("PipAnimation") .setCallsite(TAG)
.setName(PipSnapshotOverlay.class.getSimpleName()) .setName(TAG)
.build(); .build();
} }
@@ -143,7 +160,8 @@ public abstract class PipContentOverlay {
} }
@Override @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. // Do nothing. Keep the snapshot till animation ends.
} }
@@ -152,4 +170,113 @@ public abstract class PipContentOverlay {
atomicTx.remove(mLeash); 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;
}
}
} }

View File

@@ -62,6 +62,7 @@ import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log; import android.util.Log;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Display; 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 // Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation. // source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) { 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 { } else {
final TaskSnapshot snapshot = PipUtils.getTaskSnapshot( final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */); mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);

View File

@@ -50,6 +50,7 @@ import android.graphics.Matrix;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemProperties;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.WindowManager; import android.view.WindowManager;
@@ -792,7 +793,13 @@ public class PipTransition extends PipTransitionController {
if (sourceHintRect == null) { if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds // We use content overlay when there is no source rect hint to enter PiP use bounds
// animation. // 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) { } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,