blob: c701b9581ca212e5d0eb7aa9534ca09de1d7cda8 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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;
/**
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
// Fixed string used in WMShellFlickerTests
protected static final String LAYER_NAME = "PipContentOverlay";
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
public abstract void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash);
/** Detaches the internal {@link #mLeash} from its parent by removing itself. */
public void detach(SurfaceControl.Transaction tx) {
if (mLeash != null && mLeash.isValid()) {
tx.remove(mLeash);
tx.apply();
}
}
@Nullable
public SurfaceControl getLeash() {
return mLeash;
}
/**
* 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,
Rect currentBounds, float fraction);
/**
* Callback when reaches the end of animation on the internal {@link #mLeash}.
* @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 destinationBounds {@link Rect} of the final bounds.
*/
public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx,
Rect destinationBounds);
/** 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(TAG)
.setName(LAYER_NAME)
.setColorLayer()
.build();
}
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
tx.setColor(mLeash, getContentOverlayColor(mContext));
tx.setAlpha(mLeash, 0f);
tx.reparent(mLeash, parentLeash);
tx.apply();
}
@Override
public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
Rect currentBounds, float fraction) {
atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
// Do nothing. Color overlay should be fully opaque by now.
}
private float[] getContentOverlayColor(Context context) {
final TypedArray ta = context.obtainStyledAttributes(new int[] {
android.R.attr.colorBackground });
try {
int colorAccent = ta.getColor(0, 0);
return new float[] {
Color.red(colorAccent) / 255f,
Color.green(colorAccent) / 255f,
Color.blue(colorAccent) / 255f };
} finally {
ta.recycle();
}
}
}
/** 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;
public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
.setName(LAYER_NAME)
.build();
}
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
/ mSnapshot.getHardwareBuffer().getWidth();
final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
/ mSnapshot.getHardwareBuffer().getHeight();
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
// Relocate the content to parentLeash's coordinates.
tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
tx.reparent(mLeash, parentLeash);
tx.apply();
}
@Override
public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
Rect currentBounds, float fraction) {
// Do nothing. Keep the snapshot till animation ends.
}
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
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();
// The maximum size for app icon in pixel.
private static final int MAX_APP_ICON_SIZE_DP = 72;
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,
Drawable appIcon, int appIconSizePx) {
mContext = context;
final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
mAppBounds = new Rect(appBounds);
mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
Bitmap.Config.ARGB_8888);
prepareAppIconOverlay(appIcon);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
.setName(LAYER_NAME)
.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.setAlpha(mLeash, 0f);
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(Drawable appIcon) {
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 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 */);
}
}
}