| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wm.shell.startingsurface; |
| |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; |
| import static android.graphics.Color.WHITE; |
| import static android.graphics.Color.alpha; |
| import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; |
| import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; |
| import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; |
| import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; |
| import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; |
| import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; |
| import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; |
| import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; |
| import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; |
| import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; |
| import static android.view.WindowManager.LayoutParams.FLAG_SCALED; |
| import static android.view.WindowManager.LayoutParams.FLAG_SECURE; |
| import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; |
| import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; |
| import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; |
| import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; |
| import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; |
| import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; |
| import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; |
| import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; |
| import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; |
| |
| import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; |
| import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; |
| import static com.android.internal.policy.DecorView.getNavigationBarRect; |
| |
| import android.annotation.BinderThread; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.TaskDescription; |
| import android.app.ActivityThread; |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.GraphicBuffer; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.PixelFormat; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.hardware.HardwareBuffer; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.os.Trace; |
| import android.util.MergedConfiguration; |
| import android.util.Slog; |
| import android.view.IWindowSession; |
| import android.view.InputChannel; |
| import android.view.InsetsSourceControl; |
| import android.view.InsetsState; |
| import android.view.SurfaceControl; |
| import android.view.SurfaceSession; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowInsets; |
| import android.view.WindowManager; |
| import android.view.WindowManagerGlobal; |
| import android.window.ClientWindowFrames; |
| import android.window.StartingWindowInfo; |
| import android.window.TaskSnapshot; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.policy.DecorView; |
| import com.android.internal.protolog.common.ProtoLog; |
| import com.android.internal.view.BaseIWindow; |
| import com.android.wm.shell.common.ShellExecutor; |
| import com.android.wm.shell.protolog.ShellProtoLogGroup; |
| |
| import java.lang.ref.WeakReference; |
| |
| /** |
| * This class represents a starting window that shows a snapshot. |
| * |
| * @hide |
| */ |
| public class TaskSnapshotWindow { |
| /** |
| * When creating the starting window, we use the exact same layout flags such that we end up |
| * with a window with the exact same dimensions etc. However, these flags are not used in layout |
| * and might cause other side effects so we exclude them. |
| */ |
| static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE |
| | FLAG_NOT_TOUCHABLE |
| | FLAG_NOT_TOUCH_MODAL |
| | FLAG_ALT_FOCUSABLE_IM |
| | FLAG_NOT_FOCUSABLE |
| | FLAG_HARDWARE_ACCELERATED |
| | FLAG_IGNORE_CHEEK_PRESSES |
| | FLAG_LOCAL_FOCUS_MODE |
| | FLAG_SLIPPERY |
| | FLAG_WATCH_OUTSIDE_TOUCH |
| | FLAG_SPLIT_TOUCH |
| | FLAG_SCALED |
| | FLAG_SECURE; |
| |
| private static final String TAG = StartingWindowController.TAG; |
| private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; |
| |
| private static final long DELAY_REMOVAL_TIME_GENERAL = 100; |
| /** |
| * The max delay time in milliseconds for removing the task snapshot window with IME visible. |
| * Ideally the delay time will be shorter when receiving |
| * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. |
| */ |
| private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; |
| |
| private final Window mWindow; |
| private final Runnable mClearWindowHandler; |
| private final ShellExecutor mSplashScreenExecutor; |
| private final SurfaceControl mSurfaceControl; |
| private final IWindowSession mSession; |
| private final Rect mTaskBounds; |
| private final Rect mFrame = new Rect(); |
| private final Rect mSystemBarInsets = new Rect(); |
| private TaskSnapshot mSnapshot; |
| private final RectF mTmpSnapshotSize = new RectF(); |
| private final RectF mTmpDstFrame = new RectF(); |
| private final CharSequence mTitle; |
| private boolean mHasDrawn; |
| private boolean mSizeMismatch; |
| private final Paint mBackgroundPaint = new Paint(); |
| private final int mActivityType; |
| private final int mStatusBarColor; |
| private final SystemBarBackgroundPainter mSystemBarBackgroundPainter; |
| private final int mOrientationOnCreation; |
| private final SurfaceControl.Transaction mTransaction; |
| private final Matrix mSnapshotMatrix = new Matrix(); |
| private final float[] mTmpFloat9 = new float[9]; |
| private final Runnable mScheduledRunnable = this::removeImmediately; |
| private final boolean mHasImeSurface; |
| |
| static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, |
| TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, |
| @NonNull Runnable clearWindowHandler) { |
| final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; |
| final int taskId = runningTaskInfo.taskId; |
| ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, |
| "create taskSnapshot surface for task: %d", taskId); |
| |
| final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; |
| final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; |
| final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; |
| if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { |
| Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId); |
| return null; |
| } |
| final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); |
| |
| final int appearance = attrs.insetsFlags.appearance; |
| final int windowFlags = attrs.flags; |
| final int windowPrivateFlags = attrs.privateFlags; |
| |
| layoutParams.packageName = mainWindowParams.packageName; |
| layoutParams.windowAnimations = mainWindowParams.windowAnimations; |
| layoutParams.dimAmount = mainWindowParams.dimAmount; |
| layoutParams.type = TYPE_APPLICATION_STARTING; |
| layoutParams.format = snapshot.getHardwareBuffer().getFormat(); |
| layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) |
| | FLAG_NOT_FOCUSABLE |
| | FLAG_NOT_TOUCHABLE; |
| // Setting as trusted overlay to let touches pass through. This is safe because this |
| // window is controlled by the system. |
| layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) |
| | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST; |
| layoutParams.token = appToken; |
| layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; |
| layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; |
| layoutParams.insetsFlags.appearance = appearance; |
| layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior; |
| layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode; |
| layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); |
| layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); |
| layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); |
| |
| layoutParams.setTitle(String.format(TITLE_FORMAT, taskId)); |
| |
| final Point taskSize = snapshot.getTaskSize(); |
| final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); |
| final int orientation = snapshot.getOrientation(); |
| final int activityType = runningTaskInfo.topActivityType; |
| final int displayId = runningTaskInfo.displayId; |
| |
| final IWindowSession session = WindowManagerGlobal.getWindowSession(); |
| final SurfaceControl surfaceControl = new SurfaceControl(); |
| final ClientWindowFrames tmpFrames = new ClientWindowFrames(); |
| |
| final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0]; |
| final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); |
| |
| final TaskDescription taskDescription; |
| if (runningTaskInfo.taskDescription != null) { |
| taskDescription = runningTaskInfo.taskDescription; |
| } else { |
| taskDescription = new TaskDescription(); |
| taskDescription.setBackgroundColor(WHITE); |
| } |
| |
| final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( |
| surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance, |
| windowFlags, windowPrivateFlags, taskBounds, orientation, activityType, |
| topWindowInsetsState, clearWindowHandler, splashScreenExecutor); |
| final Window window = snapshotSurface.mWindow; |
| |
| final InsetsState tmpInsetsState = new InsetsState(); |
| final InputChannel tmpInputChannel = new InputChannel(); |
| final float[] sizeCompatScale = { 1f }; |
| |
| try { |
| Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay"); |
| final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, |
| info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls, |
| new Rect(), sizeCompatScale); |
| Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); |
| if (res < 0) { |
| Slog.w(TAG, "Failed to add snapshot starting window res=" + res); |
| return null; |
| } |
| } catch (RemoteException e) { |
| snapshotSurface.clearWindowSynced(); |
| } |
| window.setOuter(snapshotSurface); |
| try { |
| Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout"); |
| session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, |
| tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, |
| tmpControls, new Bundle()); |
| Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); |
| } catch (RemoteException e) { |
| snapshotSurface.clearWindowSynced(); |
| } |
| |
| final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState); |
| snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets); |
| snapshotSurface.drawSnapshot(); |
| return snapshotSurface; |
| } |
| |
| public 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) { |
| mSplashScreenExecutor = splashScreenExecutor; |
| mSession = WindowManagerGlobal.getWindowSession(); |
| mWindow = new Window(); |
| mWindow.setSession(mSession); |
| mSurfaceControl = surfaceControl; |
| mSnapshot = snapshot; |
| mTitle = title; |
| int backgroundColor = taskDescription.getBackgroundColor(); |
| mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); |
| mTaskBounds = taskBounds; |
| mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, |
| windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState); |
| mStatusBarColor = taskDescription.getStatusBarColor(); |
| mOrientationOnCreation = currentOrientation; |
| mActivityType = activityType; |
| mTransaction = new SurfaceControl.Transaction(); |
| mClearWindowHandler = clearWindowHandler; |
| mHasImeSurface = snapshot.hasImeSurface(); |
| } |
| |
| int getBackgroundColor() { |
| return mBackgroundPaint.getColor(); |
| } |
| |
| boolean hasImeSurface() { |
| return mHasImeSurface; |
| } |
| |
| /** |
| * Ask system bar background painter to draw status bar background. |
| * @hide |
| */ |
| public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { |
| mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, |
| mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); |
| } |
| |
| /** |
| * Ask system bar background painter to draw navigation bar background. |
| * @hide |
| */ |
| public void drawNavigationBarBackground(Canvas c) { |
| mSystemBarBackgroundPainter.drawNavigationBarBackground(c); |
| } |
| |
| void scheduleRemove(boolean deferRemoveForIme) { |
| // Show the latest content as soon as possible for unlocking to home. |
| if (mActivityType == ACTIVITY_TYPE_HOME) { |
| removeImmediately(); |
| return; |
| } |
| mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); |
| final long delayRemovalTime = mHasImeSurface && deferRemoveForIme |
| ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE |
| : DELAY_REMOVAL_TIME_GENERAL; |
| mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); |
| ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, |
| "Defer removing snapshot surface in %d", delayRemovalTime); |
| } |
| |
| void removeImmediately() { |
| mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); |
| try { |
| ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, |
| "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn); |
| mSession.remove(mWindow); |
| } catch (RemoteException e) { |
| // nothing |
| } |
| } |
| |
| /** |
| * Set frame size. |
| * @hide |
| */ |
| public void setFrames(Rect frame, Rect systemBarInsets) { |
| mFrame.set(frame); |
| mSystemBarInsets.set(systemBarInsets); |
| final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); |
| mSizeMismatch = (mFrame.width() != snapshot.getWidth() |
| || mFrame.height() != snapshot.getHeight()); |
| mSystemBarBackgroundPainter.setInsets(systemBarInsets); |
| } |
| |
| static Rect getSystemBarInsets(Rect frame, InsetsState state) { |
| return state.calculateInsets(frame, WindowInsets.Type.systemBars(), |
| false /* ignoreVisibility */).toRect(); |
| } |
| |
| private void drawSnapshot() { |
| ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, |
| "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch); |
| if (mSizeMismatch) { |
| // The dimensions of the buffer and the window don't match, so attaching the buffer |
| // will fail. Better create a child window with the exact dimensions and fill the parent |
| // window with the background color! |
| drawSizeMismatchSnapshot(); |
| } else { |
| drawSizeMatchSnapshot(); |
| } |
| mHasDrawn = true; |
| reportDrawn(); |
| |
| // In case window manager leaks us, make sure we don't retain the snapshot. |
| if (mSnapshot.getHardwareBuffer() != null) { |
| mSnapshot.getHardwareBuffer().close(); |
| } |
| mSnapshot = null; |
| mSurfaceControl.release(); |
| } |
| |
| private void drawSizeMatchSnapshot() { |
| mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer()) |
| .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace()) |
| .apply(); |
| } |
| |
| private void drawSizeMismatchSnapshot() { |
| final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); |
| final SurfaceSession session = new SurfaceSession(); |
| |
| // We consider nearly matched dimensions as there can be rounding errors and the user won't |
| // notice very minute differences from scaling one dimension more than the other |
| final boolean aspectRatioMismatch = Math.abs( |
| ((float) buffer.getWidth() / buffer.getHeight()) |
| - ((float) mFrame.width() / mFrame.height())) > 0.01f; |
| |
| // Keep a reference to it such that it doesn't get destroyed when finalized. |
| SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) |
| .setName(mTitle + " - task-snapshot-surface") |
| .setBLASTLayer() |
| .setFormat(buffer.getFormat()) |
| .setParent(mSurfaceControl) |
| .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") |
| .build(); |
| |
| final Rect frame; |
| // We can just show the surface here as it will still be hidden as the parent is |
| // still hidden. |
| mTransaction.show(childSurfaceControl); |
| if (aspectRatioMismatch) { |
| // Clip off ugly navigation bar. |
| final Rect crop = calculateSnapshotCrop(); |
| frame = calculateSnapshotFrame(crop); |
| mTransaction.setWindowCrop(childSurfaceControl, crop); |
| mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); |
| mTmpSnapshotSize.set(crop); |
| mTmpDstFrame.set(frame); |
| } else { |
| frame = null; |
| mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); |
| mTmpDstFrame.set(mFrame); |
| mTmpDstFrame.offsetTo(0, 0); |
| } |
| |
| // Scale the mismatch dimensions to fill the task bounds |
| mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); |
| mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9); |
| mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); |
| mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); |
| |
| if (aspectRatioMismatch) { |
| GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), |
| PixelFormat.RGBA_8888, |
| GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER |
| | GraphicBuffer.USAGE_SW_WRITE_RARELY); |
| // TODO: Support this on HardwareBuffer |
| final Canvas c = background.lockCanvas(); |
| drawBackgroundAndBars(c, frame); |
| background.unlockCanvasAndPost(c); |
| mTransaction.setBuffer(mSurfaceControl, |
| HardwareBuffer.createFromGraphicBuffer(background)); |
| } |
| mTransaction.apply(); |
| childSurfaceControl.release(); |
| } |
| |
| /** |
| * Calculates the snapshot crop in snapshot coordinate space. |
| * |
| * @return crop rect in snapshot coordinate space. |
| */ |
| public Rect calculateSnapshotCrop() { |
| final Rect rect = new Rect(); |
| final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); |
| rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); |
| final Rect insets = mSnapshot.getContentInsets(); |
| |
| final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; |
| final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; |
| |
| // Let's remove all system decorations except the status bar, but only if the task is at the |
| // very top of the screen. |
| final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0; |
| rect.inset((int) (insets.left * scaleX), |
| isTop ? 0 : (int) (insets.top * scaleY), |
| (int) (insets.right * scaleX), |
| (int) (insets.bottom * scaleY)); |
| return rect; |
| } |
| |
| /** |
| * Calculates the snapshot frame in window coordinate space from crop. |
| * |
| * @param crop rect that is in snapshot coordinate space. |
| */ |
| public Rect calculateSnapshotFrame(Rect crop) { |
| final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); |
| final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; |
| final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; |
| |
| // Rescale the frame from snapshot to window coordinate space |
| final Rect frame = new Rect(0, 0, |
| (int) (crop.width() / scaleX + 0.5f), |
| (int) (crop.height() / scaleY + 0.5f) |
| ); |
| |
| // However, we also need to make space for the navigation bar on the left side. |
| frame.offset(mSystemBarInsets.left, 0); |
| return frame; |
| } |
| |
| /** |
| * Draw status bar and navigation bar background. |
| * @hide |
| */ |
| public void drawBackgroundAndBars(Canvas c, Rect frame) { |
| final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); |
| final boolean fillHorizontally = c.getWidth() > frame.right; |
| final boolean fillVertically = c.getHeight() > frame.bottom; |
| if (fillHorizontally) { |
| c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0, |
| c.getWidth(), fillVertically |
| ? frame.bottom |
| : c.getHeight(), |
| mBackgroundPaint); |
| } |
| if (fillVertically) { |
| c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint); |
| } |
| mSystemBarBackgroundPainter.drawDecors(c, frame); |
| } |
| |
| /** |
| * Clear window from drawer, must be post on main executor. |
| */ |
| private void clearWindowSynced() { |
| mSplashScreenExecutor.executeDelayed(mClearWindowHandler, 0); |
| } |
| |
| private void reportDrawn() { |
| try { |
| mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE); |
| } catch (RemoteException e) { |
| clearWindowSynced(); |
| } |
| } |
| |
| static class Window extends BaseIWindow { |
| private WeakReference<TaskSnapshotWindow> mOuter; |
| |
| public void setOuter(TaskSnapshotWindow outer) { |
| mOuter = new WeakReference<>(outer); |
| } |
| |
| @BinderThread |
| @Override |
| public void resized(ClientWindowFrames frames, boolean reportDraw, |
| MergedConfiguration mergedConfiguration, InsetsState insetsState, |
| boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, |
| int resizeMode) { |
| final TaskSnapshotWindow snapshot = mOuter.get(); |
| if (snapshot == null) { |
| return; |
| } |
| snapshot.mSplashScreenExecutor.execute(() -> { |
| if (mergedConfiguration != null |
| && snapshot.mOrientationOnCreation |
| != mergedConfiguration.getMergedConfiguration().orientation) { |
| // The orientation of the screen is changing. We better remove the snapshot |
| // ASAP as we are going to wait on the new window in any case to unfreeze |
| // the screen, and the starting window is not needed anymore. |
| snapshot.clearWindowSynced(); |
| } else if (reportDraw) { |
| if (snapshot.mHasDrawn) { |
| snapshot.reportDrawn(); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Helper class to draw the background of the system bars in regions the task snapshot isn't |
| * filling the window. |
| */ |
| static class SystemBarBackgroundPainter { |
| private final Paint mStatusBarPaint = new Paint(); |
| private final Paint mNavigationBarPaint = new Paint(); |
| private final int mStatusBarColor; |
| private final int mNavigationBarColor; |
| private final int mWindowFlags; |
| private final int mWindowPrivateFlags; |
| private final float mScale; |
| private final InsetsState mInsetsState; |
| private final Rect mSystemBarInsets = new Rect(); |
| |
| SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, |
| TaskDescription taskDescription, float scale, InsetsState insetsState) { |
| mWindowFlags = windowFlags; |
| mWindowPrivateFlags = windowPrivateFlags; |
| mScale = scale; |
| final Context context = ActivityThread.currentActivityThread().getSystemUiContext(); |
| final int semiTransparent = context.getColor( |
| R.color.system_bar_background_semi_transparent); |
| mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS, |
| semiTransparent, taskDescription.getStatusBarColor(), appearance, |
| APPEARANCE_LIGHT_STATUS_BARS, |
| taskDescription.getEnsureStatusBarContrastWhenTransparent()); |
| mNavigationBarColor = DecorView.calculateBarColor(windowFlags, |
| FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, |
| taskDescription.getNavigationBarColor(), appearance, |
| APPEARANCE_LIGHT_NAVIGATION_BARS, |
| taskDescription.getEnsureNavigationBarContrastWhenTransparent() |
| && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim)); |
| mStatusBarPaint.setColor(mStatusBarColor); |
| mNavigationBarPaint.setColor(mNavigationBarColor); |
| mInsetsState = insetsState; |
| } |
| |
| void setInsets(Rect systemBarInsets) { |
| mSystemBarInsets.set(systemBarInsets); |
| } |
| |
| int getStatusBarColorViewHeight() { |
| final boolean forceBarBackground = |
| (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; |
| if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( |
| mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { |
| return (int) (mSystemBarInsets.top * mScale); |
| } else { |
| return 0; |
| } |
| } |
| |
| private boolean isNavigationBarColorViewVisible() { |
| final boolean forceBarBackground = |
| (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; |
| return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( |
| mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground); |
| } |
| |
| void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) { |
| drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight()); |
| drawNavigationBarBackground(c); |
| } |
| |
| void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, |
| int statusBarHeight) { |
| if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 |
| && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { |
| final int rightInset = (int) (mSystemBarInsets.right * mScale); |
| final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; |
| c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); |
| } |
| } |
| |
| @VisibleForTesting |
| void drawNavigationBarBackground(Canvas c) { |
| final Rect navigationBarRect = new Rect(); |
| getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, |
| mScale); |
| final boolean visible = isNavigationBarColorViewVisible(); |
| if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { |
| c.drawRect(navigationBarRect, mNavigationBarPaint); |
| } |
| } |
| } |
| } |