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