Implement app icon overlay when entering PiP
This is done by - draw a bitmap with the theme background and app icon at its center - reparent the hardware buffer of that bitmap to the task leash - transform the bitmap leash to be unscaled and centered during the animation Video: http://recall/-/aaaaaabFQoRHlzixHdtY/cyuEoL0G1HOS8hjfRmlyPt Bug: 265998256 Test: turn on the flag and enter PiP, see video Change-Id: Ia7354d83e9b5701f1358b44b0db7bb32d48ccd2a
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user