• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.shared.pip;
18 
19 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.TypedArray;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Matrix;
29 import android.graphics.Rect;
30 import android.graphics.drawable.Drawable;
31 import android.hardware.HardwareBuffer;
32 import android.util.TypedValue;
33 import android.view.SurfaceControl;
34 import android.window.TaskSnapshot;
35 
36 /**
37  * Represents the content overlay used during the entering PiP animation.
38  */
39 public abstract class PipContentOverlay {
40     // Fixed string used in WMShellFlickerTests
41     protected static final String LAYER_NAME = "PipContentOverlay";
42 
43     protected SurfaceControl mLeash;
44 
45     /** Attaches the internal {@link #mLeash} to the given parent leash. */
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)46     public abstract void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash);
47 
48     /** Detaches the internal {@link #mLeash} from its parent by removing itself. */
detach(SurfaceControl.Transaction tx)49     public void detach(SurfaceControl.Transaction tx) {
50         if (mLeash != null && mLeash.isValid()) {
51             tx.remove(mLeash);
52             tx.apply();
53         }
54     }
55 
56     @Nullable
getLeash()57     public SurfaceControl getLeash() {
58         return mLeash;
59     }
60 
61     /**
62      * Animates the internal {@link #mLeash} by a given fraction.
63      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
64      *                 call apply on this transaction, it should be applied on the caller side.
65      * @param currentBounds {@link Rect} of the current animation bounds.
66      * @param fraction progress of the animation ranged from 0f to 1f.
67      */
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)68     public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
69             Rect currentBounds, float fraction) {}
70 
71     /**
72      * Animates the internal {@link #mLeash} by a given fraction for a config-at-end transition.
73      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
74      *                 call apply on this transaction, it should be applied on the caller side.
75      * @param scale scaling to apply onto the overlay.
76      * @param fraction progress of the animation ranged from 0f to 1f.
77      * @param endBounds the final bounds PiP is animating into.
78      */
onAnimationUpdate(SurfaceControl.Transaction atomicTx, float scale, float fraction, Rect endBounds)79     public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
80             float scale, float fraction, Rect endBounds) {}
81 
82     /** A {@link PipContentOverlay} uses solid color. */
83     public static final class PipColorOverlay extends PipContentOverlay {
84         private static final String TAG = PipColorOverlay.class.getSimpleName();
85 
86         private final Context mContext;
87 
PipColorOverlay(Context context)88         public PipColorOverlay(Context context) {
89             mContext = context;
90             mLeash = new SurfaceControl.Builder()
91                     .setCallsite(TAG)
92                     .setName(LAYER_NAME)
93                     .setColorLayer()
94                     .build();
95         }
96 
97         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)98         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
99             tx.show(mLeash);
100             tx.setLayer(mLeash, Integer.MAX_VALUE);
101             tx.setColor(mLeash, getContentOverlayColor(mContext));
102             tx.setAlpha(mLeash, 0f);
103             tx.reparent(mLeash, parentLeash);
104             tx.apply();
105         }
106 
107         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)108         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
109                 Rect currentBounds, float fraction) {
110             atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
111         }
112 
getContentOverlayColor(Context context)113         private float[] getContentOverlayColor(Context context) {
114             final TypedArray ta = context.obtainStyledAttributes(new int[] {
115                     android.R.attr.colorBackground });
116             try {
117                 int colorAccent = ta.getColor(0, 0);
118                 return new float[] {
119                         Color.red(colorAccent) / 255f,
120                         Color.green(colorAccent) / 255f,
121                         Color.blue(colorAccent) / 255f };
122             } finally {
123                 ta.recycle();
124             }
125         }
126     }
127 
128     /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
129     public static final class PipSnapshotOverlay extends PipContentOverlay {
130         private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
131 
132         private final TaskSnapshot mSnapshot;
133         private final Rect mSourceRectHint;
134 
PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint)135         public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
136             mSnapshot = snapshot;
137             mSourceRectHint = new Rect(sourceRectHint);
138             mLeash = new SurfaceControl.Builder()
139                     .setCallsite(TAG)
140                     .setName(LAYER_NAME)
141                     .build();
142         }
143 
144         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)145         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
146             final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
147                     / mSnapshot.getHardwareBuffer().getWidth();
148             final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
149                     / mSnapshot.getHardwareBuffer().getHeight();
150             tx.show(mLeash);
151             tx.setLayer(mLeash, Integer.MAX_VALUE);
152             tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
153             // Relocate the content to parentLeash's coordinates.
154             tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
155             tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
156             tx.reparent(mLeash, parentLeash);
157             tx.apply();
158         }
159 
160         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)161         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
162                 Rect currentBounds, float fraction) {
163             // Do nothing. Keep the snapshot till animation ends.
164         }
165     }
166 
167     /** A {@link PipContentOverlay} shows app icon on solid color background. */
168     public static final class PipAppIconOverlay extends PipContentOverlay {
169         private static final String TAG = PipAppIconOverlay.class.getSimpleName();
170         // The maximum size for app icon in pixel.
171         private static final int MAX_APP_ICON_SIZE_DP = 72;
172 
173         private final Context mContext;
174         private final int mAppIconSizePx;
175         /**
176          * The bounds of the application window relative to the task leash.
177          */
178         private final Rect mRelativeAppBounds;
179         private final int mOverlayHalfSize;
180         private final Matrix mTmpTransform = new Matrix();
181         private final float[] mTmpFloat9 = new float[9];
182 
183         private Bitmap mBitmap;
184 
185         // TODO(b/356277166): add non-match_parent support on PIP2.
186         /**
187          * @param context the {@link Context} that contains the icon information
188          * @param relativeAppBounds the bounds of the app window frame relative to the task leash
189          * @param destinationBounds the bounds for rhe PIP task
190          * @param appIcon the app icon {@link Drawable}
191          * @param appIconSizePx the icon dimension in pixel
192          */
PipAppIconOverlay(@onNull Context context, @NonNull Rect relativeAppBounds, @NonNull Rect destinationBounds, @NonNull Drawable appIcon, int appIconSizePx)193         public PipAppIconOverlay(@NonNull Context context, @NonNull Rect relativeAppBounds,
194                 @NonNull Rect destinationBounds, @NonNull Drawable appIcon, int appIconSizePx) {
195             mContext = context;
196             final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
197                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
198             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
199 
200             final int overlaySize = getOverlaySize(relativeAppBounds, destinationBounds);
201             mOverlayHalfSize = overlaySize >> 1;
202             mRelativeAppBounds = relativeAppBounds;
203 
204             mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
205             prepareAppIconOverlay(appIcon);
206             mLeash = new SurfaceControl.Builder()
207                     .setCallsite(TAG)
208                     .setName(LAYER_NAME)
209                     .build();
210         }
211 
212         /**
213          * Returns the size of the app icon overlay.
214          *
215          * In order to have the overlay always cover the pip window during the transition,
216          * the overlay will be drawn with the max size of the start and end bounds in different
217          * rotation.
218          */
getOverlaySize(Rect overlayBounds, Rect destinationBounds)219         public static int getOverlaySize(Rect overlayBounds, Rect destinationBounds) {
220             final int appWidth = overlayBounds.width();
221             final int appHeight = overlayBounds.height();
222 
223             return Math.max(Math.max(appWidth, appHeight),
224                     Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
225         }
226 
227         @Override
attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)228         public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
229             final HardwareBuffer buffer = mBitmap.getHardwareBuffer();
230             tx.show(mLeash);
231             tx.setLayer(mLeash, Integer.MAX_VALUE);
232             tx.setBuffer(mLeash, buffer);
233             tx.setAlpha(mLeash, 0f);
234             tx.reparent(mLeash, parentLeash);
235             tx.apply();
236             // Cleanup the bitmap and buffer after setting up the leash
237             mBitmap.recycle();
238             mBitmap = null;
239             buffer.close();
240         }
241 
242         @Override
onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)243         public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
244                 Rect currentBounds, float fraction) {
245             mTmpTransform.reset();
246             // In order for the overlay to always cover the pip window, the overlay may have a
247             // size larger than the pip window. Make sure that app icon is at the center.
248             final int appBoundsCenterX = mRelativeAppBounds.centerX();
249             final int appBoundsCenterY = mRelativeAppBounds.centerY();
250             mTmpTransform.setTranslate(
251                     appBoundsCenterX - mOverlayHalfSize,
252                     appBoundsCenterY - mOverlayHalfSize);
253             // Scale back the bitmap with the pivot point at center.
254             final float scale = Math.min(
255                     (float) mRelativeAppBounds.width() / currentBounds.width(),
256                     (float) mRelativeAppBounds.height() / currentBounds.height());
257             mTmpTransform.postScale(scale, scale, appBoundsCenterX, appBoundsCenterY);
258             atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
259                     .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
260         }
261 
prepareAppIconOverlay(Drawable appIcon)262         private void prepareAppIconOverlay(Drawable appIcon) {
263             final Canvas canvas = new Canvas();
264             canvas.setBitmap(mBitmap);
265             final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
266                     android.R.attr.colorBackground });
267             try {
268                 int colorAccent = ta.getColor(0, 0);
269                 canvas.drawRGB(
270                         Color.red(colorAccent),
271                         Color.green(colorAccent),
272                         Color.blue(colorAccent));
273             } finally {
274                 ta.recycle();
275             }
276             final Rect appIconBounds = new Rect(
277                     mOverlayHalfSize - mAppIconSizePx / 2,
278                     mOverlayHalfSize - mAppIconSizePx / 2,
279                     mOverlayHalfSize + mAppIconSizePx / 2,
280                     mOverlayHalfSize + mAppIconSizePx / 2);
281             appIcon.setBounds(appIconBounds);
282             appIcon.draw(canvas);
283             Bitmap oldBitmap = mBitmap;
284             mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
285             oldBitmap.recycle();
286         }
287     }
288 }
289