1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.pip;
18 
19 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
20 
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.content.res.TypedArray;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.Matrix;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
30 import android.util.TypedValue;
31 import android.view.SurfaceControl;
32 import android.view.SurfaceSession;
33 import android.window.TaskSnapshot;
34 
35 /**
36  * Represents the content overlay used during the entering PiP animation.
37  */
38 public abstract class PipContentOverlay {
39     // Fixed string used in WMShellFlickerTests
40     protected static final String LAYER_NAME = "PipContentOverlay";
41 
42     protected SurfaceControl mLeash;
43 
44     /** Attaches the internal {@link #mLeash} to the given parent leash. */
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)45     public abstract void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash);
46 
47     /** Detaches the internal {@link #mLeash} from its parent by removing itself. */
detach(SurfaceControl.Transaction tx)48     public void detach(SurfaceControl.Transaction tx) {
49         if (mLeash != null && mLeash.isValid()) {
50             tx.remove(mLeash);
51             tx.apply();
52         }
53     }
54 
55     @Nullable
getLeash()56     public SurfaceControl getLeash() {
57         return mLeash;
58     }
59 
60     /**
61      * Animates the internal {@link #mLeash} by a given fraction.
62      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
63      *                 call apply on this transaction, it should be applied on the caller side.
64      * @param currentBounds {@link Rect} of the current animation bounds.
65      * @param fraction progress of the animation ranged from 0f to 1f.
66      */
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)67     public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
68             Rect currentBounds, float fraction);
69 
70     /**
71      * Callback when reaches the end of animation on the internal {@link #mLeash}.
72      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
73      *                 call apply on this transaction, it should be applied on the caller side.
74      * @param destinationBounds {@link Rect} of the final bounds.
75      */
onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)76     public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx,
77             Rect destinationBounds);
78 
79     /** A {@link PipContentOverlay} uses solid color. */
80     public static final class PipColorOverlay extends PipContentOverlay {
81         private static final String TAG = PipColorOverlay.class.getSimpleName();
82 
83         private final Context mContext;
84 
PipColorOverlay(Context context)85         public PipColorOverlay(Context context) {
86             mContext = context;
87             mLeash = new SurfaceControl.Builder(new SurfaceSession())
88                     .setCallsite(TAG)
89                     .setName(LAYER_NAME)
90                     .setColorLayer()
91                     .build();
92         }
93 
94         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)95         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
96             tx.show(mLeash);
97             tx.setLayer(mLeash, Integer.MAX_VALUE);
98             tx.setColor(mLeash, getContentOverlayColor(mContext));
99             tx.setAlpha(mLeash, 0f);
100             tx.reparent(mLeash, parentLeash);
101             tx.apply();
102         }
103 
104         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)105         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
106                 Rect currentBounds, float fraction) {
107             atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
108         }
109 
110         @Override
onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)111         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
112             // Do nothing. Color overlay should be fully opaque by now.
113         }
114 
getContentOverlayColor(Context context)115         private float[] getContentOverlayColor(Context context) {
116             final TypedArray ta = context.obtainStyledAttributes(new int[] {
117                     android.R.attr.colorBackground });
118             try {
119                 int colorAccent = ta.getColor(0, 0);
120                 return new float[] {
121                         Color.red(colorAccent) / 255f,
122                         Color.green(colorAccent) / 255f,
123                         Color.blue(colorAccent) / 255f };
124             } finally {
125                 ta.recycle();
126             }
127         }
128     }
129 
130     /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
131     public static final class PipSnapshotOverlay extends PipContentOverlay {
132         private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
133 
134         private final TaskSnapshot mSnapshot;
135         private final Rect mSourceRectHint;
136 
PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint)137         public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
138             mSnapshot = snapshot;
139             mSourceRectHint = new Rect(sourceRectHint);
140             mLeash = new SurfaceControl.Builder(new SurfaceSession())
141                     .setCallsite(TAG)
142                     .setName(LAYER_NAME)
143                     .build();
144         }
145 
146         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)147         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
148             final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
149                     / mSnapshot.getHardwareBuffer().getWidth();
150             final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
151                     / mSnapshot.getHardwareBuffer().getHeight();
152             tx.show(mLeash);
153             tx.setLayer(mLeash, Integer.MAX_VALUE);
154             tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
155             // Relocate the content to parentLeash's coordinates.
156             tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
157             tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
158             tx.reparent(mLeash, parentLeash);
159             tx.apply();
160         }
161 
162         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)163         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
164                 Rect currentBounds, float fraction) {
165             // Do nothing. Keep the snapshot till animation ends.
166         }
167 
168         @Override
onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)169         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
170             atomicTx.remove(mLeash);
171         }
172     }
173 
174     /** A {@link PipContentOverlay} shows app icon on solid color background. */
175     public static final class PipAppIconOverlay extends PipContentOverlay {
176         private static final String TAG = PipAppIconOverlay.class.getSimpleName();
177         // The maximum size for app icon in pixel.
178         private static final int MAX_APP_ICON_SIZE_DP = 72;
179 
180         private final Context mContext;
181         private final int mAppIconSizePx;
182         private final Rect mAppBounds;
183         private final Matrix mTmpTransform = new Matrix();
184         private final float[] mTmpFloat9 = new float[9];
185 
186         private Bitmap mBitmap;
187 
PipAppIconOverlay(Context context, Rect appBounds, Drawable appIcon, int appIconSizePx)188         public PipAppIconOverlay(Context context, Rect appBounds,
189                 Drawable appIcon, int appIconSizePx) {
190             mContext = context;
191             final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
192                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
193             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
194             mAppBounds = new Rect(appBounds);
195             mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
196                     Bitmap.Config.ARGB_8888);
197             prepareAppIconOverlay(appIcon);
198             mLeash = new SurfaceControl.Builder(new SurfaceSession())
199                     .setCallsite(TAG)
200                     .setName(LAYER_NAME)
201                     .build();
202         }
203 
204         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)205         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
206             tx.show(mLeash);
207             tx.setLayer(mLeash, Integer.MAX_VALUE);
208             tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
209             tx.setAlpha(mLeash, 0f);
210             tx.reparent(mLeash, parentLeash);
211             tx.apply();
212         }
213 
214         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)215         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
216                 Rect currentBounds, float fraction) {
217             mTmpTransform.reset();
218             // Scale back the bitmap with the pivot point at center.
219             mTmpTransform.postScale(
220                     (float) mAppBounds.width() / currentBounds.width(),
221                     (float) mAppBounds.height() / currentBounds.height(),
222                     mAppBounds.centerX(),
223                     mAppBounds.centerY());
224             atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
225                     .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
226         }
227 
228         @Override
onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)229         public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
230             atomicTx.remove(mLeash);
231         }
232 
233         @Override
detach(SurfaceControl.Transaction tx)234         public void detach(SurfaceControl.Transaction tx) {
235             super.detach(tx);
236             if (mBitmap != null && !mBitmap.isRecycled()) {
237                 mBitmap.recycle();
238             }
239         }
240 
prepareAppIconOverlay(Drawable appIcon)241         private void prepareAppIconOverlay(Drawable appIcon) {
242             final Canvas canvas = new Canvas();
243             canvas.setBitmap(mBitmap);
244             final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
245                     android.R.attr.colorBackground });
246             try {
247                 int colorAccent = ta.getColor(0, 0);
248                 canvas.drawRGB(
249                         Color.red(colorAccent),
250                         Color.green(colorAccent),
251                         Color.blue(colorAccent));
252             } finally {
253                 ta.recycle();
254             }
255             final Rect appIconBounds = new Rect(
256                     mAppBounds.centerX() - mAppIconSizePx / 2,
257                     mAppBounds.centerY() - mAppIconSizePx / 2,
258                     mAppBounds.centerX() + mAppIconSizePx / 2,
259                     mAppBounds.centerY() + mAppIconSizePx / 2);
260             appIcon.setBounds(appIconBounds);
261             appIcon.draw(canvas);
262             mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
263         }
264     }
265 }
266