/*
 * Copyright (C) 2018 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.server.wm;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE;
import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
import static android.view.InsetsFrameProvider.SOURCE_FRAME;
import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
import static android.window.DesktopExperienceFlags.ENABLE_RESTRICT_FREEFORM_HIDDEN_SYSTEM_BARS_TO_FILLING_TASKS;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;

import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.DropInputMode;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayInfo;
import android.view.InsetsFlags;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.window.ClientWindowFrames;
import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.policy.ForceShowNavBarSettingsObserver;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.function.TriFunction;
import com.android.internal.view.AppearanceRegion;
import com.android.internal.widget.PointerLocationView;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.wm.shell.Flags;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * The policy that provides the basic behaviors and states of a display to show UI.
 */
public class DisplayPolicy {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayPolicy" : TAG_WM;

    // The panic gesture may become active only after the keyguard is dismissed and the immersive
    // app shows again. If that doesn't happen for 30s we drop the gesture.
    private static final long PANIC_GESTURE_EXPIRATION = 30000;

    // Controls navigation bar opacity depending on which workspace root tasks are currently
    // visible.
    // Nav bar is always opaque when either the freeform root task or docked root task is visible.
    private static final int NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED = 0;
    // Nav bar is always translucent when the freeform rootTask is visible, otherwise always opaque.
    private static final int NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE = 1;
    // Nav bar is never forced opaque.
    private static final int NAV_BAR_FORCE_TRANSPARENT = 2;

    /** Don't apply window animation (see {@link #selectAnimation}). */
    static final int ANIMATION_NONE = -1;
    /** Use the transit animation in style resource (see {@link #selectAnimation}). */
    static final int ANIMATION_STYLEABLE = 0;

    private static final int SHOW_TYPES_FOR_SWIPE = Type.statusBars() | Type.navigationBars();
    private static final int SHOW_TYPES_FOR_PANIC = Type.navigationBars();

    private static final int INSETS_OVERRIDE_INDEX_INVALID = -1;

    // TODO(b/266197298): Remove this by a more general protocol from the insets providers.
    private static final boolean USE_CACHED_INSETS_FOR_DISPLAY_SWITCH =
            SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", true);

    private final WindowManagerService mService;
    private final Context mContext;
    private final Context mUiContext;
    private final DisplayContent mDisplayContent;
    private final Object mLock;
    private final Handler mHandler;

    private Resources mCurrentUserResources;

    private final boolean mCarDockEnablesAccelerometer;
    private final boolean mDeskDockEnablesAccelerometer;
    private final AccessibilityManager mAccessibilityManager;

    private final Object mServiceAcquireLock = new Object();
    private long mPanicTime;
    private final long mPanicThresholdMs;
    private StatusBarManagerInternal mStatusBarManagerInternal;
    private UiModeManagerInternal mUiModeManagerInternal;

    @Px
    private int mLeftGestureInset;
    @Px
    private int mRightGestureInset;

    private boolean mCanSystemBarsBeShownByUser;

    /**
     * Let remote insets controller control system bars regardless of other settings.
     */
    private boolean mRemoteInsetsControllerControlsSystemBars;

    @Nullable
    StatusBarManagerInternal getStatusBarManagerInternal() {
        synchronized (mServiceAcquireLock) {
            if (mStatusBarManagerInternal == null) {
                mStatusBarManagerInternal =
                        LocalServices.getService(StatusBarManagerInternal.class);
            }
            return mStatusBarManagerInternal;
        }
    }

    UiModeManagerInternal getUiModeManagerInternal() {
        synchronized (mServiceAcquireLock) {
            if (mUiModeManagerInternal == null) {
                mUiModeManagerInternal = LocalServices.getService(UiModeManagerInternal.class);
            }
            return mUiModeManagerInternal;
        }
    }

    // Will be null in client transient mode.
    private SystemGesturesPointerEventListener mSystemGestures;

    final DecorInsets mDecorInsets;
    /** Currently it can only be non-null when physical display switch happens. */
    private DecorInsets.Cache mCachedDecorInsets;

    private volatile int mLidState = LID_ABSENT;
    private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
    private volatile boolean mHdmiPlugged;

    private volatile boolean mHasStatusBar;
    private volatile boolean mHasNavigationBar;
    // Can the navigation bar ever move to the side?
    private volatile boolean mNavigationBarCanMove;
    private volatile boolean mNavigationBarAlwaysShowOnSideGesture;

    // Written by vr manager thread, only read in this class.
    private volatile boolean mPersistentVrModeEnabled;

    private volatile int mDisplayUiMode =
            Configuration.UI_MODE_TYPE_UNDEFINED | Configuration.UI_MODE_NIGHT_UNDEFINED;

    private volatile boolean mAwake;
    private volatile boolean mScreenOnEarly;
    private volatile boolean mScreenOnFully;
    private volatile ScreenOnListener mScreenOnListener;

    private volatile boolean mKeyguardDrawComplete;
    private volatile boolean mWindowManagerDrawComplete;

    private boolean mImmersiveConfirmationWindowExists;

    private WindowState mStatusBar = null;
    private volatile WindowState mNotificationShade;
    private WindowState mNavigationBar = null;
    private boolean mHasBottomNavigationBar = true;

    private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();

    /** Apps which are controlling the appearance of system bars */
    private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>();

    /** Apps which are relaunching and were controlling the appearance of system bars */
    private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>();

    private boolean mIsFreeformWindowOverlappingWithNavBar;

    /**
     * Insets types requested to be shown transiently which won't affect the insets visibility
     * received by clients windows, so they don't need to update their layout when this changes.
     */
    private @InsetsType int mShowingTransientInsetsTypes;

    /**
     * Insets types requested to be shown permanently which will affect the insets visibility
     * received by clients windows. They might need to update their layout when this changes.
     */
    private @InsetsType int mShowingPermanentInsetsTypes;

    /**
     * Insets types requested to be hidden permanently which will affect the insets visibility
     * received by clients windows. They might need to update their layout when this changes.
     */
    private @InsetsType int mHidingPermanentInsetsTypes;

    /** Mapping from a caller to a SystemBarVisibilityOverride. */
    private final ArrayMap<IBinder, SystemBarVisibilityOverride> mSystemBarVisibilityOverrideMap =
            new ArrayMap<>();

    private boolean mImeInsetsConsumed;

    private boolean mIsImmersiveMode;

    // The windows we were told about in focusChanged.
    private WindowState mFocusedWindow;
    private WindowState mLastFocusedWindow;

    private WindowState mSystemUiControllingWindow;

    // Candidate window to determine the color of navigation bar. The window needs to be top
    // fullscreen-app windows or dim layers that are intersecting with the window frame of
    // navigation bar.
    private WindowState mNavBarColorWindowCandidate;

    // Candidate window to determine opacity and background of translucent navigation bar.
    // The window frame must intersect the frame of navigation bar.
    private WindowState mNavBarBackgroundWindowCandidate;

    /**
     * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
     * which appearance.
     */
    private final ArrayList<AppearanceRegion> mStatusBarAppearanceRegionList = new ArrayList<>();

    /**
     * Windows to determine opacity and background of translucent status bar. The window needs to be
     * opaque
     */
    private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();

    /**
     * A collection of {@link LetterboxDetails} of all visible activities to be sent to SysUI in
     * order to determine status bar appearance
     */
    private final ArrayList<LetterboxDetails> mLetterboxDetails = new ArrayList<>();

    private String mFocusedPackageName;
    private int mLastDisableFlags;
    private int mLastAppearance;
    private int mLastBehavior;
    private int mLastRequestedVisibleTypes = Type.defaultVisible();
    private AppearanceRegion[] mLastStatusBarAppearanceRegions;
    private LetterboxDetails[] mLastLetterboxDetails;

    /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
    private final Rect mStatusBarColorCheckedBounds = new Rect();

    /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */
    private final Rect mStatusBarBackgroundCheckedBounds = new Rect();

    // What we last reported to input dispatcher about whether the focused window is fullscreen.
    private boolean mLastFocusIsFullscreen = false;

    // If nonzero, a panic gesture was performed at that time in uptime millis and is still pending.
    private long mPendingPanicGestureUptime;

    private static final Rect sTmpRect = new Rect();
    private static final Rect sTmpRect2 = new Rect();
    private static final Rect sTmpDisplayCutoutSafe = new Rect();
    private static final ClientWindowFrames sTmpClientFrames = new ClientWindowFrames();

    private final WindowLayout mWindowLayout = new WindowLayout();

    private WindowState mTopFullscreenOpaqueWindowState;
    private boolean mTopIsFullscreen;
    private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;

    /**
     * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
     * side, the window with the highest z-order wins.
     */
    private WindowState mLeftGestureHost;
    private WindowState mTopGestureHost;
    private WindowState mRightGestureHost;
    private WindowState mBottomGestureHost;

    private boolean mShowingDream;
    private boolean mLastShowingDream;
    private boolean mDreamingLockscreen;
    private boolean mAllowLockscreenWhenOn;

    private PointerLocationView mPointerLocationView;

    private RefreshRatePolicy mRefreshRatePolicy;

    /**
     * If true, attach the navigation bar to the current transition app.
     * The value is read from config_attachNavBarToAppDuringTransition and could be overlaid by RRO
     * when the navigation bar mode is changed.
     */
    private boolean mShouldAttachNavBarToAppDuringTransition;

    // -------- PolicyHandler --------
    private static final int MSG_ENABLE_POINTER_LOCATION = 4;
    private static final int MSG_DISABLE_POINTER_LOCATION = 5;

    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;

    private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;

    private final ForceShowNavBarSettingsObserver mForceShowNavBarSettingsObserver;
    private boolean mForceShowNavigationBarEnabled;

    private class PolicyHandler extends Handler {

        PolicyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ENABLE_POINTER_LOCATION:
                    enablePointerLocation();
                    break;
                case MSG_DISABLE_POINTER_LOCATION:
                    disablePointerLocation();
                    break;
            }
        }
    }

    DisplayPolicy(WindowManagerService service, DisplayContent displayContent) {
        mService = service;
        mContext = displayContent.isDefaultDisplay ? service.mContext
                : service.mContext.createDisplayContext(displayContent.getDisplay());
        mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.getUiContext()
                : service.mAtmService.mSystemThread
                        .getSystemUiContext(displayContent.getDisplayId());
        mDisplayContent = displayContent;
        mDecorInsets = new DecorInsets(displayContent);
        mLock = service.getWindowManagerLock();

        final int displayId = displayContent.getDisplayId();

        final Resources r = mContext.getResources();
        mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
        mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
        mCanSystemBarsBeShownByUser = !r.getBoolean(
                R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean(
                R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction);
        mPanicThresholdMs = r.getInteger(R.integer.config_immersive_mode_confirmation_panic);

        mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
                Context.ACCESSIBILITY_SERVICE);
        if (!displayContent.isDefaultDisplay) {
            mAwake = true;
            mScreenOnEarly = true;
            mScreenOnFully = true;
        }

        final Looper looper = UiThread.getHandler().getLooper();
        mHandler = new PolicyHandler(looper);
        // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context.
        if (!CLIENT_TRANSIENT) {
            SystemGesturesPointerEventListener.Callbacks gesturesPointerEventCallbacks =
                    new SystemGesturesPointerEventListener.Callbacks() {

                private static final long MOUSE_GESTURE_DELAY_MS = 500;

                private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
                private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
                private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
                private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;

                private Insets getControllableInsets(WindowState win) {
                    if (win == null) {
                        return Insets.NONE;
                    }
                    final InsetsSourceProvider provider = win.getControllableInsetProvider();
                    if (provider == null) {
                        return Insets.NONE;
                    }
                    final Rect bounds = win.getBounds();
                    return provider.getSource().calculateInsets(bounds, bounds,
                            true /* ignoreVisibility */);
                }

                @Override
                public void onSwipeFromTop() {
                    synchronized (mLock) {
                        requestTransientBars(mTopGestureHost,
                                getControllableInsets(mTopGestureHost).top > 0);
                    }
                }

                @Override
                public void onSwipeFromBottom() {
                    synchronized (mLock) {
                        requestTransientBars(mBottomGestureHost,
                                getControllableInsets(mBottomGestureHost).bottom > 0);
                    }
                }

                private boolean allowsSideSwipe(Region excludedRegion) {
                    return mNavigationBarAlwaysShowOnSideGesture
                            && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
                }

                @Override
                public void onSwipeFromRight() {
                    final Region excludedRegion = Region.obtain();
                    synchronized (mLock) {
                        mDisplayContent.calculateSystemGestureExclusion(
                                excludedRegion, null /* outUnrestricted */);
                        final boolean hasWindow =
                                getControllableInsets(mRightGestureHost).right > 0;
                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
                            requestTransientBars(mRightGestureHost, hasWindow);
                        }
                    }
                    excludedRegion.recycle();
                }

                @Override
                public void onSwipeFromLeft() {
                    final Region excludedRegion = Region.obtain();
                    synchronized (mLock) {
                        mDisplayContent.calculateSystemGestureExclusion(
                                excludedRegion, null /* outUnrestricted */);
                        final boolean hasWindow =
                                getControllableInsets(mLeftGestureHost).left > 0;
                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
                            requestTransientBars(mLeftGestureHost, hasWindow);
                        }
                    }
                    excludedRegion.recycle();
                }

                @Override
                public void onFling(int duration) {
                    if (mService.mPowerManagerInternal != null) {
                        mService.mPowerManagerInternal.setPowerBoost(
                                Boost.INTERACTION, duration);
                    }
                }

                @Override
                public void onDebug() {
                    // no-op
                }

                private WindowOrientationListener getOrientationListener() {
                    final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
                    return rotation != null ? rotation.getOrientationListener() : null;
                }

                @Override
                public void onDown() {
                    final WindowOrientationListener listener = getOrientationListener();
                    if (listener != null) {
                        listener.onTouchStart();
                    }
                }

                @Override
                public void onUpOrCancel() {
                    final WindowOrientationListener listener = getOrientationListener();
                    if (listener != null) {
                        listener.onTouchEnd();
                    }
                }

                @Override
                public void onMouseHoverAtLeft() {
                    mHandler.removeCallbacks(mOnSwipeFromLeft);
                    mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
                }

                @Override
                public void onMouseHoverAtTop() {
                    mHandler.removeCallbacks(mOnSwipeFromTop);
                    mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
                }

                @Override
                public void onMouseHoverAtRight() {
                    mHandler.removeCallbacks(mOnSwipeFromRight);
                    mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
                }

                @Override
                public void onMouseHoverAtBottom() {
                    mHandler.removeCallbacks(mOnSwipeFromBottom);
                    mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
                }

                @Override
                public void onMouseLeaveFromLeft() {
                    mHandler.removeCallbacks(mOnSwipeFromLeft);
                }

                @Override
                public void onMouseLeaveFromTop() {
                    mHandler.removeCallbacks(mOnSwipeFromTop);
                }

                @Override
                public void onMouseLeaveFromRight() {
                    mHandler.removeCallbacks(mOnSwipeFromRight);
                }

                @Override
                public void onMouseLeaveFromBottom() {
                    mHandler.removeCallbacks(mOnSwipeFromBottom);
                }
            };
            mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
                    gesturesPointerEventCallbacks);
            displayContent.registerPointerEventListener(mSystemGestures);
        }
        mAppTransitionListener = new WindowManagerInternal.AppTransitionListener(displayId) {

            private Runnable mAppTransitionPending = () -> {
                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                if (statusBar != null) {
                    statusBar.appTransitionPending(displayId);
                }
            };

            private Runnable mAppTransitionCancelled = () -> {
                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                if (statusBar != null) {
                    statusBar.appTransitionCancelled(displayId);
                }
            };

            private Runnable mAppTransitionFinished = () -> {
                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                if (statusBar != null) {
                    statusBar.appTransitionFinished(displayId);
                }
            };

            @Override
            public void onAppTransitionPendingLocked() {
                mHandler.post(mAppTransitionPending);
            }

            @Override
            public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
                    long statusBarAnimationDuration) {
                mHandler.post(() -> {
                    StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                    if (statusBar != null) {
                        statusBar.appTransitionStarting(mContext.getDisplayId(),
                                statusBarAnimationStartTime, statusBarAnimationDuration);
                    }
                });
                return 0;
            }

            @Override
            public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
                mHandler.post(mAppTransitionCancelled);
            }

            @Override
            public void onAppTransitionFinishedLocked(IBinder token) {
                mHandler.post(mAppTransitionFinished);
            }
        };
        displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);

        if (mDisplayContent.isDefaultDisplay) {
            mHasStatusBar = true;
            mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);

            // Allow a system property to override this. Used by the emulator.
            // See also hasNavigationBar().
            String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
            if ("1".equals(navBarOverride)) {
                mHasNavigationBar = false;
            } else if ("0".equals(navBarOverride)) {
                mHasNavigationBar = true;
            }
        } else {
            mHasStatusBar = false;
            mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
        }

        mRefreshRatePolicy = new RefreshRatePolicy(mService,
                mDisplayContent.getDisplayInfo(),
                mService.mHighRefreshRateDenylist);

        mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler,
                BackgroundThread.getHandler(),
                mContext, () -> {
            synchronized (mLock) {
                onConfigurationChanged();
                if (!CLIENT_TRANSIENT) {
                    mSystemGestures.onConfigurationChanged();
                }
                mDisplayContent.updateSystemGestureExclusion();
            }
        });
        mHandler.post(mGestureNavigationSettingsObserver::register);

        mForceShowNavBarSettingsObserver = new ForceShowNavBarSettingsObserver(
                mHandler, mContext);
        mForceShowNavBarSettingsObserver.setOnChangeRunnable(this::updateForceShowNavBarSettings);
        mForceShowNavigationBarEnabled = mForceShowNavBarSettingsObserver.isEnabled();
        mHandler.post(mForceShowNavBarSettingsObserver::register);
    }

    private void updateForceShowNavBarSettings() {
        synchronized (mLock) {
            mForceShowNavigationBarEnabled =
                    mForceShowNavBarSettingsObserver.isEnabled();
            updateSystemBarAttributes();
        }
    }

    void systemReady() {
        if (!CLIENT_TRANSIENT) {
            mSystemGestures.systemReady();
        }
        if (mService.mPointerLocationEnabled) {
            setPointerLocationEnabled(true);
        }
    }

    private int getDisplayId() {
        return mDisplayContent.getDisplayId();
    }

    public void setHdmiPlugged(boolean plugged) {
        setHdmiPlugged(plugged, false /* force */);
    }

    public void setHdmiPlugged(boolean plugged, boolean force) {
        if (force || mHdmiPlugged != plugged) {
            mHdmiPlugged = plugged;
            mService.updateRotation(true /* alwaysSendConfiguration */, true /* forceRelayout */);
            final Intent intent = new Intent(ACTION_HDMI_PLUGGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged);
            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
        }
    }

    boolean isHdmiPlugged() {
        return mHdmiPlugged;
    }

    boolean isCarDockEnablesAccelerometer() {
        return mCarDockEnablesAccelerometer;
    }

    boolean isDeskDockEnablesAccelerometer() {
        return mDeskDockEnablesAccelerometer;
    }

    public void setPersistentVrModeEnabled(boolean persistentVrModeEnabled) {
        mPersistentVrModeEnabled = persistentVrModeEnabled;
    }

    public boolean isPersistentVrModeEnabled() {
        return mPersistentVrModeEnabled;
    }

    public void setDockMode(int dockMode) {
        mDockMode = dockMode;
    }

    public int getDockMode() {
        return mDockMode;
    }

    public boolean hasNavigationBar() {
        return mHasNavigationBar;
    }

    void updateHasNavigationBarIfNeeded() {
        if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
            Slog.e(TAG, "mHasNavigationBar shouldn't be updated when the flag is off.");
        }

        if (mDisplayContent.isDefaultDisplay) {
            return;
        }

        mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
    }

    public boolean hasStatusBar() {
        return mHasStatusBar;
    }

    boolean hasSideGestures() {
        return mHasNavigationBar && (mLeftGestureInset > 0 || mRightGestureInset > 0);
    }

    public boolean navigationBarCanMove() {
        return mNavigationBarCanMove;
    }

    public void setLidState(int lidState) {
        mLidState = lidState;
    }

    public int getLidState() {
        return mLidState;
    }

    private void onDisplaySwitchFinished() {
        mDisplayContent.mDisplayUpdater.onDisplaySwitching(false);
    }

    public void setAwake(boolean awake) {
        synchronized (mLock) {
            if (awake == mAwake) {
                return;
            }
            mAwake = awake;
            if (!mDisplayContent.isDefaultDisplay) {
                return;
            }
            if (awake) {
                mService.mAtmService.mVisibleDozeUiProcess = null;
                mService.mAtmService.mActivityStateUpdater.setVisibleDozeUiProcessAsync(null);
            } else if (mScreenOnFully && mNotificationShade != null) {
                // Screen is still on, so it may be showing an always-on UI.
                final WindowProcessController visibleDozeUiProcess =
                        mNotificationShade.getProcess();
                mService.mAtmService.mVisibleDozeUiProcess = visibleDozeUiProcess;
                mService.mAtmService.mActivityStateUpdater.setVisibleDozeUiProcessAsync(
                        visibleDozeUiProcess);
            }
            mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                    mAwake /* waiting */);
            if (!awake) {
                onDisplaySwitchFinished();
                // In case PhoneWindowManager's startedGoingToSleep is called after screenTurnedOff
                // (the source caller is in order but the methods run on different threads) and
                // updateScreenOffSleepToken was skipped by mIsGoingToSleepDefaultDisplay. Then
                // acquire sleep token if screen is off.
                if (!mScreenOnEarly && !mScreenOnFully && !mDisplayContent.isSleeping()) {
                    Slog.w(TAG, "Late acquire sleep token for " + mDisplayContent);
                    mDisplayContent.addSleepToken(DisplayContent.DISPLAY_OFF_SLEEP_TOKEN_TAG);
                }
            }
        }
    }

    public boolean isAwake() {
        return mAwake;
    }

    public boolean isScreenOnEarly() {
        return mScreenOnEarly;
    }

    public boolean isScreenOnFully() {
        return mScreenOnFully;
    }

    public boolean isKeyguardDrawComplete() {
        return mKeyguardDrawComplete;
    }

    public boolean isWindowManagerDrawComplete() {
        return mWindowManagerDrawComplete;
    }

    public boolean isForceShowNavigationBarEnabled() {
        return mForceShowNavigationBarEnabled;
    }

    public ScreenOnListener getScreenOnListener() {
        return mScreenOnListener;
    }


    boolean isRemoteInsetsControllerControllingSystemBars() {
        return mRemoteInsetsControllerControlsSystemBars;
    }

    @VisibleForTesting
    void setRemoteInsetsControllerControlsSystemBars(
            boolean remoteInsetsControllerControlsSystemBars) {
        mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars;
    }

    /** Prepares to turn on screen. The given listener is used to notify that it is ready. */
    public void screenTurningOn(ScreenOnListener screenOnListener) {
        WindowProcessController visibleDozeUiProcess = null;
        synchronized (mLock) {
            mDisplayContent.removeSleepToken(DisplayContent.DISPLAY_OFF_SLEEP_TOKEN_TAG);
            mScreenOnEarly = true;
            mScreenOnFully = false;
            mKeyguardDrawComplete = false;
            mWindowManagerDrawComplete = false;
            mScreenOnListener = screenOnListener;
            if (!mAwake && mNotificationShade != null) {
                // The screen is turned on without awake state. It is usually triggered by an
                // adding notification, so make the UI process have a higher priority.
                visibleDozeUiProcess = mNotificationShade.getProcess();
                mService.mAtmService.mVisibleDozeUiProcess = visibleDozeUiProcess;
            }
        }
        // The method calls AM directly, so invoke it outside the lock.
        if (visibleDozeUiProcess != null) {
            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurnedOnWhileDozing");
            mService.mAtmService.setProcessAnimatingWhileDozing(visibleDozeUiProcess);
        }
    }

    /** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */
    public void screenTurnedOn() {
        onDisplaySwitchFinished();
    }

    /** It is called after {@link #screenTurningOn}. This runs on PowerManager's thread. */
    public void screenTurnedOff(boolean acquireSleepToken) {
        synchronized (mLock) {
            if (acquireSleepToken) {
                mDisplayContent.addSleepToken(DisplayContent.DISPLAY_OFF_SLEEP_TOKEN_TAG);
            }
            mScreenOnEarly = false;
            mScreenOnFully = false;
            mKeyguardDrawComplete = false;
            mWindowManagerDrawComplete = false;
            mScreenOnListener = null;
            mService.mAtmService.mVisibleDozeUiProcess = null;
        }
    }

    /** Return false if we are not awake yet or we have already informed of this event. */
    public boolean finishKeyguardDrawn() {
        synchronized (mLock) {
            if (!mScreenOnEarly || mKeyguardDrawComplete) {
                return false;
            }

            mKeyguardDrawComplete = true;
            mWindowManagerDrawComplete = false;
        }
        return true;
    }

    /** Return false if screen is not turned on or we did already handle this case earlier. */
    public boolean finishWindowsDrawn() {
        synchronized (mLock) {
            if (!mScreenOnEarly || mWindowManagerDrawComplete) {
                return false;
            }

            mWindowManagerDrawComplete = true;
        }
        return true;
    }

    /** Return false if it is not ready to turn on. */
    public boolean finishScreenTurningOn() {
        synchronized (mLock) {
            ProtoLog.d(WM_DEBUG_SCREEN_ON,
                            "finishScreenTurningOn: mAwake=%b, mScreenOnEarly=%b, "
                                    + "mScreenOnFully=%b, mKeyguardDrawComplete=%b, "
                                    + "mWindowManagerDrawComplete=%b",
                            mAwake, mScreenOnEarly, mScreenOnFully, mKeyguardDrawComplete,
                            mWindowManagerDrawComplete);

            if (mScreenOnFully || !mScreenOnEarly || !mWindowManagerDrawComplete
                    || (mAwake && !mKeyguardDrawComplete)) {
                return false;
            }

            ProtoLog.i(WM_DEBUG_SCREEN_ON, "Finished screen turning on...");
            mScreenOnListener = null;
            mScreenOnFully = true;
        }
        return true;
    }

    /**
     * Sanitize the layout parameters coming from a client.  Allows the policy
     * to do things like ensure that windows of a specific type can't take
     * input focus.
     *
     * @param attrs The window layout parameters to be modified.  These values
     * are modified in-place.
     */
    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_SYSTEM_OVERLAY:
            case TYPE_SECURE_SYSTEM_OVERLAY:
                // These types of windows can't receive input events.
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
                break;
            case TYPE_WALLPAPER:
                // Dreams and wallpapers don't have an app window token and can thus not be
                // letterboxed. Hence always let them extend under the cutout.
                attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
                break;

            case TYPE_TOAST:
                // While apps should use the dedicated toast APIs to add such windows
                // it possible legacy apps to add the window directly. Therefore, we
                // make windows added directly by the app behave as a toast as much
                // as possible in terms of timeout and animation.
                if (attrs.hideTimeoutMilliseconds < 0
                        || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
                    attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
                }
                // Accessibility users may need longer timeout duration. This api compares
                // original timeout with user's preference and return longer one. It returns
                // original timeout if there's no preference.
                attrs.hideTimeoutMilliseconds = mAccessibilityManager.getRecommendedTimeoutMillis(
                        (int) attrs.hideTimeoutMilliseconds,
                        AccessibilityManager.FLAG_CONTENT_TEXT);
                // Toasts can't be clickable
                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                // Do not allow untrusted toast to customize animation.
                if (!win.mSession.mCanAddInternalSystemWindow) {
                    attrs.windowAnimations = R.style.Animation_Toast;
                }
                break;

            case TYPE_BASE_APPLICATION:
                if (attrs.isFullscreen() && win.mActivityRecord != null
                        && win.mActivityRecord.fillsParent()
                        && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0) {
                    if (attrs.getFitInsetsTypes() != 0) {
                        // A non-translucent main app window isn't allowed to fit insets,
                        // as it would create a hole on the display!
                        throw new IllegalArgumentException("Illegal attributes: Main window of "
                                + win.mActivityRecord.getName() + " that isn't translucent trying"
                                + " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
                                        attrs.getFitInsetsTypes()));
                    }
                }
                break;
        }
        if ((attrs.insetsFlags.appearance & APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS) != 0) {
            attrs.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
        }

        if (LayoutParams.isSystemAlertWindowType(attrs.type)) {
            float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
            if (attrs.alpha > maxOpacity
                    && (attrs.flags & FLAG_NOT_TOUCHABLE) != 0
                    && !win.isTrustedOverlay()) {
                // The app is posting a SAW with the intent of letting touches pass through, but
                // they are going to be deemed untrusted and will be blocked. Try to honor the
                // intent of letting touches pass through at the cost of 0.2 opacity for app
                // compatibility reasons. More details on b/218777508.
                Slog.w(TAG, String.format(
                        "App %s has a system alert window (type = %d) with FLAG_NOT_TOUCHABLE and "
                                + "LayoutParams.alpha = %.2f > %.2f, setting alpha to %.2f to "
                                + "let touches pass through (if this is isn't desirable, remove "
                                + "flag FLAG_NOT_TOUCHABLE).",
                        attrs.packageName, attrs.type, attrs.alpha, maxOpacity, maxOpacity));
                attrs.alpha = maxOpacity;
                win.mWinAnimator.mAlpha = maxOpacity;
            }
        }

        if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
            attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
        }
    }

    /**
     * Add additional policy if needed to ensure the window or its children should not receive any
     * input.
     */
    public void setDropInputModePolicy(WindowState win, LayoutParams attrs) {
        if (attrs.type == TYPE_TOAST
                && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
            // Toasts should not receive input. These windows should not have any children, so
            // force this hierarchy of windows to drop all input.
            mService.mTransactionFactory.get()
                    .setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply();
        }
    }

    /**
     * Check if a window can be added to the system.
     *
     * <p>Currently enforces that these window types are singletons per display:
     * <ul>
     * <li>{@link WindowManager.LayoutParams#TYPE_STATUS_BAR}</li>
     * <li>{@link WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}</li>
     * <li>{@link WindowManager.LayoutParams#TYPE_NAVIGATION_BAR}</li>
     * </ul>
     *
     * @param attrs Information about the window to be added.
     *
     * @return If ok, WindowManagerImpl.ADD_OKAY.  If too many singletons,
     * WindowManagerImpl.ADD_MULTIPLE_SINGLETON
     */
    int validateAddingWindowLw(WindowManager.LayoutParams attrs, int callingPid, int callingUid) {
        if ((attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
            mContext.enforcePermission(
                    android.Manifest.permission.INTERNAL_SYSTEM_WINDOW, callingPid, callingUid,
                    "DisplayPolicy");
        }
        if ((attrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0) {
            mContext.enforcePermission(
                    android.Manifest.permission.MANAGE_ACTIVITY_TASKS, callingPid, callingUid,
                    "DisplayPolicy");
        }

        final String systemUiPermission =
                mService.isCallerVirtualDeviceOwner(mDisplayContent.getDisplayId(), callingUid)
                        && mDisplayContent.isTrusted()
                        // Virtual device owners can add system windows on their trusted displays.
                        ? android.Manifest.permission.CREATE_VIRTUAL_DEVICE
                        : android.Manifest.permission.STATUS_BAR_SERVICE;

        final int windowType = attrs.type;

        if (assertDisplaySingletonPolicy(windowType, systemUiPermission, callingPid, callingUid)) {
            return ADD_MULTIPLE_SINGLETON;
        }

        switch (windowType) {
            case TYPE_NAVIGATION_BAR_PANEL:
            case TYPE_STATUS_BAR_ADDITIONAL:
            case TYPE_STATUS_BAR_SUB_PANEL:
            case TYPE_VOICE_INTERACTION_STARTING:
                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                        "DisplayPolicy");
                break;
            case TYPE_STATUS_BAR_PANEL:
                return WindowManagerGlobal.ADD_INVALID_TYPE;
        }

        if (attrs.providedInsets != null) {
            // Recents component is allowed to add inset types.
            if (!mService.mAtmService.isCallerRecents(callingUid)) {
                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                        "DisplayPolicy");
            }
        }
        return ADD_OKAY;
    }

    /**
     * Returns {@code true} if the given {@code windowType} violates the display singleton policy,
     * which means there has been an existing window with {@code windowType} attached to
     * this display.
     *
     * @param windowType the window type to check
     * @param callingPid the caller PID
     * @param callingUid the caller UID
     * @return {@code true} if there has been an existing window with {@code windowType} attached
     * to this display
     */
    boolean assertDisplaySingletonPolicy(
            @LayoutParams.WindowType int windowType,
            @NonNull String systemUiPermission,
            int callingPid,
            int callingUid) {
        if (!isSingletonPerDisplay(windowType)) {
            return false;
        }
        mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                "DisplayPolicy");

        switch (windowType) {
            case TYPE_STATUS_BAR:
                if (mStatusBar != null && mStatusBar.isAlive()) {
                    return true;
                }
                break;
            case TYPE_NOTIFICATION_SHADE:
                if (mNotificationShade != null && mNotificationShade.isAlive()) {
                    return true;
                }
                break;
            case TYPE_NAVIGATION_BAR:
                if (mNavigationBar != null && mNavigationBar.isAlive()) {
                    return true;
                }
                break;
        }
        return false;
    }

    private boolean isSingletonPerDisplay(@LayoutParams.WindowType int windowType) {
        return windowType == TYPE_STATUS_BAR
                || windowType == TYPE_NOTIFICATION_SHADE
                || windowType == TYPE_NAVIGATION_BAR;
    }

    /**
     * Called when a window is being added to the system.  Must not throw an exception.
     *
     * @param win The window being added.
     * @param attrs Information about the window to be added.
     */
    void addWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_NOTIFICATION_SHADE:
                mNotificationShade = win;
                break;
            case TYPE_STATUS_BAR:
                mStatusBar = win;
                break;
            case TYPE_NAVIGATION_BAR:
                mNavigationBar = win;
                break;
        }
        if ((attrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
            mImmersiveConfirmationWindowExists = true;
        }
        if (attrs.providedInsets != null) {
            for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
                final InsetsFrameProvider provider = attrs.providedInsets[i];
                // The index of the provider and corresponding insets types cannot change at
                // runtime as ensured in WMS. Make use of the index in the provider directly
                // to access the latest provided size at runtime.
                final TriFunction<DisplayFrames, WindowState, Rect, Integer> frameProvider =
                        getFrameProvider(win, i, INSETS_OVERRIDE_INDEX_INVALID);
                final InsetsFrameProvider.InsetsSizeOverride[] overrides =
                        provider.getInsetsSizeOverrides();
                final SparseArray<TriFunction<DisplayFrames, WindowState, Rect, Integer>>
                        overrideProviders;
                if (overrides != null) {
                    overrideProviders = new SparseArray<>();
                    for (int j = overrides.length - 1; j >= 0; j--) {
                        overrideProviders.put(
                                overrides[j].getWindowType(), getFrameProvider(win, i, j));
                    }
                } else {
                    overrideProviders = null;
                }
                final InsetsSourceProvider sourceProvider = mDisplayContent
                        .getInsetsStateController().getOrCreateSourceProvider(provider.getId(),
                                provider.getType());
                sourceProvider.getSource().setFlags(provider.getFlags());
                sourceProvider.setWindow(win, frameProvider, overrideProviders);
                mInsetsSourceWindowsExceptIme.add(win);
            }
        }
    }

    @NonNull
    private static TriFunction<DisplayFrames, WindowState, Rect, Integer> getFrameProvider(
            WindowState win, int index, int overrideIndex) {
        return (displayFrames, windowState, inOutFrame) -> {
            final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
            final InsetsFrameProvider ifp = lp.providedInsets[index];
            final Rect displayFrame = displayFrames.mUnrestricted;
            final Rect safe = displayFrames.mDisplayCutoutSafe;
            boolean extendByCutout = false;
            switch (ifp.getSource()) {
                case SOURCE_DISPLAY:
                    inOutFrame.set(displayFrame);
                    break;
                case SOURCE_CONTAINER_BOUNDS:
                    inOutFrame.set(windowState.getBounds());
                    break;
                case SOURCE_FRAME:
                    extendByCutout =
                            (lp.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
                    break;
                case SOURCE_ARBITRARY_RECTANGLE:
                    inOutFrame.set(ifp.getArbitraryRectangle());
                    break;
            }
            final Insets insetsSize = overrideIndex == INSETS_OVERRIDE_INDEX_INVALID
                    ? ifp.getInsetsSize()
                    : ifp.getInsetsSizeOverrides()[overrideIndex].getInsetsSize();

            if (ifp.getMinimalInsetsSizeInDisplayCutoutSafe() != null) {
                sTmpRect2.set(inOutFrame);
            }
            calculateInsetsFrame(inOutFrame, insetsSize);

            if (extendByCutout && insetsSize != null) {
                WindowLayout.extendFrameByCutout(safe, displayFrame, inOutFrame, sTmpRect);
            }

            if (ifp.getMinimalInsetsSizeInDisplayCutoutSafe() != null) {
                // The insets is at least with the given size within the display cutout safe area.
                // Calculate the smallest size.
                calculateInsetsFrame(sTmpRect2, ifp.getMinimalInsetsSizeInDisplayCutoutSafe());
                WindowLayout.extendFrameByCutout(safe, displayFrame, sTmpRect2, sTmpRect);
                // If it's larger than previous calculation, use it.
                if (sTmpRect2.contains(inOutFrame)) {
                    inOutFrame.set(sTmpRect2);
                }
            }
            return ifp.getFlags();
        };
    }

    /**
     * Calculate the insets frame given the insets size and the source frame.
     * @param inOutFrame the source frame.
     * @param insetsSize the insets size. Only the first non-zero value will be taken.
     */
    static void calculateInsetsFrame(Rect inOutFrame, Insets insetsSize) {
        if (insetsSize == null) {
            return;
        }
        // Only one side of the provider shall be applied. Check in the order of left - top -
        // right - bottom, only the first non-zero value will be applied.
        if (insetsSize.left != 0) {
            inOutFrame.right = inOutFrame.left + insetsSize.left;
        } else if (insetsSize.top != 0) {
            inOutFrame.bottom = inOutFrame.top + insetsSize.top;
        } else if (insetsSize.right != 0) {
            inOutFrame.left = inOutFrame.right - insetsSize.right;
        } else if (insetsSize.bottom != 0) {
            inOutFrame.top = inOutFrame.bottom - insetsSize.bottom;
        } else {
            inOutFrame.setEmpty();
        }
    }

    @NonNull
    TriFunction<DisplayFrames, WindowState, Rect, Integer> getImeSourceFrameProvider() {
        return (displayFrames, windowState, inOutFrame) -> {
            inOutFrame.inset(windowState.mGivenContentInsets);
            return 0;
        };
    }

    /**
     * Called when a window is being removed from a window manager.  Must not
     * throw an exception -- clean up as much as possible.
     *
     * @param win The window being removed.
     */
    void removeWindowLw(WindowState win) {
        if (mStatusBar == win) {
            mStatusBar = null;
        } else if (mNavigationBar == win) {
            mNavigationBar = null;
        } else if (mNotificationShade == win) {
            mNotificationShade = null;
        }
        if (mLastFocusedWindow == win) {
            mLastFocusedWindow = null;
        }

        if (win.hasInsetsSourceProvider()) {
            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
            final InsetsStateController controller = mDisplayContent.getInsetsStateController();
            for (int index = providers.size() - 1; index >= 0; index--) {
                final InsetsSourceProvider provider = providers.valueAt(index);
                provider.setWindow(null /* win */, null /* frameProvider */,
                        null /* overrideFrameProviders */);
                controller.removeSourceProvider(provider.getSource().getId());
            }
        }
        mInsetsSourceWindowsExceptIme.remove(win);
        if ((win.mAttrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
            mImmersiveConfirmationWindowExists = false;
        }
    }

    WindowState getStatusBar() {
        return mStatusBar;
    }

    WindowState getNotificationShade() {
        return mNotificationShade;
    }

    WindowState getNavigationBar() {
        return mNavigationBar;
    }

    boolean isImmersiveMode() {
        return mIsImmersiveMode;
    }

    /**
     * Control the animation to run when a window's state changes.  Return a positive number to
     * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
     * style resource defining the animation, or {@link #ANIMATION_NONE} for no animation.
     *
     * @param win The window that is changing.
     * @param transit What is happening to the window:
     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_ENTER},
     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_EXIT},
     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_SHOW}, or
     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_HIDE}.
     *
     * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none.
     */
    int selectAnimation(WindowState win, int transit) {
        ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);

        if (transit == TRANSIT_PREVIEW_DONE) {
            if (win.hasAppShownWindows()) {
                if (win.isActivityTypeHome()) {
                    // Dismiss the starting window as soon as possible to avoid the crossfade out
                    // with old content because home is easier to have different UI states.
                    return ANIMATION_NONE;
                }
                ProtoLog.i(WM_DEBUG_ANIM, "**** STARTING EXIT");
                return R.anim.app_starting_exit;
            }
        }

        return ANIMATION_STYLEABLE;
    }

    /**
     * Computes the frames of display (its logical size, rotation and cutout should already be set)
     * used to layout window. This method only changes the given display frames, insets state and
     * some temporal states, but doesn't change the window frames used to show on screen.
     */
    void simulateLayoutDisplay(@NonNull DisplayFrames displayFrames) {
        sTmpClientFrames.attachedFrame = null;
        for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
            final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
            mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
                    displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
                    displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
                    UNSPECIFIED_LENGTH, win.getRequestedVisibleTypes(), win.mGlobalScale,
                    sTmpClientFrames);
            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
            final InsetsState state = displayFrames.mInsetsState;
            for (int index = providers.size() - 1; index >= 0; index--) {
                state.addSource(providers.valueAt(index).createSimulatedSource(
                        displayFrames, sTmpClientFrames.frame));
            }
        }
    }

    void onDisplayInfoChanged(DisplayInfo info) {
        if (!CLIENT_TRANSIENT) {
            mSystemGestures.onDisplayInfoChanged(info);
        }
    }

    /**
     * Called for each window attached to the window manager as layout is proceeding. The
     * implementation of this function must take care of setting the window's frame, either here or
     * in finishLayout().
     *
     * @param win The window being positioned.
     * @param attached For sub-windows, the window it is attached to; this
     *                 window will already have had layoutWindow() called on it
     *                 so you can use its Rect.  Otherwise null.
     * @param displayFrames The display frames.
     */
    void layoutWindowLw(@NonNull WindowState win, @Nullable WindowState attached,
            @NonNull DisplayFrames displayFrames) {
        if (win.skipLayout()) {
            return;
        }

        // This window might be in the simulated environment.
        // We invoke this to get the proper DisplayFrames.
        displayFrames = win.getDisplayFrames(displayFrames);

        final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation);
        sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null;

        // If this window has different LayoutParams for rotations, we cannot trust its requested
        // size. Because it might have not sent its requested size for the new rotation.
        final boolean trustedSize = attrs == win.mAttrs;
        final int requestedWidth = trustedSize ? win.mRequestedWidth : UNSPECIFIED_LENGTH;
        final int requestedHeight = trustedSize ? win.mRequestedHeight : UNSPECIFIED_LENGTH;

        mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
                win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
                win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);

        win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
    }

    WindowState getTopFullscreenOpaqueWindow() {
        return mTopFullscreenOpaqueWindowState;
    }

    boolean isTopLayoutFullscreen() {
        return mTopIsFullscreen;
    }

    /**
     * Called following layout of all windows before each window has policy applied.
     */
    public void beginPostLayoutPolicyLw() {
        mLeftGestureHost = null;
        mTopGestureHost = null;
        mRightGestureHost = null;
        mBottomGestureHost = null;
        mTopFullscreenOpaqueWindowState = null;
        mNavBarColorWindowCandidate = null;
        mNavBarBackgroundWindowCandidate = null;
        mStatusBarAppearanceRegionList.clear();
        mLetterboxDetails.clear();
        mStatusBarBackgroundWindows.clear();
        mStatusBarColorCheckedBounds.setEmpty();
        mStatusBarBackgroundCheckedBounds.setEmpty();
        mSystemBarColorApps.clear();

        mAllowLockscreenWhenOn = false;
        mShowingDream = false;
        mIsFreeformWindowOverlappingWithNavBar = false;
        mShowingTransientInsetsTypes = 0;
        mImeInsetsConsumed = false;
    }

    /**
     * Called following layout of all window to apply policy to each window.
     *
     * @param win The window being positioned.
     * @param attrs The LayoutParams of the window.
     * @param attached For sub-windows, the window it is attached to. Otherwise null.
     * @param imeLayeringTarget the current IME layering target, if any.
     */
    public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
            WindowState attached, @Nullable WindowState imeLayeringTarget) {
        if (attrs.type == TYPE_NAVIGATION_BAR) {
            // Keep mHasBottomNavigationBar updated to make sure the bar color control is working
            // correctly.
            mHasBottomNavigationBar = hasBottomNavigationBar();
        }
        final boolean affectsSystemUi = win.canAffectSystemUiFlags();
        if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
        applyKeyguardPolicy(win, imeLayeringTarget);

        // Check if the freeform window overlaps with the navigation bar area.
        if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
                && win.mActivityRecord != null && isOverlappingWithNavBar(win)) {
            mIsFreeformWindowOverlappingWithNavBar = true;
        }

        if (win.hasInsetsSourceProvider()) {
            final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
            final Rect bounds = win.getBounds();
            for (int index = providers.size() - 1; index >= 0; index--) {
                final InsetsSourceProvider provider = providers.valueAt(index);
                final InsetsSource source = provider.getSource();
                if ((source.getType()
                        & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) {
                    continue;
                }
                if (mLeftGestureHost != null && mTopGestureHost != null
                        && mRightGestureHost != null && mBottomGestureHost != null) {
                    continue;
                }
                final Insets insets = source.calculateInsets(bounds, bounds,
                        false /* ignoreVisibility */);
                if (mLeftGestureHost == null && insets.left > 0) {
                    mLeftGestureHost = win;
                }
                if (mTopGestureHost == null && insets.top > 0) {
                    mTopGestureHost = win;
                }
                if (mRightGestureHost == null && insets.right > 0) {
                    mRightGestureHost = win;
                }
                if (mBottomGestureHost == null && insets.bottom > 0) {
                    mBottomGestureHost = win;
                }
            }
        }

        if (win.mSession.mCanForceShowingInsets) {
            mShowingTransientInsetsTypes |= win.mAttrs.forciblyShownTypes;
        }

        if (win.mImeInsetsConsumed != mImeInsetsConsumed) {
            win.mImeInsetsConsumed = mImeInsetsConsumed;
            final WindowState imeWin = mDisplayContent.mInputMethodWindow;
            if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) {
                win.notifyInsetsChanged();
            }
        }
        if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) {
            mImeInsetsConsumed = true;
        }

        if (!affectsSystemUi) {
            return;
        }

        boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                && attrs.type < FIRST_SYSTEM_WINDOW;
        if (mTopFullscreenOpaqueWindowState == null) {
            final int fl = attrs.flags;
            if (win.isDreamWindow()) {
                // If the lockscreen was showing when the dream started then wait
                // for the dream to draw before hiding the lockscreen.
                if (!mDreamingLockscreen || (win.isVisible() && win.hasDrawn())) {
                    mShowingDream = true;
                    appWindow = true;
                }
            }

            if (appWindow && attached == null && attrs.isFullscreen()
                    && (fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
                mAllowLockscreenWhenOn = true;
            }
        }

        // Check the windows that overlap with system bars to determine system bars' appearance.
        if ((appWindow && attached == null && attrs.isFullscreen())
                || attrs.type == TYPE_VOICE_INTERACTION) {

            // If this is the exiting starting window, don't let it control the system bars.
            // The app window behind it should be the controlling window instead. Reason: when an
            // activity starts another activity behind a starting window, the app window of the
            // first activity will lose the window focus. And then mTopFullscreenOpaqueWindowState
            // will control the system bars. The logic here is to let first app window keep
            // controlling system bars until the second app window is ready.
            final boolean exitingStartingWindow =
                    attrs.type == TYPE_APPLICATION_STARTING && win.mAnimatingExit;

            // Record the top-fullscreen-app-window which will be used to determine the system UI
            // controlling window.
            if (mTopFullscreenOpaqueWindowState == null && !exitingStartingWindow
                    && fillsDisplayWindowingMode(win)) {
                mTopFullscreenOpaqueWindowState = win;
            }

            // Cache app windows that is overlapping with the status bar to determine appearance
            // of status bar.
            if (mStatusBar != null
                    && sTmpRect.setIntersect(win.getFrame(), mStatusBar.getFrame())
                    && !mStatusBarBackgroundCheckedBounds.contains(sTmpRect)) {
                mStatusBarBackgroundWindows.add(win);
                mStatusBarBackgroundCheckedBounds.union(sTmpRect);
                if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) {
                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(
                            win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
                            new Rect(win.getFrame())));
                    mStatusBarColorCheckedBounds.union(sTmpRect);
                    addSystemBarColorApp(win);
                }
            }

            // Cache app window that overlaps with the navigation bar area to determine opacity
            // and appearance of the navigation bar. We only need to cache one window because
            // there should be only one overlapping window if it's not in gesture navigation
            // mode; if it's in gesture navigation mode, the navigation bar will be
            // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping
            // windows.
            if (isOverlappingWithNavBar(win)) {
                if (mNavBarColorWindowCandidate == null) {
                    mNavBarColorWindowCandidate = win;
                    addSystemBarColorApp(win);
                }
                if (mNavBarBackgroundWindowCandidate == null) {
                    mNavBarBackgroundWindowCandidate = win;
                }
            }

            // Check if current activity is letterboxed in order create a LetterboxDetails
            // component to be passed to SysUI for status bar treatment
            final ActivityRecord currentActivity = win.getActivityRecord();
            if (currentActivity != null) {
                final LetterboxDetails currentLetterboxDetails = currentActivity
                        .mAppCompatController.getLetterboxPolicy().getLetterboxDetails();
                if (currentLetterboxDetails != null) {
                    mLetterboxDetails.add(currentLetterboxDetails);
                }
            }
        } else if (win.isDimming()) {
            if (mStatusBar != null) {
                // If the dim window is below status bar window, we should update the appearance
                // region if needed. Otherwise, leave it as it is.
                final int statusBarLayer = mStatusBar.mToken.getWindowLayerFromType();
                final int targetWindowLayer = win.mToken.getWindowLayerFromType();
                if (targetWindowLayer < statusBarLayer
                        && addStatusBarAppearanceRegionsForDimmingWindow(
                                win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
                                mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
                    addSystemBarColorApp(win);
                }
            }
            if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                mNavBarColorWindowCandidate = win;
                addSystemBarColorApp(win);
            }
        } else if (appWindow && attached == null
                && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null)
                && win.getFrame().contains(
                        getBarContentFrameForWindow(win, Type.navigationBars()))) {
            if (mNavBarColorWindowCandidate == null) {
                mNavBarColorWindowCandidate = win;
                addSystemBarColorApp(win);
            }
            if (mNavBarBackgroundWindowCandidate == null) {
                mNavBarBackgroundWindowCandidate = win;
            }
        }
    }

    /**
     * Returns true if mStatusBarAppearanceRegionList is changed.
     */
    private boolean addStatusBarAppearanceRegionsForDimmingWindow(
            int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) {
        if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
            return false;
        }
        if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
            return false;
        }
        if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
            mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
            mStatusBarColorCheckedBounds.union(sTmpRect);
            return true;
        }
        // A dimming window can divide status bar into different appearance regions (up to 3).
        // +---------+-------------+---------+
        // |/////////|             |/////////| <-- Status Bar
        // +---------+-------------+---------+
        // |/////////|             |/////////|
        // |/////////|             |/////////|
        // |/////////|             |/////////|
        // |/////////|             |/////////|
        // |/////////|             |/////////|
        // +---------+-------------+---------+
        //      ^           ^           ^
        //  dim layer     window    dim layer
        mStatusBarAppearanceRegionList.add(new AppearanceRegion(appearance, new Rect(winFrame)));
        if (!sTmpRect.equals(sTmpRect2)) {
            if (sTmpRect.height() == sTmpRect2.height()) {
                if (sTmpRect.left != sTmpRect2.left) {
                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
                            winBounds.left, winBounds.top, sTmpRect2.left, winBounds.bottom)));
                }
                if (sTmpRect.right != sTmpRect2.right) {
                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
                            sTmpRect2.right, winBounds.top, winBounds.right, winBounds.bottom)));
                }
            }
            // We don't have vertical status bar yet, so we don't handle the other orientation.
        }
        mStatusBarColorCheckedBounds.union(sTmpRect);
        return true;
    }

    private void addSystemBarColorApp(WindowState win) {
        final ActivityRecord app = win.mActivityRecord;
        if (app != null) {
            mSystemBarColorApps.add(app);
        }
    }

    /**
     * Called following layout of all windows and after policy has been applied to each window.
     */
    public void finishPostLayoutPolicyLw() {
        // If we are not currently showing a dream then remember the current
        // lockscreen state.  We will use this to determine whether the dream
        // started while the lockscreen was showing and remember this state
        // while the dream is showing.
        if (!mShowingDream) {
            mDreamingLockscreen = mService.mPolicy.isKeyguardShowingAndNotOccluded();
        }

        updateSystemBarAttributes();

        if (mShowingDream != mLastShowingDream) {
            mLastShowingDream = mShowingDream;
            // Notify that isShowingDreamLw (which is checked in KeyguardController) has changed.
            mDisplayContent.notifyKeyguardFlagsChanged();
        }

        mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
    }

    boolean areInsetsTypesForciblyShownTransiently(@InsetsType int types) {
        return (mShowingTransientInsetsTypes & types) == types;
    }

    /**
     * Applies the keyguard policy to a specific window.
     *
     * @param win The window to apply the keyguard policy.
     * @param imeLayeringTarget The current IME layering target, if any.
     */
    private void applyKeyguardPolicy(WindowState win, @Nullable WindowState imeLayeringTarget) {
        if (win.canBeHiddenByKeyguard()) {
            final boolean shouldBeHiddenByKeyguard = shouldBeHiddenByKeyguard(win,
                    imeLayeringTarget);
            if (win.mIsImWindow) {
                // Notify IME insets provider to freeze the IME insets. In case when turning off
                // the screen, the IME insets source window will be hidden because of keyguard
                // policy change and affects the system to freeze the last insets state. (And
                // unfreeze when the IME is going to show)
                mDisplayContent.getInsetsStateController().getImeSourceProvider().setFrozen(
                        shouldBeHiddenByKeyguard);
            }
            if (shouldBeHiddenByKeyguard) {
                win.hide(false /* doAnimation */, true /* requestAnim */);
            } else {
                win.show(false /* doAnimation */, true /* requestAnim */);
            }
        }
    }

    private boolean shouldBeHiddenByKeyguard(WindowState win,
            @Nullable WindowState imeLayeringTarget) {
        if (!mDisplayContent.isDefaultDisplay || !isKeyguardShowing()) {
            return false;
        }

        // Show IME over the keyguard if the target allows it.
        final boolean showImeOverKeyguard = imeLayeringTarget != null && win.mIsImWindow
                && imeLayeringTarget.isDisplayed() && (imeLayeringTarget.canShowWhenLocked()
                    || !imeLayeringTarget.canBeHiddenByKeyguard());
        if (showImeOverKeyguard) {
            return false;
        }

        // Show SHOW_WHEN_LOCKED windows if keyguard is occluded.
        final boolean allowShowWhenLocked = isKeyguardOccluded()
                // Show error dialogs over apps that are shown on keyguard.
                && (win.canShowWhenLocked()
                        || (win.mAttrs.privateFlags & LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR) != 0);
        return !allowShowWhenLocked;
    }

    /**
     * @return Whether the top fullscreen app hides the given type of system bar.
     */
    boolean topAppHidesSystemBar(@InsetsType int type) {
        if (mTopFullscreenOpaqueWindowState == null
                || getInsetsPolicy().areTypesForciblyShown(type)) {
            return false;
        }
        return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type);
    }

    /**
     * Called when the user is switched.
     */
    public void switchUser() {
        updateCurrentUserResources();
        updateForceShowNavBarSettings();
    }

    /**
     * Called when the resource overlays change.
     */
    void onOverlayChanged() {
        updateCurrentUserResources();
        // Update the latest display size, cutout.
        mDisplayContent.requestDisplayUpdate(() -> {
            onConfigurationChanged();
            if (!CLIENT_TRANSIENT) {
                mSystemGestures.onConfigurationChanged();
            }
        });
    }

    /**
     * Called when the configuration has changed, and it's safe to load new values from resources.
     */
    public void onConfigurationChanged() {
        final Resources res = getCurrentUserResources();
        mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
        mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
        mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
        mNavigationBarAlwaysShowOnSideGesture =
                res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
        mRemoteInsetsControllerControlsSystemBars = res.getBoolean(
                R.bool.config_remoteInsetsControllerControlsSystemBars);

        updateConfigurationAndScreenSizeDependentBehaviors();

        final boolean shouldAttach =
                res.getBoolean(R.bool.config_attachNavBarToAppDuringTransition)
                        && !Flags.enableTinyTaskbar();
        if (mShouldAttachNavBarToAppDuringTransition != shouldAttach) {
            mShouldAttachNavBarToAppDuringTransition = shouldAttach;
        }
    }

    void updateConfigurationAndScreenSizeDependentBehaviors() {
        final Resources res = getCurrentUserResources();
        mNavigationBarCanMove =
                mDisplayContent.mBaseDisplayWidth != mDisplayContent.mBaseDisplayHeight
                        && res.getBoolean(R.bool.config_navBarCanMove);
        mDisplayContent.getDisplayRotation().updateUserDependentConfiguration(res);
    }

    /**
     * Updates the current user's resources to pick up any changes for the current user (including
     * overlay paths)
     */
    private void updateCurrentUserResources() {
        final int userId = mService.mAmInternal.getCurrentUserId();
        final Context uiContext = getSystemUiContext();

        if (userId == UserHandle.USER_SYSTEM) {
            // Skip the (expensive) recreation of resources for the system user below and just
            // use the resources from the system ui context
            mCurrentUserResources = uiContext.getResources();
            return;
        }

        // For non-system users, ensure that the resources are loaded from the current
        // user's package info (see ContextImpl.createDisplayContext)
        final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo(
                uiContext.getPackageName(), null, 0, userId);
        mCurrentUserResources = ResourcesManager.getInstance().getResources(
                uiContext.getWindowContextToken(),
                pi.getResDir(),
                null /* splitResDirs */,
                pi.getOverlayDirs(),
                pi.getOverlayPaths(),
                pi.getApplicationInfo().sharedLibraryFiles,
                mDisplayContent.getDisplayId(),
                null /* overrideConfig */,
                uiContext.getResources().getCompatibilityInfo(),
                null /* classLoader */,
                null /* loaders */);
    }

    @VisibleForTesting
    Resources getCurrentUserResources() {
        if (mCurrentUserResources == null) {
            updateCurrentUserResources();
        }
        return mCurrentUserResources;
    }

    @NonNull
    Context getContext() {
        return mContext;
    }

    @NonNull
    Context getSystemUiContext() {
        return mUiContext;
    }

    @VisibleForTesting
    void setCanSystemBarsBeShownByUser(boolean canBeShown) {
        mCanSystemBarsBeShownByUser = canBeShown;
    }

    void notifyDisplayAddSystemDecorations() {
        if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue()) {
            final int displayId = getDisplayId();
            final boolean isSystemDecorationsSupported =
                    mDisplayContent.isSystemDecorationsSupported();
            if (DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()
                    && isSystemDecorationsSupported) {
                mService.mDisplayNotificationController
                        .dispatchDisplayAddSystemDecorations(displayId);
            }
            final boolean isFreeformSupported =
                    mDisplayContent.isWindowingModeSupported(WINDOWING_MODE_FREEFORM);
            final boolean eligibleForDesktopMode = isFreeformSupported
                    && isSystemDecorationsSupported && (mDisplayContent.isDefaultDisplay
                            || mDisplayContent.allowContentModeSwitch());
            if (eligibleForDesktopMode) {
                mService.mDisplayNotificationController.dispatchDesktopModeEligibleChanged(
                        displayId);
            }

            final boolean isHomeSupported = mDisplayContent.isHomeSupported();
            mHandler.post(() -> {
                if (!DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()
                        && isSystemDecorationsSupported) {
                    StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                    if (statusBar != null) {
                        statusBar.onDisplayAddSystemDecorations(displayId);
                    }
                }
                if (isHomeSupported) {
                    final WallpaperManagerInternal wpMgr =
                            LocalServices.getService(WallpaperManagerInternal.class);
                    if (wpMgr != null) {
                        wpMgr.onDisplayAddSystemDecorations(displayId);
                    }
                }
            });
        } else {
            mHandler.post(() -> {
                final int displayId = getDisplayId();
                StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                if (statusBar != null) {
                    statusBar.onDisplayAddSystemDecorations(displayId);
                }
                final WallpaperManagerInternal wpMgr = LocalServices
                        .getService(WallpaperManagerInternal.class);
                if (wpMgr != null) {
                    wpMgr.onDisplayAddSystemDecorations(displayId);
                }
            });
        }
    }

    void notifyDisplayRemoveSystemDecorations() {
        final int displayId = getDisplayId();
        if (DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()) {
            mService.mDisplayNotificationController
                    .dispatchDisplayRemoveSystemDecorations(displayId);
        }
        mService.mDisplayNotificationController.dispatchDesktopModeEligibleChanged(displayId);
        mHandler.post(
                () -> {
                    if (!DesktopExperienceFlags.ENABLE_SYS_DECORS_CALLBACKS_VIA_WM.isTrue()) {
                        StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
                        if (statusBar != null) {
                            statusBar.onDisplayRemoveSystemDecorations(displayId);
                        }
                    }
                    final WallpaperManagerInternal wpMgr =
                            LocalServices.getService(WallpaperManagerInternal.class);
                    if (wpMgr != null) {
                        wpMgr.onDisplayRemoveSystemDecorations(displayId);
                    }
                    final NotificationManagerInternal notificationManager =
                            LocalServices.getService(NotificationManagerInternal.class);
                    if (notificationManager != null) {
                        notificationManager.onDisplayRemoveSystemDecorations(displayId);
                    }
                });
    }

    boolean isShowingDreamLw() {
        return mShowingDream;
    }

    /** The latest insets and frames for screen configuration calculation. */
    static class DecorInsets {
        static final int OVERRIDE_CONFIG_TYPES = WindowInsets.Type.displayCutout()
                | WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
        static final int OVERRIDE_DECOR_TYPES = WindowInsets.Type.displayCutout()
                | WindowInsets.Type.navigationBars();

        static class Info {
            // TODO(b/409608996):
            //  Remove mNonDecorInsets, mConfigInsets -> always empty
            /**
             * The insets for the areas that could never be removed, i.e. display cutout and
             * navigation bar. Note that its meaning is actually "decor insets". The "non" is just
             * because it is used to calculate {@link #mNonDecorFrame}.
             */
            final Rect mNonDecorInsets = new Rect();

            /**
             * The stable insets that can affect configuration. The sources are usually from
             * display cutout, navigation bar, and status bar.
             */
            final Rect mConfigInsets = new Rect();

            /**
             * Override value of mConfigInsets for app compatibility purpose.
             */
            final Rect mOverrideConfigInsets = new Rect();

            /**
             * Override value of mNonDecorInsets for app compatibility purpose.
             */
            final Rect mOverrideNonDecorInsets = new Rect();

            /**
             * Override available (stable) screen size for app compatibility purpose.
             * This must be no larger than {@link #mOverrideNonDecorFrame}; it may be smaller than
             * that to account for more transient decoration like a status bar.
             */
            final Rect mOverrideConfigFrame = new Rect();

            /**
             * Override display frame which excludes {@link #mOverrideNonDecorInsets} for app
             * compatibility purpose.
             */
            final Rect mOverrideNonDecorFrame = new Rect();

            private boolean mNeedUpdate = true;

            void update(DisplayContent dc, int rotation, int w, int h) {
                final DisplayFrames df = new DisplayFrames();
                dc.updateDisplayFrames(df, rotation, w, h);
                dc.getDisplayPolicy().simulateLayoutDisplay(df);
                final InsetsState insetsState = df.mInsetsState;
                final Rect displayFrame = insetsState.getDisplayFrame();
                final Insets overrideConfigInsets =
                        insetsState.calculateInsets(displayFrame, displayFrame,
                                OVERRIDE_CONFIG_TYPES, true /* ignoreVisibility */);
                final Insets overrideDecorInsets =
                        insetsState.calculateInsets(displayFrame, displayFrame,
                                OVERRIDE_DECOR_TYPES, true /* ignoreVisibility */);
                mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
                        overrideConfigInsets.right, overrideConfigInsets.bottom);
                mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top,
                        overrideDecorInsets.right, overrideDecorInsets.bottom);
                mOverrideConfigFrame.set(displayFrame);
                mOverrideConfigFrame.inset(mOverrideConfigInsets);
                mOverrideNonDecorFrame.set(displayFrame);
                mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets);
                mNeedUpdate = false;
            }

            void set(Info other) {
                mNonDecorInsets.set(other.mNonDecorInsets);
                mConfigInsets.set(other.mConfigInsets);
                mOverrideConfigInsets.set(other.mOverrideConfigInsets);
                mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets);
                mOverrideConfigFrame.set(other.mOverrideConfigFrame);
                mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame);
                mNeedUpdate = false;
            }

            @Override
            public String toString() {
                final StringBuilder tmpSb = new StringBuilder(32);
                return "{nonDecorInsets=" + mNonDecorInsets.toShortString(tmpSb)
                        + ", overrideNonDecorInsets=" + mOverrideNonDecorInsets.toShortString(tmpSb)
                        + ", configInsets=" + mConfigInsets.toShortString(tmpSb)
                        + ", overrideConfigInsets=" + mOverrideConfigInsets.toShortString(tmpSb)
                        + ", overrideNonDecorFrame=" + mOverrideNonDecorFrame.toShortString(tmpSb)
                        + ", overrideConfigFrame=" + mOverrideConfigFrame.toShortString(tmpSb)
                        + '}';
            }
        }

        private final DisplayContent mDisplayContent;
        private final Info[] mInfoForRotation = new Info[4];
        final Info mTmpInfo = new Info();

        DecorInsets(DisplayContent dc) {
            mDisplayContent = dc;
            for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
                mInfoForRotation[i] = new Info();
            }
        }

        Info get(int rotation, int w, int h) {
            final Info info = mInfoForRotation[rotation];
            if (info.mNeedUpdate) {
                info.update(mDisplayContent, rotation, w, h);
            }
            return info;
        }

        /** Called when the screen decor insets providers have changed. */
        void invalidate() {
            for (Info info : mInfoForRotation) {
                info.mNeedUpdate = true;
            }
        }

        void setTo(DecorInsets src) {
            for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
                mInfoForRotation[i].set(src.mInfoForRotation[i]);
            }
        }

        void dump(String prefix, PrintWriter pw) {
            for (int rotation = 0; rotation < mInfoForRotation.length; rotation++) {
                final DecorInsets.Info info = mInfoForRotation[rotation];
                pw.println(prefix + Surface.rotationToString(rotation) + "=" + info);
            }
        }

        private static class Cache {
            static final int TYPE_REGULAR_BARS = WindowInsets.Type.statusBars()
                    | WindowInsets.Type.navigationBars();
            /**
             * If {@link #mPreserveId} is this value, it is in the middle of updating display
             * configuration before a transition is started. Then the active cache should be used.
             */
            static final int ID_UPDATING_CONFIG = -1;
            final DecorInsets mDecorInsets;
            int mPreserveId;
            boolean mActive;

            /**
             * When display switches, mRegularBarsInsets will assign to mPreservedInsets, and the
             * insets sources of previous device state will copy to mRegularBarsInsets.
             */
            ArrayList<InsetsSource> mPreservedInsets;
            ArrayList<InsetsSource> mRegularBarsInsets;
            PrivacyIndicatorBounds mPrivacyIndicatorBounds;
            int mRotation;

            Cache(DisplayContent dc) {
                mDecorInsets = new DecorInsets(dc);
            }

            boolean canPreserve() {
                return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
                        .mTransitionController.inTransition(mPreserveId);
            }

            static ArrayList<InsetsSource> copyRegularBarInsets(InsetsState srcState) {
                final ArrayList<InsetsSource> state = new ArrayList<>();
                for (int i = srcState.sourceSize() - 1; i >= 0; i--) {
                    final InsetsSource source = srcState.sourceAt(i);
                    if ((source.getType() & TYPE_REGULAR_BARS) != 0) {
                        state.add(new InsetsSource(source));
                    }
                }
                return state;
            }
        }
    }

    /**
     * If the decor insets changes, the display configuration may be affected. The caller should
     * call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
     */
    boolean updateDecorInsetsInfo() {
        if (shouldKeepCurrentDecorInsets()) {
            return false;
        }
        final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
        final int rotation = displayFrames.mRotation;
        final int dw = displayFrames.mWidth;
        final int dh = displayFrames.mHeight;
        final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
        newInfo.update(mDisplayContent, rotation, dw, dh);
        final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
        if (newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) {
            return false;
        }
        if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() && mScreenOnFully) {
            mCachedDecorInsets = null;
        }
        mDecorInsets.invalidate();
        mDecorInsets.mInfoForRotation[rotation].set(newInfo);
        if (!mService.mDisplayEnabled) {
            // There could be other pending changes during booting. It might be better to let the
            // clients receive the new states earlier.
            return true;
        }
        return false;
    }

    DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) {
        return mDecorInsets.get(rotation, w, h);
    }

    /** Returns {@code true} to trust that {@link #mDecorInsets} already has the expected state. */
    boolean shouldKeepCurrentDecorInsets() {
        return mCachedDecorInsets != null && mCachedDecorInsets.mActive
                && mCachedDecorInsets.canPreserve();
    }

    void physicalDisplayChanged() {
        if (USE_CACHED_INSETS_FOR_DISPLAY_SWITCH) {
            updateCachedDecorInsets();
        }
    }

    /**
     * Caches the current insets and switches current insets to previous cached insets. This is to
     * reduce multiple display configuration changes if there are multiple insets provider windows
     * which may trigger {@link #updateDecorInsetsInfo()} individually.
     */
    @VisibleForTesting
    void updateCachedDecorInsets() {
        DecorInsets prevCache = null;
        PrivacyIndicatorBounds privacyIndicatorBounds = null;
        if (mCachedDecorInsets == null) {
            mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
        } else {
            prevCache = new DecorInsets(mDisplayContent);
            prevCache.setTo(mCachedDecorInsets.mDecorInsets);
            privacyIndicatorBounds = mCachedDecorInsets.mPrivacyIndicatorBounds;
            mCachedDecorInsets.mPreservedInsets =
                    mCachedDecorInsets.mRotation == mDisplayContent.mDisplayFrames.mRotation
                            ? mCachedDecorInsets.mRegularBarsInsets : null;
        }
        // Set a special id to preserve it before a real id is available from transition.
        mCachedDecorInsets.mPreserveId = DecorInsets.Cache.ID_UPDATING_CONFIG;
        // Cache the current insets.
        mCachedDecorInsets.mDecorInsets.setTo(mDecorInsets);
        mCachedDecorInsets.mRegularBarsInsets = DecorInsets.Cache.copyRegularBarInsets(
                mDisplayContent.mDisplayFrames.mInsetsState);
        mCachedDecorInsets.mRotation = mDisplayContent.mDisplayFrames.mRotation;
        mCachedDecorInsets.mPrivacyIndicatorBounds = mDisplayContent.mCurrentPrivacyIndicatorBounds;

        // Switch current to previous cache.
        if (prevCache != null) {
            mDecorInsets.setTo(prevCache);
            if (privacyIndicatorBounds != null) {
                mDisplayContent.mCurrentPrivacyIndicatorBounds = privacyIndicatorBounds;
            }
            mCachedDecorInsets.mActive = true;
        }
    }

    /**
     * This returns a new InsetsState with replacing the insets in target device state when the
     * display is switching (e.g. fold/unfold). Otherwise, it returns the original state. This is
     * to avoid dispatching old insets source before the insets providers update new insets.
     */
    @NonNull
    InsetsState replaceInsetsSourcesIfNeeded(@NonNull InsetsState originalState,
            boolean copyState) {
        if (mCachedDecorInsets == null || mCachedDecorInsets.mPreservedInsets == null
                || !shouldKeepCurrentDecorInsets()) {
            return originalState;
        }
        final ArrayList<InsetsSource> preservedSources = mCachedDecorInsets.mPreservedInsets;
        final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
        for (int i = preservedSources.size() - 1; i >= 0; i--) {
            final InsetsSource cachedSource = preservedSources.get(i);
            final InsetsSource originalSource = state.peekSource(cachedSource.getId());
            if (originalSource != null) {
                state.addSource(new InsetsSource(cachedSource)
                        .setVisible(originalSource.isVisible()));
            }
        }
        return state;
    }

    /**
     * Called after the display configuration is updated according to the physical change. Suppose
     * there should be a display change transition, so associate the cached decor insets with the
     * transition to limit the lifetime of using the cache.
     */
    void physicalDisplayUpdated() {
        if (mCachedDecorInsets == null) {
            return;
        }
        if (!mDisplayContent.mTransitionController.isCollecting()) {
            // Unable to know when the display switch is finished.
            mCachedDecorInsets = null;
            return;
        }
        mCachedDecorInsets.mPreserveId =
                mDisplayContent.mTransitionController.getCollectingTransitionId();
    }

    /** If this is called, expect that there will be an onDisplayChanged about unique id. */
    public void onDisplaySwitchStart() {
        mDisplayContent.mDisplayUpdater.onDisplaySwitching(true);
    }

    /**
     * Returns 'true' if the physical display is currently in the process of switching, for example
     * on foldable devices when folding or unfolding. The value becomes 'false' when the switching
     * has been finished (the new display is fully turned on).
     * It is guaranteed that this method will start returning 'true' before any callbacks related
     * to this display switch from the DisplayManager like onScreenTurningOn or onDisplayChanged.
     */
    public boolean isDisplaySwitching() {
        return mDisplayContent.mDisplayUpdater.isDisplaySwitching();
    }

    boolean hasBottomNavigationBar() {
        final Rect displayFrame = mDisplayContent.mDisplayFrames.mUnrestricted;
        Insets navBarInsets = mDisplayContent.getInsetsStateController().getRawInsetsState()
                .calculateInsets(displayFrame, displayFrame,
                        Type.navigationBars(), true /* ignoreVisibilities */);
        return navBarInsets.bottom > 0;
    }

    /**
     * A new window has been focused.
     */
    public void focusChangedLw(WindowState lastFocus, WindowState newFocus) {
        mFocusedWindow = newFocus;
        mLastFocusedWindow = lastFocus;
        if (mDisplayContent.isDefaultDisplay) {
            mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);
        }
        updateSystemBarAttributes();
    }

    @VisibleForTesting
    void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) {
        if (CLIENT_TRANSIENT) {
            return;
        }
        if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) {
            // Swipe-up for navigation bar is disabled during setup
            return;
        }
        if (!mCanSystemBarsBeShownByUser) {
            Slog.d(TAG, "Remote insets controller disallows showing system bars - ignoring "
                    + "request");
            return;
        }
        final InsetsSourceProvider provider = swipeTarget.getControllableInsetProvider();
        final InsetsControlTarget controlTarget = provider != null
                ? provider.getControlTarget() : null;

        if (controlTarget == null || controlTarget == getNotificationShade()) {
            // No transient mode on lockscreen (in notification shade window).
            return;
        }

        if (controlTarget != null) {
            final WindowState win = controlTarget.getWindow();

            if (win != null && win.isActivityTypeDream()) {
                return;
            }
        }

        final @InsetsType int restorePositionTypes = (Type.statusBars() | Type.navigationBars())
                & controlTarget.getRequestedVisibleTypes();

        final InsetsSourceProvider sp = swipeTarget.getControllableInsetProvider();
        if (sp != null && sp.getSource().getType() == Type.navigationBars()
                && (restorePositionTypes & Type.navigationBars()) != 0) {
            // Don't show status bar when swiping on already visible navigation bar.
            // But restore the position of navigation bar if it has been moved by the control
            // target.
            controlTarget.showInsets(Type.navigationBars(), null /* statsToken */);
            return;
        }

        if (controlTarget.canShowTransient()) {
            // Show transient bars if they are hidden; restore position if they are visible.
            mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
                    isGestureOnSystemBar);
            controlTarget.showInsets(restorePositionTypes, null /* statsToken */);
        } else {
            // Restore visibilities and positions of system bars.
            controlTarget.showInsets(Type.statusBars() | Type.navigationBars(),
                    null /* statsToken */);
            // To further allow the pull-down-from-the-top gesture to pull down the notification
            // shade as a consistent motion, we reroute the touch events here from the currently
            // touched window to the status bar after making it visible.
            if (swipeTarget == mStatusBar) {
                final boolean transferred = mStatusBar.transferTouch();
                if (!transferred) {
                    Slog.i(TAG, "Could not transfer touch to the status bar");
                }
            }
        }
        mStatusBarManagerInternal.confirmImmersivePrompt();
    }

    boolean isKeyguardShowing() {
        return mService.mPolicy.isKeyguardShowing();
    }
    private boolean isKeyguardOccluded() {
        // TODO (b/113840485): Handle per display keyguard.
        return mService.mPolicy.isKeyguardOccluded();
    }

    InsetsPolicy getInsetsPolicy() {
        return mDisplayContent.getInsetsPolicy();
    }

    /**
     * Called when an app has started replacing its main window.
     */
    void addRelaunchingApp(ActivityRecord app) {
        if (mSystemBarColorApps.contains(app) && !app.hasStartingWindow()) {
            mRelaunchingSystemBarColorApps.add(app);
        }
    }

    /**
     * Called when an app has finished replacing its main window or aborted.
     */
    void removeRelaunchingApp(ActivityRecord app) {
        final boolean removed = mRelaunchingSystemBarColorApps.remove(app);
        if (removed & mRelaunchingSystemBarColorApps.isEmpty()) {
            updateSystemBarAttributes();
        }
    }

    private class SystemBarVisibilityOverride implements IBinder.DeathRecipient {

        private final IBinder mCaller;
        private @InsetsType int mForciblyShowingInsetsTypes;
        private @InsetsType int mForciblyHidingInsetsTypes;

        SystemBarVisibilityOverride(@NonNull IBinder caller) throws RemoteException {
            mCaller = caller;
        }

        boolean set(
                @InsetsType int forciblyShowingInsetsTypes,
                @InsetsType int forciblyHidingInsetsTypes) {
            if (mForciblyShowingInsetsTypes != forciblyShowingInsetsTypes
                    || mForciblyHidingInsetsTypes != forciblyHidingInsetsTypes) {
                mForciblyShowingInsetsTypes = forciblyShowingInsetsTypes;
                mForciblyHidingInsetsTypes = forciblyHidingInsetsTypes;
                return true;
            }
            return false;
        }

        @InsetsType int getForciblyShowingInsetsTypes() {
            return mForciblyShowingInsetsTypes;
        }

        @InsetsType int getForciblyHidingInsetsTypes() {
            return mForciblyHidingInsetsTypes;
        }

        @Override
        public void binderDied() {
            synchronized (mService.mGlobalLock) {
                setSystemBarVisibilityOverride(
                        mCaller,
                        0 /* forciblyShowingInsetsTypes */,
                        0 /* forciblyHidingInsetsTypes */);
            }
        }

        @Override
        public String toString() {
            return "SystemBarVisibilityOverride{@" + Integer.toHexString(mCaller.hashCode())
                    + " show:[" + Type.toString(mForciblyShowingInsetsTypes)
                    + "] hide: [" + Type.toString(mForciblyHidingInsetsTypes) + "]}";
        }
    }

    void setSystemBarVisibilityOverride(
            @NonNull IBinder caller,
            @InsetsType int forciblyShowingInsetsTypes,
            @InsetsType int forciblyHidingInsetsTypes) {
        SystemBarVisibilityOverride override = mSystemBarVisibilityOverrideMap.get(caller);
        try {
            if (forciblyShowingInsetsTypes != 0 || forciblyHidingInsetsTypes != 0) {
                if (override == null) {
                    override = new SystemBarVisibilityOverride(caller);
                    caller.linkToDeath(override, 0 /* flags */);
                    mSystemBarVisibilityOverrideMap.put(caller, override);
                }
                if (override.set(forciblyShowingInsetsTypes, forciblyHidingInsetsTypes)) {
                    updateSystemBarVisibilityOverride();
                }
            } else {
                if (override != null) {
                    caller.unlinkToDeath(override, 0 /* flags */);
                    mSystemBarVisibilityOverrideMap.remove(caller);
                    updateSystemBarVisibilityOverride();
                }
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Unable to set " + override + " due to RemoteException.", e);
        }
    }

    private void updateSystemBarVisibilityOverride() {
        mShowingPermanentInsetsTypes = 0;
        mHidingPermanentInsetsTypes = 0;
        for (int i = mSystemBarVisibilityOverrideMap.size() - 1; i >= 0; i--) {
            final SystemBarVisibilityOverride override = mSystemBarVisibilityOverrideMap.valueAt(i);
            mShowingPermanentInsetsTypes |= override.getForciblyShowingInsetsTypes();
            mHidingPermanentInsetsTypes |= override.getForciblyHidingInsetsTypes();
        }
        updateSystemBarAttributes();
    }

    void resetSystemBarAttributes() {
        mLastDisableFlags = 0;
        updateSystemBarAttributes();
    }

    private boolean fillsDisplayWindowingMode(@NonNull WindowState win) {
        if (!WindowConfiguration.inMultiWindowMode(win.getWindowingMode())) {
            // Always accept the window not in multi-window mode.
            return true;
        }
        // Accept the window in multi-window mode only if its task fills the display.
        // e.g., A maximized free-form window.
        final Task task = win.getTask();
        final Rect bounds = task != null ? task.getBounds() : win.getBounds();
        return bounds.equals(mDisplayContent.getBounds());
    }

    private boolean fillsDisplayWindowingMode(@NonNull ActivityRecord app) {
        if (!WindowConfiguration.inMultiWindowMode(app.getWindowingMode())) {
            // Always accept the app not in multi-window mode.
            return true;
        }
        // Accept the app in multi-window mode only if its task fills the display.
        // e.g., A maximized free-form window.
        final Task task = app.getTask();
        final Rect bounds = task != null ? task.getBounds() : app.getBounds();
        return bounds.equals(mDisplayContent.getBounds());
    }

    void updateSystemBarAttributes() {
        // The focused window always needs to be sent to System UI regardless of filling
        // display when the remote insets controller is controlling system bars.
        final boolean isRemoteControlling = isRemoteInsetsControllerControllingSystemBars();
        // If there is no window focused, there will be nobody to handle the events
        // anyway, so just hang on in whatever state we're in until things settle down.
        WindowState winCandidate =
                mFocusedWindow != null && (isRemoteControlling || fillsDisplayWindowingMode(
                        mFocusedWindow)) ? mFocusedWindow : mTopFullscreenOpaqueWindowState;

        // Immersive mode confirmation should never affect the system bar visibility, otherwise
        // it will unhide the navigation bar and hide itself.
        if (winCandidate != null && (winCandidate.mAttrs.privateFlags
                & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
            if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
                // Let notification shade control the system bar visibility.
                winCandidate = mNotificationShade;
            } else if (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys()
                    && (isRemoteControlling || fillsDisplayWindowingMode(mLastFocusedWindow))) {
                // Immersive mode confirmation took the focus from mLastFocusedWindow which was
                // controlling the system bar visibility. Let it keep controlling the visibility.
                winCandidate = mLastFocusedWindow;
            } else {
                winCandidate = mTopFullscreenOpaqueWindowState;
            }
        }
        if (winCandidate == null) {
            final ActivityRecord focusedApp = mDisplayContent.mFocusedApp;
            if (focusedApp == null
                    || (isRemoteControlling || fillsDisplayWindowingMode(focusedApp))) {
                // Don't change the system UI controlling window when the new one is not ready.
                return;
            }
        }
        final WindowState win = winCandidate;
        mSystemUiControllingWindow = win;

        final int displayId = getDisplayId();
        final int disableFlags = win != null ? win.getDisableFlags() : 0;
        final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
        if (!mRelaunchingSystemBarColorApps.isEmpty()) {
            // The appearance of system bars might change while relaunching apps. We don't report
            // the intermediate state to system UI. Otherwise, it might trigger redundant effects.
            return;
        }
        final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
                mDisplayContent.mInputMethodWindow, mHasBottomNavigationBar);
        final boolean isNavbarColorManagedByIme =
                navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
        final int appearance = updateLightNavigationBarLw(win != null
                        ? win.mAttrs.insetsFlags.appearance
                        : 0, navColorWin)
                | opaqueAppearance;
        final WindowState navBarControlWin = topAppHidesSystemBar(Type.navigationBars())
                ? mTopFullscreenOpaqueWindowState
                : win;
        final int behavior = navBarControlWin != null
                ? navBarControlWin.mAttrs.insetsFlags.behavior
                : BEHAVIOR_DEFAULT;
        final String focusedPackageName = win != null
                ? win.mAttrs.packageName
                : "none";
        final boolean isFullscreen = win != null && (!win.isRequestedVisible(Type.statusBars())
                || !win.isRequestedVisible(Type.navigationBars()));
        final AppearanceRegion[] statusBarAppearanceRegions =
                new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
        mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
        if (mLastDisableFlags != disableFlags) {
            mLastDisableFlags = disableFlags;
            final String cause = win != null ? win.toString() : "null";
            callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
                    cause));
        }
        final @InsetsType int requestedVisibleTypes = win != null
                ? win.getRequestedVisibleTypes() : 0;
        final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
        mLetterboxDetails.toArray(letterboxDetails);
        if (mLastAppearance == appearance
                && mLastBehavior == behavior
                && mLastRequestedVisibleTypes == requestedVisibleTypes
                && Objects.equals(mFocusedPackageName, focusedPackageName)
                && mLastFocusIsFullscreen == isFullscreen
                && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
                && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
            return;
        }
        if (mDisplayContent.isDefaultDisplay && (mLastFocusIsFullscreen != isFullscreen
                || ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0)) {
            mService.mInputManager.setSystemUiLightsOut(
                    isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
        }
        mLastAppearance = appearance;
        mLastBehavior = behavior;
        mLastRequestedVisibleTypes = requestedVisibleTypes;
        mFocusedPackageName = focusedPackageName;
        mLastFocusIsFullscreen = isFullscreen;
        mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
        mLastLetterboxDetails = letterboxDetails;
        callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
                appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
                requestedVisibleTypes, focusedPackageName, letterboxDetails));
    }

    private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
        mHandler.post(() -> {
            StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
            if (statusBar != null) {
                consumer.accept(statusBar);
            }
        });
    }

    @VisibleForTesting
    @Nullable
    static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow,
            boolean hasBottomNavigationBar) {
        // If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
        // window can be navigation color window.
        final boolean imeWindowCanNavColorWindow = imeWindow != null
                && imeWindow.isVisible()
                && hasBottomNavigationBar
                && (imeWindow.mAttrs.flags
                        & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
        if (!imeWindowCanNavColorWindow) {
            // No IME window is involved. Determine the result only with candidate window.
            return candidate;
        }

        if (candidate != null && candidate.isDimming()) {
            // The IME window and the dimming window are competing. Check if the dimming window can
            // be IME layering target or not.
            if (LayoutParams.mayUseInputMethod(candidate.mAttrs.flags)) {
                // The IME window is above the dimming window.
                return imeWindow;
            } else {
                // The dimming window is above the IME window.
                return candidate;
            }
        }

        return imeWindow;
    }

    @VisibleForTesting
    int updateLightNavigationBarLw(int appearance, WindowState navColorWin) {
        if (!isLightBarAllowed(navColorWin, Type.navigationBars())) {
            // Clear the light flag while not allowed.
            appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
            return appearance;
        }

        // Respect the light flag of navigation color window.
        appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
        appearance |= navColorWin.mAttrs.insetsFlags.appearance
                & APPEARANCE_LIGHT_NAVIGATION_BARS;
        return appearance;
    }

    private int updateSystemBarsLw(@Nullable WindowState win, int disableFlags) {
        final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
        // TODO(b/407898759): Migrate to have WM Shell to override the insets visibility based on
        // top focused Task.
        final boolean adjacentTasksVisible =
                defaultTaskDisplayArea.getRootTask(task -> task.isVisible()
                        && task.getTopLeafTask().hasAdjacentTask())
                        != null;
        final Task topFreeformTask =
                ENABLE_RESTRICT_FREEFORM_HIDDEN_SYSTEM_BARS_TO_FILLING_TASKS.isTrue()
                        ? defaultTaskDisplayArea.getTask(task ->
                                task.getWindowingMode() == WINDOWING_MODE_FREEFORM
                                // Must be filling to avoid container-only roots, such as
                                // created-by-organizer desk roots.
                                && task.hasFillingContent())
                        : defaultTaskDisplayArea
                                .getTopRootTaskInWindowingMode(WINDOWING_MODE_FREEFORM);
        final boolean freeformRootTaskVisible = topFreeformTask != null
                && topFreeformTask.isVisible();
        final boolean inNonFullscreenFreeformMode = freeformRootTaskVisible
                && !topFreeformTask.getBounds().equals(mDisplayContent.getBounds());
        // Always show status/nav bar for non-fullscreen multi window (excluding PiP).
        final boolean showSystemBarsByLegacyPolicy = adjacentTasksVisible
                || (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
                ? inNonFullscreenFreeformMode : freeformRootTaskVisible);

        getInsetsPolicy().updateSystemBars(
                win,
                mShowingPermanentInsetsTypes,
                mHidingPermanentInsetsTypes,
                showSystemBarsByLegacyPolicy);

        final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
        if (getStatusBar() != null) {
            final StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
            if (statusBar != null) {
                statusBar.setTopAppHidesStatusBar(getDisplayId(), topAppHidesStatusBar);
            }
        }

        // If the top app is not fullscreen, only the default rotation animation is allowed.
        mTopIsFullscreen = topAppHidesStatusBar
                && (mNotificationShade == null || !mNotificationShade.isVisible());

        int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
        appearance = configureStatusBarOpacity(appearance);
        appearance = configureNavBarOpacity(appearance, adjacentTasksVisible,
                freeformRootTaskVisible);

        // Show immersive mode confirmation if needed.
        final boolean wasImmersiveMode = mIsImmersiveMode;
        final boolean isImmersiveMode = isImmersiveMode(win);
        if (wasImmersiveMode != isImmersiveMode) {
            mIsImmersiveMode = isImmersiveMode;
            // The immersive confirmation window should be attached to the immersive window root.
            final RootDisplayArea root = win != null ? win.getRootDisplayArea() : null;
            final int rootDisplayAreaId = root != null ? root.mFeatureId : FEATURE_UNDEFINED;
            final int windowType = win != null ? win.getWindowType() : INVALID_WINDOW_TYPE;
            // TODO(b/277290737): Move this to the client side, instead of using a proxy.
            callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(getDisplayId(),
                        rootDisplayAreaId, isImmersiveMode, windowType));
        }

        // Show transient bars for panic if needed.
        final boolean requestHideNavBar =
                win != null && !win.isRequestedVisible(Type.navigationBars());
        final long now = SystemClock.uptimeMillis();
        final boolean pendingPanic = mPendingPanicGestureUptime != 0
                && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
        final DisplayPolicy defaultDisplayPolicy =
                mService.getDefaultDisplayContentLocked().getDisplayPolicy();
        if (pendingPanic && requestHideNavBar && isImmersiveMode
                // TODO (b/111955725): Show keyguard presentation on all external displays
                && defaultDisplayPolicy.isKeyguardDrawComplete()) {
            // The user performed the panic gesture recently, we're about to hide the bars,
            // we're no longer on the Keyguard and the screen is ready. We can now request the bars.
            mPendingPanicGestureUptime = 0;
            if (!isNavBarEmpty(disableFlags)) {
                mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC,
                        true /* isGestureOnSystemBar */);
            }
        }

        return appearance;
    }

    private static boolean isLightBarAllowed(WindowState win, @InsetsType int type) {
        if (win == null) {
            return false;
        }
        return intersectsAnyInsets(win.getFrame(), win.getInsetsState(), type);
    }

    private Rect getBarContentFrameForWindow(WindowState win, @InsetsType int type) {
        final DisplayFrames displayFrames = win.getDisplayFrames(mDisplayContent.mDisplayFrames);
        final InsetsState state = displayFrames.mInsetsState;
        final Rect df = displayFrames.mUnrestricted;
        final Rect safe = sTmpDisplayCutoutSafe;
        final Insets waterfallInsets = state.getDisplayCutout().getWaterfallInsets();
        final Rect outRect = new Rect();
        final Rect sourceContent = sTmpRect;
        safe.set(displayFrames.mDisplayCutoutSafe);
        for (int i = state.sourceSize() - 1; i >= 0; i--) {
            final InsetsSource source = state.sourceAt(i);
            if (source.getType() != type) {
                continue;
            }
            if (type == Type.statusBars()) {
                safe.set(displayFrames.mDisplayCutoutSafe);
                final Insets insets = source.calculateInsets(df, df, true /* ignoreVisibility */);
                // The status bar content can extend into regular display cutout insets if they are
                // at the same side, but the content cannot extend into waterfall insets.
                if (insets.left > 0) {
                    safe.left = Math.max(df.left + waterfallInsets.left, df.left);
                } else if (insets.top > 0) {
                    safe.top = Math.max(df.top + waterfallInsets.top, df.top);
                } else if (insets.right > 0) {
                    safe.right = Math.max(df.right - waterfallInsets.right, df.right);
                } else if (insets.bottom > 0) {
                    safe.bottom = Math.max(df.bottom - waterfallInsets.bottom, df.bottom);
                }
            }
            sourceContent.set(source.getFrame());
            sourceContent.intersect(safe);
            outRect.union(sourceContent);
        }
        return outRect;
    }

    /**
     * @return {@code true} if bar is allowed to be fully transparent when given window is show.
     *
     * <p>Prevents showing a transparent bar over a letterboxed activity which can make
     * notification icons or navigation buttons unreadable due to contrast between letterbox
     * background and an activity. For instance, this happens when letterbox background is solid
     * black while activity is white. To resolve this, only semi-transparent bars are allowed to
     * be drawn over letterboxed activity.
     */
    @VisibleForTesting
    boolean isFullyTransparentAllowed(WindowState win, @InsetsType int type) {
        if (win == null) {
            return true;
        }
        return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type));
    }

    private static boolean drawsBarBackground(WindowState win, @InsetsType int types) {
        if (win == null) {
            return true;
        }

        final boolean drawsSystemBars =
                (win.mAttrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
        final boolean forceDrawsSystemBars =
                (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
        final boolean hidesSystemBars = (win.getRequestedVisibleTypes() & types) == 0;

        return forceDrawsSystemBars || drawsSystemBars || hidesSystemBars;
    }

    /** @return the current visibility flags with the status bar opacity related flags toggled. */
    private int configureStatusBarOpacity(int appearance) {
        boolean drawBackground = true;
        boolean isFullyTransparentAllowed = true;
        for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
            final WindowState window = mStatusBarBackgroundWindows.get(i);
            drawBackground &= drawsBarBackground(window, Type.statusBars());
            isFullyTransparentAllowed &= isFullyTransparentAllowed(window, Type.statusBars());
        }

        if (drawBackground) {
            appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
        }

        if (!isFullyTransparentAllowed) {
            appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
        }

        return appearance;
    }

    /**
     * @return the current visibility flags with the nav-bar opacity related flags toggled based
     *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
     */
    private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
            boolean freeformRootTaskVisible) {
        final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
                mNavBarBackgroundWindowCandidate,
                mDisplayContent.mInputMethodWindow,
                mHasBottomNavigationBar);
        final boolean drawBackground = navBackgroundWin != null
                // There is no app window showing underneath nav bar. (e.g., The screen is locked.)
                // Let system windows (ex: notification shade) draw nav bar background.
                || mNavBarBackgroundWindowCandidate == null;

        if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
            if (drawBackground) {
                appearance = clearNavBarOpaqueFlag(appearance);
            }
        } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
            if (multiWindowTaskVisible || freeformRootTaskVisible) {
                if (mIsFreeformWindowOverlappingWithNavBar) {
                    appearance = clearNavBarOpaqueFlag(appearance);
                }
            } else if (drawBackground) {
                appearance = clearNavBarOpaqueFlag(appearance);
            }
        } else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
            if (freeformRootTaskVisible) {
                appearance = clearNavBarOpaqueFlag(appearance);
            }
        }

        if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) {
            appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
        }

        return appearance;
    }

    private int clearNavBarOpaqueFlag(int appearance) {
        return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
    }

    @VisibleForTesting
    @Nullable
    static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
            WindowState imeWindow, boolean hasBottomNavigationBar) {
        if (imeWindow != null && imeWindow.isVisible() && hasBottomNavigationBar
                && drawsBarBackground(imeWindow, Type.navigationBars())) {
            return imeWindow;
        }
        if (drawsBarBackground(candidate, Type.navigationBars())) {
            return candidate;
        }
        return null;
    }

    private boolean isImmersiveMode(WindowState win) {
        if (win == null) {
            return false;
        }
        if (win.mPolicy.getWindowLayerLw(win) > win.mPolicy.getWindowLayerFromTypeLw(
                WindowManager.LayoutParams.TYPE_STATUS_BAR) || win.isActivityTypeDream()) {
            return false;
        }
        return getInsetsPolicy().hasHiddenSources(Type.navigationBars());
    }

    private static boolean isNavBarEmpty(int systemUiFlags) {
        final int disableNavigationBar = (View.STATUS_BAR_DISABLE_HOME
                | View.STATUS_BAR_DISABLE_BACK
                | View.STATUS_BAR_DISABLE_RECENT);

        return (systemUiFlags & disableNavigationBar) == disableNavigationBar;
    }

    private final Runnable mHiddenNavPanic = new Runnable() {
        @Override
        public void run() {
            synchronized (mLock) {
                if (!mService.mPolicy.isUserSetupComplete()) {
                    // Swipe-up for navigation bar is disabled during setup
                    return;
                }
                mPendingPanicGestureUptime = SystemClock.uptimeMillis();
                updateSystemBarAttributes();
            }
        }
    };

    void onPowerKeyDown(boolean isScreenOn) {
        // Detect user pressing the power button in panic when an application has
        // taken over the whole screen.
        boolean panic = isPowerKeyDownPanic(isScreenOn, SystemClock.elapsedRealtime(),
                    isImmersiveMode(mSystemUiControllingWindow), isNavBarEmpty(mLastDisableFlags));
        if (panic) {
            mHandler.post(mHiddenNavPanic);
        }
    }

    private boolean isPowerKeyDownPanic(boolean isScreenOn, long time, boolean inImmersiveMode,
            boolean navBarEmpty) {
        if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
            // turning the screen back on within the panic threshold
            return !mImmersiveConfirmationWindowExists;
        }
        if (isScreenOn && inImmersiveMode && !navBarEmpty) {
            // turning the screen off, remember if we were in immersive mode
            mPanicTime = time;
        } else {
            mPanicTime = 0;
        }
        return false;
    }

    /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */
    public void onUserActivityEventTouch() {
        // If there is keyguard, it may use INPUT_FEATURE_DISABLE_USER_ACTIVITY (InputDispatcher
        // won't trigger user activity for touch). So while the device is not interactive, the user
        // event is only sent explicitly from SystemUI.
        if (mAwake) return;
        // If the event is triggered while the display is not awake, the screen may be showing
        // dozing UI such as AOD or overlay UI of under display fingerprint. Then set the animating
        // state temporarily to make the process more responsive.
        final WindowState w = mNotificationShade;
        mService.mAtmService.setProcessAnimatingWhileDozing(w != null ? w.getProcess() : null);
    }

    RefreshRatePolicy getRefreshRatePolicy() {
        return mRefreshRatePolicy;
    }

    void dump(String prefix, PrintWriter pw) {
        pw.print(prefix); pw.println("DisplayPolicy");
        prefix += "  ";
        final String prefixInner = prefix + "  ";
        pw.print(prefix);
        pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
        pw.print(" mDeskDockEnablesAccelerometer=");
        pw.println(mDeskDockEnablesAccelerometer);
        pw.print(prefix); pw.print("mDockMode="); pw.print(Intent.dockStateToString(mDockMode));
        pw.print(" mLidState="); pw.println(WindowManagerFuncs.lidStateToString(mLidState));
        pw.print(prefix); pw.print("mAwake="); pw.print(mAwake);
        pw.print(" mScreenOnEarly="); pw.print(mScreenOnEarly);
        pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
        pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
        pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
        pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged);
        if (mLastDisableFlags != 0) {
            pw.print(prefix); pw.print("mLastDisableFlags=0x");
            pw.println(Integer.toHexString(mLastDisableFlags));
        }
        if (mLastAppearance != 0) {
            pw.print(prefix); pw.print("mLastAppearance=");
            pw.println(ViewDebug.flagsToString(InsetsFlags.class, "appearance", mLastAppearance));
        }
        if (mLastBehavior != 0) {
            pw.print(prefix); pw.print("mLastBehavior=");
            pw.println(ViewDebug.flagsToString(InsetsFlags.class, "behavior", mLastBehavior));
        }
        pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
        pw.print(" mDreamingLockscreen="); pw.println(mDreamingLockscreen);
        if (mStatusBar != null) {
            pw.print(prefix); pw.print("mStatusBar="); pw.println(mStatusBar);
        }
        if (mNotificationShade != null) {
            pw.print(prefix); pw.print("mExpandedPanel="); pw.println(mNotificationShade);
        }
        pw.print(prefix); pw.print("isKeyguardShowing="); pw.println(isKeyguardShowing());
        if (mNavigationBar != null) {
            pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar);
            pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode);
            pw.print(prefix); pw.print("mNavigationBarCanMove="); pw.println(mNavigationBarCanMove);
            pw.print(prefix); pw.print("mHasBottomNavigationBar=");
            pw.println(mHasBottomNavigationBar);
        }
        if (mLeftGestureHost != null) {
            pw.print(prefix); pw.print("mLeftGestureHost="); pw.println(mLeftGestureHost);
        }
        if (mTopGestureHost != null) {
            pw.print(prefix); pw.print("mTopGestureHost="); pw.println(mTopGestureHost);
        }
        if (mRightGestureHost != null) {
            pw.print(prefix); pw.print("mRightGestureHost="); pw.println(mRightGestureHost);
        }
        if (mBottomGestureHost != null) {
            pw.print(prefix); pw.print("mBottomGestureHost="); pw.println(mBottomGestureHost);
        }
        if (mFocusedWindow != null) {
            pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
        }
        if (mTopFullscreenOpaqueWindowState != null) {
            pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
            pw.println(mTopFullscreenOpaqueWindowState);
        }
        if (mSystemUiControllingWindow != null) {
            pw.print(prefix); pw.print("mSystemUiControllingWindow=");
            pw.println(mSystemUiControllingWindow);
        }
        if (!mSystemBarColorApps.isEmpty()) {
            pw.print(prefix); pw.print("mSystemBarColorApps=");
            pw.println(mSystemBarColorApps);
        }
        if (!mRelaunchingSystemBarColorApps.isEmpty()) {
            pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps=");
            pw.println(mRelaunchingSystemBarColorApps);
        }
        if (mNavBarColorWindowCandidate != null) {
            pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
            pw.println(mNavBarColorWindowCandidate);
        }
        if (mNavBarBackgroundWindowCandidate != null) {
            pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate=");
            pw.println(mNavBarBackgroundWindowCandidate);
        }
        if (mLastStatusBarAppearanceRegions != null) {
            pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
            for (int i = mLastStatusBarAppearanceRegions.length - 1; i >= 0; i--) {
                pw.print(prefixInner);  pw.println(mLastStatusBarAppearanceRegions[i]);
            }
        }
        if (mLastLetterboxDetails != null) {
            pw.print(prefix); pw.println("mLastLetterboxDetails=");
            for (int i = mLastLetterboxDetails.length - 1; i >= 0; i--) {
                pw.print(prefixInner);  pw.println(mLastLetterboxDetails[i]);
            }
        }
        if (!mStatusBarBackgroundWindows.isEmpty()) {
            pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
            for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
                final WindowState win = mStatusBarBackgroundWindows.get(i);
                pw.print(prefixInner);  pw.println(win);
            }
        }
        if (mShowingTransientInsetsTypes != 0) {
            pw.print(prefix);
            pw.print("mShowingTransientInsetsTypes=");
            pw.println(WindowInsets.Type.toString(mShowingTransientInsetsTypes));
        }
        if (mShowingPermanentInsetsTypes != 0) {
            pw.print(prefix);
            pw.print("mShowingPermanentInsetsTypes=");
            pw.println(WindowInsets.Type.toString(mShowingPermanentInsetsTypes));
        }
        if (mHidingPermanentInsetsTypes != 0) {
            pw.print(prefix);
            pw.print("mHidingPermanentInsetsTypes=");
            pw.println(WindowInsets.Type.toString(mHidingPermanentInsetsTypes));
        }
        if (!mSystemBarVisibilityOverrideMap.isEmpty()) {
            pw.print(prefix);
            pw.println("mSystemBarVisibilityOverrideMap:");
            for (int i = mSystemBarVisibilityOverrideMap.size() - 1; i >= 0; i--) {
                pw.print(prefix);
                pw.print("  ");
                pw.println(mSystemBarVisibilityOverrideMap.valueAt(i));
            }
        }
        pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
        pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed);
        pw.print(prefix); pw.print("mForceShowNavigationBarEnabled=");
        pw.print(mForceShowNavigationBarEnabled);
        pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
        pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
        pw.println(mRemoteInsetsControllerControlsSystemBars);
        pw.print(prefix); pw.println("mDecorInsetsInfo:");
        mDecorInsets.dump(prefixInner, pw);
        if (mCachedDecorInsets != null) {
            pw.print(prefix); pw.println("mCachedDecorInsets:");
            mCachedDecorInsets.mDecorInsets.dump(prefixInner, pw);
        }
        if (!CLIENT_TRANSIENT) {
            mSystemGestures.dump(pw, prefix);
        }
    }

    private boolean supportsPointerLocation() {
        return mDisplayContent.isDefaultDisplay || !mDisplayContent.isPrivate();
    }

    void setPointerLocationEnabled(boolean pointerLocationEnabled) {
        if (!supportsPointerLocation()) {
            return;
        }

        mHandler.sendEmptyMessage(pointerLocationEnabled
                ? MSG_ENABLE_POINTER_LOCATION : MSG_DISABLE_POINTER_LOCATION);
    }

    private void enablePointerLocation() {
        if (mPointerLocationView != null) {
            return;
        }

        mPointerLocationView = new PointerLocationView(mContext);
        mPointerLocationView.setPrintCoords(false);
        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
        lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
        lp.setFitInsetsTypes(0);
        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        if (ActivityManager.isHighEndGfx()) {
            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            lp.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
        }
        lp.format = PixelFormat.TRANSLUCENT;
        lp.setTitle("PointerLocation - display " + getDisplayId());
        lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
        final WindowManager wm = mContext.getSystemService(WindowManager.class);
        wm.addView(mPointerLocationView, lp);
        mDisplayContent.registerPointerEventListener(mPointerLocationView);
    }

    private void disablePointerLocation() {
        if (mPointerLocationView == null) {
            return;
        }

        if (!mDisplayContent.isRemoved()) {
            mDisplayContent.unregisterPointerEventListener(mPointerLocationView);
        }

        final WindowManager wm = mContext.getSystemService(WindowManager.class);
        wm.removeView(mPointerLocationView);
        mPointerLocationView = null;
    }

    /**
     * Check if the window could be excluded from checking if the display has content.
     *
     * @param w WindowState to check if should be excluded.
     * @return True if the window type is PointerLocation which is excluded.
     */
    boolean isWindowExcludedFromContent(WindowState w) {
        if (w != null && mPointerLocationView != null) {
            return w.mClient == mPointerLocationView.getWindowToken();
        }

        return false;
    }

    void release() {
        mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener);
        mHandler.post(mGestureNavigationSettingsObserver::unregister);
        mHandler.post(mForceShowNavBarSettingsObserver::unregister);
        if (mService.mPointerLocationEnabled) {
            setPointerLocationEnabled(false);
        }
    }

    @VisibleForTesting
    static boolean isOverlappingWithNavBar(@NonNull WindowState win) {
        if (!win.isVisible()) {
            return false;
        }

        // When the window is dimming means it's requesting dim layer to its host container, so
        // checking whether it's overlapping with a navigation bar by its container's bounds.
        return intersectsAnyInsets(win.isDimming() ? win.getBounds() : win.getFrame(),
                win.getInsetsState(), Type.navigationBars());
    }

    /**
     * Returns whether the given {@param bounds} intersects with any insets of the
     * provided {@param insetsType}.
     */
    private static boolean intersectsAnyInsets(Rect bounds, InsetsState insetsState,
            @InsetsType int insetsType) {
        for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
            final InsetsSource source = insetsState.sourceAt(i);
            if ((source.getType() & insetsType) == 0) {
                continue;
            }
            if (Rect.intersects(bounds, source.getFrame())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return Whether we should attach navigation bar to the app during transition.
     */
    boolean shouldAttachNavBarToAppDuringTransition() {
        return mShouldAttachNavBarToAppDuringTransition && mNavigationBar != null;
    }

    /**
     * @return the display's UI mode. If there's no override for this display, returns undefined,
     *    which will be overridden by the global configuration.
     */
    int getDisplayUiMode() {
        return mDisplayUiMode;
    }

    /**
     * Updates this display's UI mode.
     */
    void onDisplayUiModeChanged() {
        final UiModeManagerInternal uiModeManagerInternal = getUiModeManagerInternal();
        mDisplayUiMode = uiModeManagerInternal != null
                ? uiModeManagerInternal.getDisplayUiMode(getDisplayId())
                : (Configuration.UI_MODE_TYPE_UNDEFINED | Configuration.UI_MODE_NIGHT_UNDEFINED);
    }
}
