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