• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.startingsurface;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.graphics.Color.WHITE;
21 import static android.graphics.Color.alpha;
22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
23 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
24 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
25 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
26 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
27 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
28 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
29 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
30 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
31 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
32 import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
33 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
34 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
35 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
36 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
37 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
38 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
41 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
42 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
43 
44 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
45 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
46 import static com.android.internal.policy.DecorView.getNavigationBarRect;
47 
48 import android.annotation.BinderThread;
49 import android.annotation.NonNull;
50 import android.annotation.Nullable;
51 import android.app.ActivityManager;
52 import android.app.ActivityManager.TaskDescription;
53 import android.app.ActivityThread;
54 import android.content.Context;
55 import android.graphics.Canvas;
56 import android.graphics.Color;
57 import android.graphics.GraphicBuffer;
58 import android.graphics.Matrix;
59 import android.graphics.Paint;
60 import android.graphics.PixelFormat;
61 import android.graphics.Point;
62 import android.graphics.Rect;
63 import android.graphics.RectF;
64 import android.hardware.HardwareBuffer;
65 import android.os.IBinder;
66 import android.os.RemoteException;
67 import android.os.SystemClock;
68 import android.os.Trace;
69 import android.util.MergedConfiguration;
70 import android.util.Slog;
71 import android.view.IWindowSession;
72 import android.view.InputChannel;
73 import android.view.InsetsSourceControl;
74 import android.view.InsetsState;
75 import android.view.SurfaceControl;
76 import android.view.SurfaceSession;
77 import android.view.View;
78 import android.view.ViewGroup;
79 import android.view.WindowInsets;
80 import android.view.WindowManager;
81 import android.view.WindowManagerGlobal;
82 import android.window.ClientWindowFrames;
83 import android.window.StartingWindowInfo;
84 import android.window.TaskSnapshot;
85 
86 import com.android.internal.R;
87 import com.android.internal.annotations.VisibleForTesting;
88 import com.android.internal.policy.DecorView;
89 import com.android.internal.view.BaseIWindow;
90 import com.android.wm.shell.common.ShellExecutor;
91 
92 /**
93  * This class represents a starting window that shows a snapshot.
94  *
95  * @hide
96  */
97 public class TaskSnapshotWindow {
98     /**
99      * When creating the starting window, we use the exact same layout flags such that we end up
100      * with a window with the exact same dimensions etc. However, these flags are not used in layout
101      * and might cause other side effects so we exclude them.
102      */
103     static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
104             | FLAG_NOT_TOUCHABLE
105             | FLAG_NOT_TOUCH_MODAL
106             | FLAG_ALT_FOCUSABLE_IM
107             | FLAG_NOT_FOCUSABLE
108             | FLAG_HARDWARE_ACCELERATED
109             | FLAG_IGNORE_CHEEK_PRESSES
110             | FLAG_LOCAL_FOCUS_MODE
111             | FLAG_SLIPPERY
112             | FLAG_WATCH_OUTSIDE_TOUCH
113             | FLAG_SPLIT_TOUCH
114             | FLAG_SCALED
115             | FLAG_SECURE;
116 
117     private static final String TAG = StartingSurfaceDrawer.TAG;
118     private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_TASK_SNAPSHOT;
119     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
120 
121     private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
122     private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350;
123 
124     //tmp vars for unused relayout params
125     private static final Point TMP_SURFACE_SIZE = new Point();
126 
127     private final Window mWindow;
128     private final Runnable mClearWindowHandler;
129     private final long mDelayRemovalTime;
130     private final ShellExecutor mSplashScreenExecutor;
131     private final SurfaceControl mSurfaceControl;
132     private final IWindowSession mSession;
133     private final Rect mTaskBounds;
134     private final Rect mFrame = new Rect();
135     private final Rect mSystemBarInsets = new Rect();
136     private TaskSnapshot mSnapshot;
137     private final RectF mTmpSnapshotSize = new RectF();
138     private final RectF mTmpDstFrame = new RectF();
139     private final CharSequence mTitle;
140     private boolean mHasDrawn;
141     private long mShownTime;
142     private boolean mSizeMismatch;
143     private final Paint mBackgroundPaint = new Paint();
144     private final int mActivityType;
145     private final int mStatusBarColor;
146     private final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
147     private final int mOrientationOnCreation;
148     private final SurfaceControl.Transaction mTransaction;
149     private final Matrix mSnapshotMatrix = new Matrix();
150     private final float[] mTmpFloat9 = new float[9];
151 
create(StartingWindowInfo info, IBinder appToken, TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, @NonNull Runnable clearWindowHandler)152     static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
153             TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
154             @NonNull Runnable clearWindowHandler) {
155         final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
156         final int taskId = runningTaskInfo.taskId;
157         if (DEBUG) {
158             Slog.d(TAG, "create taskSnapshot surface for task: " + taskId);
159         }
160 
161         final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
162         final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
163         final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
164         if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
165             Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId);
166             return null;
167         }
168         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
169 
170         final int appearance = attrs.insetsFlags.appearance;
171         final int windowFlags = attrs.flags;
172         final int windowPrivateFlags = attrs.privateFlags;
173 
174         layoutParams.packageName = mainWindowParams.packageName;
175         layoutParams.windowAnimations = mainWindowParams.windowAnimations;
176         layoutParams.dimAmount = mainWindowParams.dimAmount;
177         layoutParams.type = TYPE_APPLICATION_STARTING;
178         layoutParams.format = snapshot.getHardwareBuffer().getFormat();
179         layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
180                 | FLAG_NOT_FOCUSABLE
181                 | FLAG_NOT_TOUCHABLE;
182         // Setting as trusted overlay to let touches pass through. This is safe because this
183         // window is controlled by the system.
184         layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
185                 | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
186         layoutParams.token = appToken;
187         layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
188         layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
189         layoutParams.insetsFlags.appearance = appearance;
190         layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
191         layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
192         layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
193         layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
194         layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
195 
196         layoutParams.setTitle(String.format(TITLE_FORMAT, taskId));
197 
198         final Point taskSize = snapshot.getTaskSize();
199         final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
200         final int orientation = snapshot.getOrientation();
201         final int activityType = runningTaskInfo.topActivityType;
202         final int displayId = runningTaskInfo.displayId;
203 
204         final IWindowSession session = WindowManagerGlobal.getWindowSession();
205         final SurfaceControl surfaceControl = new SurfaceControl();
206         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
207 
208         final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
209         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
210 
211         final TaskDescription taskDescription;
212         if (runningTaskInfo.taskDescription != null) {
213             taskDescription = runningTaskInfo.taskDescription;
214         } else {
215             taskDescription = new TaskDescription();
216             taskDescription.setBackgroundColor(WHITE);
217         }
218 
219         final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE
220                 : DELAY_REMOVAL_TIME_GENERAL;
221 
222         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
223                 surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
224                 windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
225                 delayRemovalTime, topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
226         final Window window = snapshotSurface.mWindow;
227 
228         final InsetsState mTmpInsetsState = new InsetsState();
229         final InputChannel tmpInputChannel = new InputChannel();
230 
231         try {
232             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
233             final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
234                     mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
235             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
236             if (res < 0) {
237                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
238                 return null;
239             }
240         } catch (RemoteException e) {
241             snapshotSurface.clearWindowSynced();
242         }
243         window.setOuter(snapshotSurface);
244         try {
245             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
246             session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
247                     tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
248                     mTempControls, TMP_SURFACE_SIZE);
249             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
250         } catch (RemoteException e) {
251             snapshotSurface.clearWindowSynced();
252         }
253 
254         final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState);
255         snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets);
256         snapshotSurface.drawSnapshot();
257         return snapshotSurface;
258     }
259 
TaskSnapshotWindow(SurfaceControl surfaceControl, TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, int currentOrientation, int activityType, long delayRemovalTime, InsetsState topWindowInsetsState, Runnable clearWindowHandler, ShellExecutor splashScreenExecutor)260     public TaskSnapshotWindow(SurfaceControl surfaceControl,
261             TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
262             int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
263             int currentOrientation, int activityType, long delayRemovalTime,
264             InsetsState topWindowInsetsState, Runnable clearWindowHandler,
265             ShellExecutor splashScreenExecutor) {
266         mSplashScreenExecutor = splashScreenExecutor;
267         mSession = WindowManagerGlobal.getWindowSession();
268         mWindow = new Window();
269         mWindow.setSession(mSession);
270         mSurfaceControl = surfaceControl;
271         mSnapshot = snapshot;
272         mTitle = title;
273         int backgroundColor = taskDescription.getBackgroundColor();
274         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
275         mTaskBounds = taskBounds;
276         mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
277                 windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState);
278         mStatusBarColor = taskDescription.getStatusBarColor();
279         mOrientationOnCreation = currentOrientation;
280         mActivityType = activityType;
281         mDelayRemovalTime = delayRemovalTime;
282         mTransaction = new SurfaceControl.Transaction();
283         mClearWindowHandler = clearWindowHandler;
284     }
285 
getBackgroundColor()286     int getBackgroundColor() {
287         return mBackgroundPaint.getColor();
288     }
289 
290     /**
291      * Ask system bar background painter to draw status bar background.
292      * @hide
293      */
drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame)294     public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
295         mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
296                 mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
297     }
298 
299     /**
300      * Ask system bar background painter to draw navigation bar background.
301      * @hide
302      */
drawNavigationBarBackground(Canvas c)303     public void drawNavigationBarBackground(Canvas c) {
304         mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
305     }
306 
remove()307     void remove() {
308         final long now = SystemClock.uptimeMillis();
309         if ((now - mShownTime < mDelayRemovalTime)
310                 // Show the latest content as soon as possible for unlocking to home.
311                 && mActivityType != ACTIVITY_TYPE_HOME) {
312             final long delayTime = mShownTime + mDelayRemovalTime - now;
313             mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime);
314             if (DEBUG) {
315                 Slog.d(TAG, "Defer removing snapshot surface in " + delayTime);
316             }
317             return;
318         }
319         try {
320             if (DEBUG) {
321                 Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn);
322             }
323             mSession.remove(mWindow);
324         } catch (RemoteException e) {
325             // nothing
326         }
327     }
328 
329     /**
330      * Set frame size.
331      * @hide
332      */
setFrames(Rect frame, Rect systemBarInsets)333     public void setFrames(Rect frame, Rect systemBarInsets) {
334         mFrame.set(frame);
335         mSystemBarInsets.set(systemBarInsets);
336         final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
337         mSizeMismatch = (mFrame.width() != snapshot.getWidth()
338                 || mFrame.height() != snapshot.getHeight());
339         mSystemBarBackgroundPainter.setInsets(systemBarInsets);
340     }
341 
getSystemBarInsets(Rect frame, InsetsState state)342     static Rect getSystemBarInsets(Rect frame, InsetsState state) {
343         return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
344                 false /* ignoreVisibility */);
345     }
346 
drawSnapshot()347     private void drawSnapshot() {
348         if (DEBUG) {
349             Slog.d(TAG, "Drawing snapshot surface sizeMismatch= " + mSizeMismatch);
350         }
351         if (mSizeMismatch) {
352             // The dimensions of the buffer and the window don't match, so attaching the buffer
353             // will fail. Better create a child window with the exact dimensions and fill the parent
354             // window with the background color!
355             drawSizeMismatchSnapshot();
356         } else {
357             drawSizeMatchSnapshot();
358         }
359         mShownTime = SystemClock.uptimeMillis();
360         mHasDrawn = true;
361         reportDrawn();
362 
363         // In case window manager leaks us, make sure we don't retain the snapshot.
364         mSnapshot = null;
365     }
366 
drawSizeMatchSnapshot()367     private void drawSizeMatchSnapshot() {
368         GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
369                 mSnapshot.getHardwareBuffer());
370         mTransaction.setBuffer(mSurfaceControl, graphicBuffer)
371                 .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
372                 .apply();
373     }
374 
drawSizeMismatchSnapshot()375     private void drawSizeMismatchSnapshot() {
376         final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
377         final SurfaceSession session = new SurfaceSession();
378 
379         // We consider nearly matched dimensions as there can be rounding errors and the user won't
380         // notice very minute differences from scaling one dimension more than the other
381         final boolean aspectRatioMismatch = Math.abs(
382                 ((float) buffer.getWidth() / buffer.getHeight())
383                 - ((float) mFrame.width() / mFrame.height())) > 0.01f;
384 
385         // Keep a reference to it such that it doesn't get destroyed when finalized.
386         SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
387                 .setName(mTitle + " - task-snapshot-surface")
388                 .setBLASTLayer()
389                 .setFormat(buffer.getFormat())
390                 .setParent(mSurfaceControl)
391                 .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
392                 .build();
393 
394         final Rect frame;
395         // We can just show the surface here as it will still be hidden as the parent is
396         // still hidden.
397         mTransaction.show(childSurfaceControl);
398         if (aspectRatioMismatch) {
399             // Clip off ugly navigation bar.
400             final Rect crop = calculateSnapshotCrop();
401             frame = calculateSnapshotFrame(crop);
402             mTransaction.setWindowCrop(childSurfaceControl, crop);
403             mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
404             mTmpSnapshotSize.set(crop);
405             mTmpDstFrame.set(frame);
406         } else {
407             frame = null;
408             mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
409             mTmpDstFrame.set(mFrame);
410             mTmpDstFrame.offsetTo(0, 0);
411         }
412 
413         // Scale the mismatch dimensions to fill the task bounds
414         mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
415         mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
416         GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
417                 mSnapshot.getHardwareBuffer());
418         mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
419         mTransaction.setBuffer(childSurfaceControl, graphicBuffer);
420 
421         if (aspectRatioMismatch) {
422             GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
423                     PixelFormat.RGBA_8888,
424                     GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
425                             | GraphicBuffer.USAGE_SW_WRITE_RARELY);
426             final Canvas c = background.lockCanvas();
427             drawBackgroundAndBars(c, frame);
428             background.unlockCanvasAndPost(c);
429             mTransaction.setBuffer(mSurfaceControl, background);
430         }
431         mTransaction.apply();
432     }
433 
434     /**
435      * Calculates the snapshot crop in snapshot coordinate space.
436      *
437      * @return crop rect in snapshot coordinate space.
438      */
calculateSnapshotCrop()439     public Rect calculateSnapshotCrop() {
440         final Rect rect = new Rect();
441         final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
442         rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
443         final Rect insets = mSnapshot.getContentInsets();
444 
445         final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
446         final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
447 
448         // Let's remove all system decorations except the status bar, but only if the task is at the
449         // very top of the screen.
450         final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
451         rect.inset((int) (insets.left * scaleX),
452                 isTop ? 0 : (int) (insets.top * scaleY),
453                 (int) (insets.right * scaleX),
454                 (int) (insets.bottom * scaleY));
455         return rect;
456     }
457 
458     /**
459      * Calculates the snapshot frame in window coordinate space from crop.
460      *
461      * @param crop rect that is in snapshot coordinate space.
462      */
calculateSnapshotFrame(Rect crop)463     public Rect calculateSnapshotFrame(Rect crop) {
464         final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
465         final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
466         final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
467 
468         // Rescale the frame from snapshot to window coordinate space
469         final Rect frame = new Rect(0, 0,
470                 (int) (crop.width() / scaleX + 0.5f),
471                 (int) (crop.height() / scaleY + 0.5f)
472         );
473 
474         // However, we also need to make space for the navigation bar on the left side.
475         frame.offset(mSystemBarInsets.left, 0);
476         return frame;
477     }
478 
479     /**
480      * Draw status bar and navigation bar background.
481      * @hide
482      */
drawBackgroundAndBars(Canvas c, Rect frame)483     public void drawBackgroundAndBars(Canvas c, Rect frame) {
484         final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
485         final boolean fillHorizontally = c.getWidth() > frame.right;
486         final boolean fillVertically = c.getHeight() > frame.bottom;
487         if (fillHorizontally) {
488             c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
489                     c.getWidth(), fillVertically
490                             ? frame.bottom
491                             : c.getHeight(),
492                     mBackgroundPaint);
493         }
494         if (fillVertically) {
495             c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
496         }
497         mSystemBarBackgroundPainter.drawDecors(c, frame);
498     }
499 
500     /**
501      * Clear window from drawer, must be post on main executor.
502      */
clearWindowSynced()503     private void clearWindowSynced() {
504         mSplashScreenExecutor.executeDelayed(mClearWindowHandler, 0);
505     }
506 
reportDrawn()507     private void reportDrawn() {
508         try {
509             mSession.finishDrawing(mWindow, null /* postDrawTransaction */);
510         } catch (RemoteException e) {
511             clearWindowSynced();
512         }
513     }
514 
515     @BinderThread
516     static class Window extends BaseIWindow {
517         private TaskSnapshotWindow mOuter;
518 
setOuter(TaskSnapshotWindow outer)519         public void setOuter(TaskSnapshotWindow outer) {
520             mOuter = outer;
521         }
522 
523         @Override
resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId)524         public void resized(ClientWindowFrames frames, boolean reportDraw,
525                 MergedConfiguration mergedConfiguration, boolean forceLayout,
526                 boolean alwaysConsumeSystemBars, int displayId) {
527             if (mOuter != null) {
528                 mOuter.mSplashScreenExecutor.execute(() -> {
529                     if (mergedConfiguration != null
530                             && mOuter.mOrientationOnCreation
531                             != mergedConfiguration.getMergedConfiguration().orientation) {
532                         // The orientation of the screen is changing. We better remove the snapshot
533                         // ASAP as we are going to wait on the new window in any case to unfreeze
534                         // the screen, and the starting window is not needed anymore.
535                         mOuter.clearWindowSynced();
536                     } else if (reportDraw) {
537                         if (mOuter.mHasDrawn) {
538                             mOuter.reportDrawn();
539                         }
540                     }
541                 });
542             }
543         }
544     }
545 
546     /**
547      * Helper class to draw the background of the system bars in regions the task snapshot isn't
548      * filling the window.
549      */
550     static class SystemBarBackgroundPainter {
551         private final Paint mStatusBarPaint = new Paint();
552         private final Paint mNavigationBarPaint = new Paint();
553         private final int mStatusBarColor;
554         private final int mNavigationBarColor;
555         private final int mWindowFlags;
556         private final int mWindowPrivateFlags;
557         private final float mScale;
558         private final InsetsState mInsetsState;
559         private final Rect mSystemBarInsets = new Rect();
560 
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, TaskDescription taskDescription, float scale, InsetsState insetsState)561         SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
562                 TaskDescription taskDescription, float scale, InsetsState insetsState) {
563             mWindowFlags = windowFlags;
564             mWindowPrivateFlags = windowPrivateFlags;
565             mScale = scale;
566             final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
567             final int semiTransparent = context.getColor(
568                     R.color.system_bar_background_semi_transparent);
569             mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
570                     semiTransparent, taskDescription.getStatusBarColor(), appearance,
571                     APPEARANCE_LIGHT_STATUS_BARS,
572                     taskDescription.getEnsureStatusBarContrastWhenTransparent());
573             mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
574                     FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
575                     taskDescription.getNavigationBarColor(), appearance,
576                     APPEARANCE_LIGHT_NAVIGATION_BARS,
577                     taskDescription.getEnsureNavigationBarContrastWhenTransparent()
578                             && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
579             mStatusBarPaint.setColor(mStatusBarColor);
580             mNavigationBarPaint.setColor(mNavigationBarColor);
581             mInsetsState = insetsState;
582         }
583 
setInsets(Rect systemBarInsets)584         void setInsets(Rect systemBarInsets) {
585             mSystemBarInsets.set(systemBarInsets);
586         }
587 
getStatusBarColorViewHeight()588         int getStatusBarColorViewHeight() {
589             final boolean forceBarBackground =
590                     (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
591             if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
592                     mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
593                 return (int) (mSystemBarInsets.top * mScale);
594             } else {
595                 return 0;
596             }
597         }
598 
isNavigationBarColorViewVisible()599         private boolean isNavigationBarColorViewVisible() {
600             final boolean forceBarBackground =
601                     (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
602             return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
603                     mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
604         }
605 
drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame)606         void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
607             drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
608             drawNavigationBarBackground(c);
609         }
610 
drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight)611         void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
612                 int statusBarHeight) {
613             if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
614                     && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
615                 final int rightInset = (int) (mSystemBarInsets.right * mScale);
616                 final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
617                 c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
618             }
619         }
620 
621         @VisibleForTesting
drawNavigationBarBackground(Canvas c)622         void drawNavigationBarBackground(Canvas c) {
623             final Rect navigationBarRect = new Rect();
624             getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
625                     mScale);
626             final boolean visible = isNavigationBarColorViewVisible();
627             if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
628                 c.drawRect(navigationBarRect, mNavigationBarPaint);
629             }
630         }
631     }
632 }
633