/*
 * 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, 0, 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);
            }
        }
    }
}
