Merge "Implement app icon overlay when entering PiP" into tm-qpr-dev am: 55176db403
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21182131 Change-Id: Ibbf8bf73c26bcd39b830d803658d7170c2283533 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
if (SystemProperties.getBoolean(
|
||||||
|
"persist.wm.debug.enable_pip_app_icon_overlay", false)) {
|
||||||
|
animator.setAppIconContentOverlay(
|
||||||
|
mContext, currentBounds, mTaskInfo.topActivityInfo);
|
||||||
|
} else {
|
||||||
animator.setColorContentOverlay(mContext);
|
animator.setColorContentOverlay(mContext);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
|
final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
|
||||||
mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
|
mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
|
||||||
|
|||||||
@@ -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,8 +793,14 @@ 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.
|
||||||
|
if (SystemProperties.getBoolean(
|
||||||
|
"persist.wm.debug.enable_pip_app_icon_overlay", false)) {
|
||||||
|
animator.setAppIconContentOverlay(
|
||||||
|
mContext, currentBounds, taskInfo.topActivityInfo);
|
||||||
|
} else {
|
||||||
animator.setColorContentOverlay(mContext);
|
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,
|
||||||
0f, 1f);
|
0f, 1f);
|
||||||
|
|||||||
Reference in New Issue
Block a user