Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39b03039 authored by Hongwei Wang's avatar Hongwei Wang
Browse files

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
parent 3a07ba97
Loading
Loading
Loading
Loading
+15 −10
Original line number Diff line number Diff line
@@ -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) {
+134 −7
Original line number Diff line number Diff line
@@ -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;
        }
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -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) {
                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 */);
+8 −1
Original line number Diff line number Diff line
@@ -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,8 +793,14 @@ 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.
                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,
                    0f, 1f);