• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.window;
18 
19 import static android.graphics.Color.WHITE;
20 import static android.graphics.Color.alpha;
21 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
22 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
23 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
24 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
25 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
26 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
27 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
28 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
29 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
30 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
31 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
32 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
33 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
34 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
35 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
36 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
37 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
38 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
41 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
42 
43 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
44 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
45 import static com.android.internal.policy.DecorView.getNavigationBarRect;
46 
47 import android.annotation.Nullable;
48 import android.app.ActivityManager;
49 import android.app.ActivityThread;
50 import android.content.Context;
51 import android.graphics.Canvas;
52 import android.graphics.Paint;
53 import android.graphics.Rect;
54 import android.hardware.HardwareBuffer;
55 import android.os.IBinder;
56 import android.util.Log;
57 import android.view.SurfaceControl;
58 import android.view.ViewGroup;
59 import android.view.WindowInsets;
60 import android.view.WindowManager;
61 
62 import com.android.internal.R;
63 import com.android.internal.annotations.VisibleForTesting;
64 import com.android.internal.policy.DecorView;
65 
66 /**
67  * Utils class to help draw a snapshot on a surface.
68  * @hide
69  */
70 public class SnapshotDrawerUtils {
71     private static final String TAG = "SnapshotDrawerUtils";
72 
73     /**
74      * Used to check if toolkitSetFrameRateReadOnly flag is enabled
75      *
76      * @hide
77      */
78     private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
79             android.view.flags.Flags.toolkitSetFrameRateReadOnly();
80 
81     /**
82      * When creating the starting window, we use the exact same layout flags such that we end up
83      * with a window with the exact same dimensions etc. However, these flags are not used in layout
84      * and might cause other side effects so we exclude them.
85      */
86     static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
87             | FLAG_NOT_TOUCHABLE
88             | FLAG_NOT_TOUCH_MODAL
89             | FLAG_ALT_FOCUSABLE_IM
90             | FLAG_NOT_FOCUSABLE
91             | FLAG_HARDWARE_ACCELERATED
92             | FLAG_IGNORE_CHEEK_PRESSES
93             | FLAG_LOCAL_FOCUS_MODE
94             | FLAG_SLIPPERY
95             | FLAG_WATCH_OUTSIDE_TOUCH
96             | FLAG_SPLIT_TOUCH
97             | FLAG_SCALED
98             | FLAG_SECURE
99             | FLAG_DIM_BEHIND;
100 
101     /**
102      * The internal object to hold the surface and drawing on it.
103      */
104     @VisibleForTesting
105     public static class SnapshotSurface {
106         private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
107         private final SurfaceControl mRootSurface;
108         private final TaskSnapshot mSnapshot;
109         private final CharSequence mTitle;
110 
111         private final int mSnapshotW;
112         private final int mSnapshotH;
113         private final int mContainerW;
114         private final int mContainerH;
115 
SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, Rect windowBounds, CharSequence title)116         public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot,
117                 Rect windowBounds, CharSequence title) {
118             mRootSurface = rootSurface;
119             mSnapshot = snapshot;
120             mTitle = title;
121             final HardwareBuffer hwBuffer = snapshot.getHardwareBuffer();
122             mSnapshotW = hwBuffer.getWidth();
123             mSnapshotH = hwBuffer.getHeight();
124             mContainerW = windowBounds.width();
125             mContainerH = windowBounds.height();
126         }
127 
drawSnapshot(boolean releaseAfterDraw)128         private void drawSnapshot(boolean releaseAfterDraw) {
129             final Rect letterboxInsets = mSnapshot.getLetterboxInsets();
130             final boolean sizeMismatch = mContainerW != mSnapshotW || mContainerH != mSnapshotH
131                     || letterboxInsets.left != 0 || letterboxInsets.top != 0;
132             Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + sizeMismatch);
133             if (sizeMismatch) {
134                 // The dimensions of the buffer and the window don't match, so attaching the buffer
135                 // will fail. Better create a child window with the exact dimensions and fill the
136                 // parent window with the background color!
137                 drawSizeMismatchSnapshot();
138             } else {
139                 drawSizeMatchSnapshot();
140             }
141 
142             // In case window manager leaks us, make sure we don't retain the snapshot.
143             if (mSnapshot.getHardwareBuffer() != null) {
144                 mSnapshot.getHardwareBuffer().close();
145             }
146             if (releaseAfterDraw) {
147                 mRootSurface.release();
148             }
149         }
150 
drawSizeMatchSnapshot()151         private void drawSizeMatchSnapshot() {
152             mTransaction.setBuffer(mRootSurface, mSnapshot.getHardwareBuffer())
153                     .setColorSpace(mRootSurface, mSnapshot.getColorSpace())
154                     .apply();
155         }
156 
drawSizeMismatchSnapshot()157         private void drawSizeMismatchSnapshot() {
158             final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
159 
160             // Keep a reference to it such that it doesn't get destroyed when finalized.
161             SurfaceControl childSurfaceControl = new SurfaceControl.Builder()
162                     .setName(mTitle + " - task-snapshot-surface")
163                     .setBLASTLayer()
164                     .setFormat(buffer.getFormat())
165                     .setParent(mRootSurface)
166                     .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
167                     .build();
168 
169             final Rect letterboxInsets = mSnapshot.getLetterboxInsets();
170             float offsetX = letterboxInsets.left;
171             float offsetY = letterboxInsets.top;
172             // We can just show the surface here as it will still be hidden as the parent is
173             // still hidden.
174             mTransaction.show(childSurfaceControl);
175 
176             // Align the snapshot with content area.
177             if (offsetX != 0f || offsetY != 0f) {
178                 mTransaction.setPosition(childSurfaceControl,
179                         -offsetX * mContainerW / mSnapshot.getTaskSize().x,
180                         -offsetY * mContainerH / mSnapshot.getTaskSize().y);
181             }
182             // Scale the mismatch dimensions to fill the target frame.
183             final float scaleX = (float) mContainerW / mSnapshotW;
184             final float scaleY = (float) mContainerH / mSnapshotH;
185             mTransaction.setScale(childSurfaceControl, scaleX, scaleY);
186             mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
187             mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
188             mTransaction.apply();
189             childSurfaceControl.release();
190         }
191     }
192 
193     /**
194      * Get or create a TaskDescription from a RunningTaskInfo.
195      */
getOrCreateTaskDescription( ActivityManager.RunningTaskInfo runningTaskInfo)196     public static ActivityManager.TaskDescription getOrCreateTaskDescription(
197             ActivityManager.RunningTaskInfo runningTaskInfo) {
198         final ActivityManager.TaskDescription taskDescription;
199         if (runningTaskInfo.taskDescription != null) {
200             taskDescription = runningTaskInfo.taskDescription;
201         } else {
202             taskDescription = new ActivityManager.TaskDescription();
203             taskDescription.setBackgroundColor(WHITE);
204         }
205         return taskDescription;
206     }
207 
208     /**
209      * Help method to draw the snapshot on a surface.
210      */
drawSnapshotOnSurface(WindowManager.LayoutParams lp, SurfaceControl rootSurface, TaskSnapshot snapshot, Rect windowBounds, boolean releaseAfterDraw)211     public static void drawSnapshotOnSurface(WindowManager.LayoutParams lp,
212             SurfaceControl rootSurface, TaskSnapshot snapshot,
213             Rect windowBounds, boolean releaseAfterDraw) {
214         if (windowBounds.isEmpty()) {
215             Log.e(TAG, "Unable to draw snapshot on an empty windowBounds");
216             return;
217         }
218         final SnapshotSurface drawSurface = new SnapshotSurface(
219                 rootSurface, snapshot, windowBounds, lp.getTitle());
220         drawSurface.drawSnapshot(releaseAfterDraw);
221     }
222 
223     /**
224      * Help method to create a layout parameters for a window.
225      */
createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token)226     public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info,
227             CharSequence title, @WindowManager.LayoutParams.WindowType int windowType,
228             int pixelFormat, IBinder token) {
229         final WindowManager.LayoutParams attrs = info.mainWindowLayoutParams;
230         if (attrs == null) {
231             Log.w(TAG, "unable to create taskSnapshot surface ");
232             return null;
233         }
234         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
235 
236         final int appearance = attrs.insetsFlags.appearance;
237         final int windowFlags = attrs.flags;
238         final int windowPrivateFlags = attrs.privateFlags;
239 
240         layoutParams.packageName = attrs.packageName;
241         layoutParams.windowAnimations = attrs.windowAnimations;
242         layoutParams.dimAmount = attrs.dimAmount;
243         layoutParams.type = windowType;
244         layoutParams.format = pixelFormat;
245         layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
246                 | FLAG_NOT_FOCUSABLE
247                 | FLAG_NOT_TOUCHABLE;
248         layoutParams.privateFlags =
249                 (windowPrivateFlags
250                         & (PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS
251                         | PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED))
252                 // Setting as trusted overlay to let touches pass through. This is safe because this
253                 // window is controlled by the system.
254                 | PRIVATE_FLAG_TRUSTED_OVERLAY;
255         layoutParams.token = token;
256         layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
257         layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
258         layoutParams.insetsFlags.appearance = appearance;
259         layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
260         layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
261         layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
262         layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
263         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
264         if (sToolkitSetFrameRateReadOnlyFlagValue) {
265             layoutParams.setFrameRatePowerSavingsBalanced(false);
266         }
267 
268         layoutParams.setTitle(title);
269         layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
270         return layoutParams;
271     }
272 
273     /**
274      * Helper class to draw the background of the system bars in regions the task snapshot isn't
275      * filling the window.
276      */
277     public static class SystemBarBackgroundPainter {
278         private final Paint mStatusBarPaint = new Paint();
279         private final Paint mNavigationBarPaint = new Paint();
280         private final int mStatusBarColor;
281         private final int mNavigationBarColor;
282         private final int mWindowFlags;
283         private final int mWindowPrivateFlags;
284         private final float mScale;
285         private final @WindowInsets.Type.InsetsType int mRequestedVisibleTypes;
286         private final Rect mSystemBarInsets = new Rect();
287 
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, ActivityManager.TaskDescription taskDescription, float scale, @WindowInsets.Type.InsetsType int requestedVisibleTypes)288         public SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
289                 ActivityManager.TaskDescription taskDescription, float scale,
290                 @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
291             mWindowFlags = windowFlags;
292             mWindowPrivateFlags = windowPrivateFlags;
293             mScale = scale;
294             final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
295             final int semiTransparent = context.getColor(
296                     R.color.system_bar_background_semi_transparent);
297             mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
298                     semiTransparent, taskDescription.getStatusBarColor(), appearance,
299                     APPEARANCE_LIGHT_STATUS_BARS,
300                     taskDescription.getEnsureStatusBarContrastWhenTransparent(),
301                     false /* movesBarColorToScrim */);
302             mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
303                     FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
304                     taskDescription.getNavigationBarColor(), appearance,
305                     APPEARANCE_LIGHT_NAVIGATION_BARS,
306                     taskDescription.getEnsureNavigationBarContrastWhenTransparent()
307                             && context.getResources().getBoolean(
308                             R.bool.config_navBarNeedsScrim),
309                     (windowPrivateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0);
310             mStatusBarPaint.setColor(mStatusBarColor);
311             mNavigationBarPaint.setColor(mNavigationBarColor);
312             mRequestedVisibleTypes = requestedVisibleTypes;
313         }
314 
315         /**
316          * Set system bar insets.
317          */
setInsets(Rect systemBarInsets)318         public void setInsets(Rect systemBarInsets) {
319             mSystemBarInsets.set(systemBarInsets);
320         }
321 
getStatusBarColorViewHeight()322         int getStatusBarColorViewHeight() {
323             final boolean forceBarBackground =
324                     (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
325             if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
326                     mRequestedVisibleTypes, mStatusBarColor, mWindowFlags,
327                     forceBarBackground)) {
328                 return (int) (mSystemBarInsets.top * mScale);
329             } else {
330                 return 0;
331             }
332         }
333 
isNavigationBarColorViewVisible()334         private boolean isNavigationBarColorViewVisible() {
335             final boolean forceBarBackground =
336                     (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
337             return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
338                     mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags,
339                     forceBarBackground);
340         }
341 
342         /**
343          * Draw bar colors to a canvas.
344          */
drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame)345         public void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
346             drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
347             drawNavigationBarBackground(c);
348         }
349 
drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight)350         void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
351                 int statusBarHeight) {
352             if (statusBarHeight > 0 && alpha(mStatusBarColor) != 0
353                     && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
354                 final int rightInset = (int) (mSystemBarInsets.right * mScale);
355                 final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
356                 c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight,
357                         mStatusBarPaint);
358             }
359         }
360 
drawNavigationBarBackground(Canvas c)361         void drawNavigationBarBackground(Canvas c) {
362             final Rect navigationBarRect = new Rect();
363             getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
364                     mScale);
365             final boolean visible = isNavigationBarColorViewVisible();
366             if (visible && alpha(mNavigationBarColor) != 0
367                     && !navigationBarRect.isEmpty()) {
368                 c.drawRect(navigationBarRect, mNavigationBarPaint);
369             }
370         }
371     }
372 }
373