| /* |
| * 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.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR; |
| import static android.view.Display.TYPE_INTERNAL; |
| 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_IMMERSIVE_CONFIRMATION; |
| import static android.view.ViewRootImpl.CLIENT_TRANSIENT; |
| 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.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.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_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_OKAY; |
| import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED; |
| import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE; |
| import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; |
| import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; |
| import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; |
| import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT; |
| import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; |
| |
| import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; |
| import static com.android.internal.protolog.ProtoLogGroup.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.content.Context; |
| import android.content.Intent; |
| 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.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.util.ArraySet; |
| import android.util.PrintWriterPrinter; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| import android.view.DisplayInfo; |
| import android.view.Gravity; |
| import android.view.InsetsFlags; |
| import android.view.InsetsFrameProvider; |
| import android.view.InsetsSource; |
| import android.view.InsetsState; |
| import android.view.Surface; |
| import android.view.View; |
| import android.view.ViewDebug; |
| 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 com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.policy.ForceShowNavBarSettingsObserver; |
| import com.android.internal.policy.GestureNavigationSettingsObserver; |
| import com.android.internal.policy.ScreenDecorationsUtils; |
| import com.android.internal.protolog.common.ProtoLog; |
| import com.android.internal.statusbar.LetterboxDetails; |
| import com.android.internal.util.ScreenshotHelper; |
| import com.android.internal.util.ScreenshotRequest; |
| 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.UiThread; |
| import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition; |
| 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 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 ImmersiveModeConfirmation mImmersiveModeConfirmation; |
| private final ScreenshotHelper mScreenshotHelper; |
| |
| private final Object mServiceAcquireLock = new Object(); |
| private long mPanicTime; |
| private final long mPanicThresholdMs; |
| private StatusBarManagerInternal mStatusBarManagerInternal; |
| |
| @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; |
| |
| StatusBarManagerInternal getStatusBarManagerInternal() { |
| synchronized (mServiceAcquireLock) { |
| if (mStatusBarManagerInternal == null) { |
| mStatusBarManagerInternal = |
| LocalServices.getService(StatusBarManagerInternal.class); |
| } |
| return mStatusBarManagerInternal; |
| } |
| } |
| |
| // 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 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; |
| @NavigationBarPosition |
| private int mNavigationBarPosition = NAV_BAR_BOTTOM; |
| |
| 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; |
| |
| private @InsetsType int mForciblyShownTypes; |
| |
| 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 mFocusedApp; |
| 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; |
| } |
| return provider.getSource().calculateInsets(win.getBounds(), |
| 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() { |
| |
| 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.mAppTransition.registerListenerLocked(mAppTransitionListener); |
| displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener); |
| if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { |
| mImmersiveModeConfirmation = null; |
| } else { |
| mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper, |
| mService.mVrModeEnabled, mCanSystemBarsBeShownByUser); |
| } |
| |
| // TODO: Make it can take screenshot on external display |
| mScreenshotHelper = displayContent.isDefaultDisplay |
| ? new ScreenshotHelper(mContext) : null; |
| |
| 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.supportsSystemDecorations(); |
| } |
| |
| mRefreshRatePolicy = new RefreshRatePolicy(mService, |
| mDisplayContent.getDisplayInfo(), |
| mService.mHighRefreshRateDenylist); |
| |
| mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler, |
| 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; |
| } |
| |
| 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; |
| } |
| |
| public void setAwake(boolean awake) { |
| synchronized (mLock) { |
| if (awake == mAwake) { |
| return; |
| } |
| mAwake = awake; |
| if (!mDisplayContent.isDefaultDisplay) { |
| return; |
| } |
| if (awake) { |
| mService.mAtmService.mVisibleDozeUiProcess = null; |
| } else if (mScreenOnFully && mNotificationShade != null) { |
| // Screen is still on, so it may be showing an always-on UI. |
| mService.mAtmService.mVisibleDozeUiProcess = mNotificationShade.getProcess(); |
| } |
| mService.mAtmService.mKeyguardController.updateDeferTransitionForAod( |
| mAwake /* waiting */); |
| } |
| } |
| |
| 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; |
| } |
| |
| public void screenTurnedOn(ScreenOnListener screenOnListener) { |
| WindowProcessController visibleDozeUiProcess = null; |
| synchronized (mLock) { |
| 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); |
| } |
| } |
| |
| public void screenTurnedOff() { |
| synchronized (mLock) { |
| 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; |
| break; |
| |
| case TYPE_BASE_APPLICATION: |
| |
| // A non-translucent main app window isn't allowed to fit insets, as it would create |
| // a hole on the display! |
| if (attrs.isFullscreen() && win.mActivityRecord != null |
| && win.mActivityRecord.fillsParent() |
| && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0 |
| && attrs.getFitInsetsTypes() != 0) { |
| throw new IllegalArgumentException("Illegal attributes: Main activity window" |
| + " that isn't translucent trying to fit insets: " |
| + attrs.getFitInsetsTypes() |
| + " attrs=" + attrs); |
| } |
| break; |
| } |
| |
| 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. |
| * |
| * Currently enforces that two 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) { |
| ActivityTaskManagerService.enforceTaskPermission("DisplayPolicy"); |
| } |
| |
| switch (attrs.type) { |
| case TYPE_STATUS_BAR: |
| mContext.enforcePermission( |
| android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, |
| "DisplayPolicy"); |
| if (mStatusBar != null && mStatusBar.isAlive()) { |
| return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; |
| } |
| break; |
| case TYPE_NOTIFICATION_SHADE: |
| mContext.enforcePermission( |
| android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, |
| "DisplayPolicy"); |
| if (mNotificationShade != null) { |
| if (mNotificationShade.isAlive()) { |
| return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; |
| } |
| } |
| break; |
| case TYPE_NAVIGATION_BAR: |
| mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE, |
| callingPid, callingUid, "DisplayPolicy"); |
| if (mNavigationBar != null && mNavigationBar.isAlive()) { |
| return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; |
| } |
| break; |
| case TYPE_NAVIGATION_BAR_PANEL: |
| mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE, |
| callingPid, callingUid, "DisplayPolicy"); |
| break; |
| case TYPE_STATUS_BAR_ADDITIONAL: |
| case TYPE_STATUS_BAR_SUB_PANEL: |
| case TYPE_VOICE_INTERACTION_STARTING: |
| mContext.enforcePermission( |
| android.Manifest.permission.STATUS_BAR_SERVICE, 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( |
| android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid, |
| "DisplayPolicy"); |
| } |
| } |
| return ADD_OKAY; |
| } |
| |
| /** |
| * 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, WindowContainer, Rect, Integer> frameProvider = |
| getFrameProvider(win, i, INSETS_OVERRIDE_INDEX_INVALID); |
| final InsetsFrameProvider.InsetsSizeOverride[] overrides = |
| provider.getInsetsSizeOverrides(); |
| final SparseArray<TriFunction<DisplayFrames, WindowContainer, 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.setWindowContainer(win, frameProvider, overrideProviders); |
| mInsetsSourceWindowsExceptIme.add(win); |
| } |
| } |
| } |
| |
| private static TriFunction<DisplayFrames, WindowContainer, Rect, Integer> getFrameProvider( |
| WindowState win, int index, int overrideIndex) { |
| return (displayFrames, windowContainer, 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(windowContainer.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. |
| */ |
| private 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(); |
| } |
| } |
| |
| TriFunction<DisplayFrames, WindowContainer, Rect, Integer> getImeSourceFrameProvider() { |
| return (displayFrames, windowContainer, inOutFrame) -> { |
| WindowState windowState = windowContainer.asWindowState(); |
| if (windowState == null) { |
| throw new IllegalArgumentException("IME insets must be provided by a window."); |
| } |
| |
| if (!ENABLE_HIDE_IME_CAPTION_BAR && mNavigationBar != null |
| && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) { |
| // In gesture navigation, nav bar frame is larger than frame to calculate insets. |
| // IME should not provide frame which is smaller than the nav bar frame. Otherwise, |
| // nav bar might be overlapped with the content of the client when IME is shown. |
| sTmpRect.set(inOutFrame); |
| sTmpRect.intersectUnchecked(mNavigationBar.getFrame()); |
| inOutFrame.inset(windowState.mGivenContentInsets); |
| inOutFrame.union(sTmpRect); |
| } else { |
| 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.setWindowContainer( |
| null /* windowContainer */, |
| 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; |
| } |
| |
| // TODO (b/277891341): Remove this and related usages. This has been replaced by |
| // InsetsSource#FLAG_FORCE_CONSUMING. |
| public boolean areSystemBarsForcedConsumedLw() { |
| return false; |
| } |
| |
| /** |
| * 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(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. |
| */ |
| public void layoutWindowLw(WindowState win, WindowState attached, 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; |
| mForciblyShownTypes = 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. |
| */ |
| public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs, |
| WindowState attached, WindowState imeTarget) { |
| if (attrs.type == TYPE_NAVIGATION_BAR) { |
| // Keep mNavigationBarPosition updated to make sure the transient detection and bar |
| // color control is working correctly. |
| final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames; |
| mNavigationBarPosition = navigationBarPosition(displayFrames.mRotation); |
| } |
| final boolean affectsSystemUi = win.canAffectSystemUiFlags(); |
| if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi); |
| applyKeyguardPolicy(win, imeTarget); |
| |
| // 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, 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) { |
| mForciblyShownTypes |= 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) { |
| // Record the top-fullscreen-app-window which will be used to determine system UI |
| // controlling window. |
| if (mTopFullscreenOpaqueWindowState == null) { |
| 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 |
| .mLetterboxUiController.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 areTypesForciblyShownTransiently(@InsetsType int types) { |
| return (mForciblyShownTypes & types) == types; |
| } |
| |
| /** |
| * Applies the keyguard policy to a specific window. |
| * |
| * @param win The window to apply the keyguard policy. |
| * @param imeTarget The current IME target window. |
| */ |
| private void applyKeyguardPolicy(WindowState win, WindowState imeTarget) { |
| if (win.canBeHiddenByKeyguard()) { |
| final boolean shouldBeHiddenByKeyguard = shouldBeHiddenByKeyguard(win, imeTarget); |
| 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, WindowState imeTarget) { |
| if (!mDisplayContent.isDefaultDisplay || !isKeyguardShowing()) { |
| return false; |
| } |
| |
| // Show IME over the keyguard if the target allows it. |
| final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible() |
| && win.mIsImWindow && (imeTarget.canShowWhenLocked() |
| || !imeTarget.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().areTypesForciblyShowing(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); |
| 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; |
| } |
| |
| @VisibleForTesting |
| Context getContext() { |
| return mContext; |
| } |
| |
| Context getSystemUiContext() { |
| return mUiContext; |
| } |
| |
| @VisibleForTesting |
| void setCanSystemBarsBeShownByUser(boolean canBeShown) { |
| mCanSystemBarsBeShownByUser = canBeShown; |
| } |
| |
| void notifyDisplayReady() { |
| mHandler.post(() -> { |
| final int displayId = getDisplayId(); |
| StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); |
| if (statusBar != null) { |
| statusBar.onDisplayReady(displayId); |
| } |
| final WallpaperManagerInternal wpMgr = LocalServices |
| .getService(WallpaperManagerInternal.class); |
| if (wpMgr != null) { |
| wpMgr.onDisplayReady(displayId); |
| } |
| }); |
| } |
| |
| /** |
| * Return corner radius in pixels that should be used on windows in order to cover the display. |
| * |
| * The radius is only valid for internal displays, since the corner radius of external displays |
| * is not known at build time when window corners are configured. |
| */ |
| float getWindowCornerRadius() { |
| return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL |
| ? ScreenDecorationsUtils.getWindowCornerRadius(mContext) : 0f; |
| } |
| |
| boolean isShowingDreamLw() { |
| return mShowingDream; |
| } |
| |
| /** The latest insets and frames for screen configuration calculation. */ |
| static class DecorInsets { |
| static class Info { |
| /** |
| * 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(); |
| |
| /** The display frame available after excluding {@link #mNonDecorInsets}. */ |
| final Rect mNonDecorFrame = new Rect(); |
| |
| /** |
| * The available (stable) screen size that we should report for the configuration. |
| * This must be no larger than {@link #mNonDecorFrame}; it may be smaller than that |
| * to account for more transient decoration like a status bar. |
| */ |
| final Rect mConfigFrame = new Rect(); |
| |
| /** The count of insets sources when calculating this info. */ |
| int mLastInsetsSourceCount; |
| |
| 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 decor = insetsState.calculateInsets(displayFrame, |
| dc.mWmService.mDecorTypes, true /* ignoreVisibility */); |
| final Insets configInsets = insetsState.calculateInsets(displayFrame, |
| dc.mWmService.mConfigTypes, true /* ignoreVisibility */); |
| mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom); |
| mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right, |
| configInsets.bottom); |
| mNonDecorFrame.set(displayFrame); |
| mNonDecorFrame.inset(mNonDecorInsets); |
| mConfigFrame.set(displayFrame); |
| mConfigFrame.inset(mConfigInsets); |
| mLastInsetsSourceCount = dc.getDisplayPolicy().mInsetsSourceWindowsExceptIme.size(); |
| mNeedUpdate = false; |
| } |
| |
| void set(Info other) { |
| mNonDecorInsets.set(other.mNonDecorInsets); |
| mConfigInsets.set(other.mConfigInsets); |
| mNonDecorFrame.set(other.mNonDecorFrame); |
| mConfigFrame.set(other.mConfigFrame); |
| mLastInsetsSourceCount = other.mLastInsetsSourceCount; |
| mNeedUpdate = false; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder tmpSb = new StringBuilder(32); |
| return "{nonDecorInsets=" + mNonDecorInsets.toShortString(tmpSb) |
| + ", configInsets=" + mConfigInsets.toShortString(tmpSb) |
| + ", nonDecorFrame=" + mNonDecorFrame.toShortString(tmpSb) |
| + ", configFrame=" + mConfigFrame.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 { |
| /** |
| * 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; |
| |
| Cache(DisplayContent dc) { |
| mDecorInsets = new DecorInsets(dc); |
| } |
| |
| boolean canPreserve() { |
| return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent |
| .mTransitionController.inTransition(mPreserveId); |
| } |
| } |
| } |
| |
| /** |
| * 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.mConfigFrame.equals(currentInfo.mConfigFrame)) { |
| // Even if the config frame is not changed in current rotation, it may change the |
| // insets in other rotations if the source count is changed. |
| if (newInfo.mLastInsetsSourceCount != currentInfo.mLastInsetsSourceCount) { |
| for (int i = mDecorInsets.mInfoForRotation.length - 1; i >= 0; i--) { |
| if (i != rotation) { |
| final boolean flipSize = (i + rotation) % 2 == 1; |
| final int w = flipSize ? dh : dw; |
| final int h = flipSize ? dw : dh; |
| mDecorInsets.mInfoForRotation[i].update(mDisplayContent, i, w, h); |
| } |
| } |
| mDecorInsets.mInfoForRotation[rotation].set(newInfo); |
| } |
| return false; |
| } |
| if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() |
| && !mDisplayContent.isSleeping()) { |
| mCachedDecorInsets = null; |
| } |
| mDecorInsets.invalidate(); |
| mDecorInsets.mInfoForRotation[rotation].set(newInfo); |
| return true; |
| } |
| |
| 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; |
| if (mCachedDecorInsets == null) { |
| mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent); |
| } else { |
| prevCache = new DecorInsets(mDisplayContent); |
| prevCache.setTo(mCachedDecorInsets.mDecorInsets); |
| } |
| // 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); |
| // Switch current to previous cache. |
| if (prevCache != null) { |
| mDecorInsets.setTo(prevCache); |
| mCachedDecorInsets.mActive = true; |
| } |
| } |
| |
| /** |
| * 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(); |
| // The validator will run after the transition is finished. So if the insets are changed |
| // during the transition, it can update to the latest state. |
| mDisplayContent.mTransitionController.mStateValidators.add(() -> { |
| // The insets provider client may defer to change its window until screen is on. So |
| // only validate when awake to avoid the cache being always dropped. |
| if (!mDisplayContent.isSleeping() && updateDecorInsetsInfo()) { |
| Slog.d(TAG, "Insets changed after display switch transition"); |
| mDisplayContent.sendNewConfiguration(); |
| } |
| }); |
| } |
| |
| @NavigationBarPosition |
| int navigationBarPosition(int displayRotation) { |
| if (mNavigationBar != null) { |
| final int gravity = mNavigationBar.mAttrs.forRotation(displayRotation).gravity; |
| switch (gravity) { |
| case Gravity.LEFT: |
| return NAV_BAR_LEFT; |
| case Gravity.RIGHT: |
| return NAV_BAR_RIGHT; |
| default: |
| return NAV_BAR_BOTTOM; |
| } |
| } |
| return NAV_BAR_INVALID; |
| } |
| |
| /** |
| * 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(), false /* fromIme */, |
| 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, false /* fromIme */, |
| null /* statsToken */); |
| } else { |
| // Restore visibilities and positions of system bars. |
| controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), |
| false /* fromIme */, 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"); |
| } |
| } |
| } |
| if (CLIENT_IMMERSIVE_CONFIRMATION || CLIENT_TRANSIENT) { |
| mStatusBarManagerInternal.confirmImmersivePrompt(); |
| } else { |
| mImmersiveModeConfirmation.confirmCurrentPrompt(); |
| } |
| } |
| |
| 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(); |
| } |
| } |
| |
| void resetSystemBarAttributes() { |
| mLastDisableFlags = 0; |
| updateSystemBarAttributes(); |
| } |
| |
| void updateSystemBarAttributes() { |
| // 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 ? mFocusedWindow |
| : mTopFullscreenOpaqueWindowState; |
| if (winCandidate == null) { |
| return; |
| } |
| |
| // Immersive mode confirmation should never affect the system bar visibility, otherwise |
| // it will unhide the navigation bar and hide itself. |
| if ((winCandidate.getAttrs().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()) { |
| // 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) { |
| return; |
| } |
| } |
| final WindowState win = winCandidate; |
| mSystemUiControllingWindow = win; |
| |
| final int displayId = getDisplayId(); |
| final int disableFlags = win.getDisableFlags(); |
| 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, mNavigationBarPosition); |
| final boolean isNavbarColorManagedByIme = |
| navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow; |
| final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance, |
| navColorWin) | opaqueAppearance; |
| final WindowState navBarControlWin = topAppHidesSystemBar(Type.navigationBars()) |
| ? mTopFullscreenOpaqueWindowState |
| : win; |
| final int behavior = navBarControlWin.mAttrs.insetsFlags.behavior; |
| final String focusedApp = win.mAttrs.packageName; |
| final boolean isFullscreen = !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.toString(); |
| callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags, |
| cause)); |
| } |
| final @InsetsType int requestedVisibleTypes = win.getRequestedVisibleTypes(); |
| final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()]; |
| mLetterboxDetails.toArray(letterboxDetails); |
| if (mLastAppearance == appearance |
| && mLastBehavior == behavior |
| && mLastRequestedVisibleTypes == requestedVisibleTypes |
| && Objects.equals(mFocusedApp, focusedApp) |
| && 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; |
| mFocusedApp = focusedApp; |
| mLastFocusIsFullscreen = isFullscreen; |
| mLastStatusBarAppearanceRegions = statusBarAppearanceRegions; |
| mLastLetterboxDetails = letterboxDetails; |
| callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId, |
| appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior, |
| requestedVisibleTypes, focusedApp, 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, |
| @NavigationBarPosition int navBarPosition) { |
| // 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() |
| && navBarPosition == NAV_BAR_BOTTOM |
| && (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 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 (navColorWin == null || !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(WindowState win, int disableFlags) { |
| final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); |
| final boolean adjacentTasksVisible = |
| defaultTaskDisplayArea.getRootTask(task -> task.isVisible() |
| && task.getTopLeafTask().getAdjacentTask() != null) |
| != null; |
| final boolean freeformRootTaskVisible = |
| defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); |
| |
| getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible); |
| |
| final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars()); |
| if (getStatusBar() != null) { |
| final StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); |
| if (statusBar != null) { |
| statusBar.setTopAppHidesStatusBar(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.getRootDisplayArea(); |
| final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId; |
| if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { |
| mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, |
| isImmersiveMode, |
| mService.mPolicy.isUserSetupComplete(), |
| isNavBarEmpty(disableFlags)); |
| } else { |
| // TODO (b/277290737): Move this to the client side, instead of using a proxy. |
| callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(rootDisplayAreaId, |
| isImmersiveMode)); |
| } |
| } |
| |
| // Show transient bars for panic if needed. |
| final boolean requestHideNavBar = !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, 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) { |
| if (win == null) { |
| return true; |
| } |
| |
| final boolean drawsSystemBars = |
| (win.getAttrs().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; |
| final boolean forceDrawsSystemBars = |
| (win.getAttrs().privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; |
| |
| return forceDrawsSystemBars || drawsSystemBars; |
| } |
| |
| /** @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); |
| 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, |
| mNavigationBarPosition); |
| 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, @NavigationBarPosition int navBarPosition) { |
| if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM |
| && drawsBarBackground(imeWindow)) { |
| return imeWindow; |
| } |
| if (drawsBarBackground(candidate)) { |
| 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 = false; |
| if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { |
| panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, |
| SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow), |
| isNavBarEmpty(mLastDisableFlags)); |
| } else { |
| 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; |
| } |
| |
| void onVrStateChangedLw(boolean enabled) { |
| if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { |
| mImmersiveModeConfirmation.onVrStateChangedLw(enabled); |
| } |
| } |
| |
| /** |
| * Called when the state of lock task mode changes. This should be used to disable immersive |
| * mode confirmation. |
| * |
| * @param lockTaskState the new lock task mode state. One of |
| * {@link ActivityManager#LOCK_TASK_MODE_NONE}, |
| * {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, |
| * {@link ActivityManager#LOCK_TASK_MODE_PINNED}. |
| */ |
| public void onLockTaskStateChangedLw(int lockTaskState) { |
| if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { |
| mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState); |
| } |
| } |
| |
| /** 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); |
| } |
| |
| boolean onSystemUiSettingsChanged() { |
| if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) { |
| return false; |
| } else { |
| return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId); |
| } |
| } |
| |
| /** |
| * Request a screenshot be taken. |
| * |
| * @param screenshotType The type of screenshot, for example either |
| * {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or |
| * {@link WindowManager#TAKE_SCREENSHOT_PROVIDED_IMAGE} |
| * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource) |
| */ |
| public void takeScreenshot(int screenshotType, int source) { |
| if (mScreenshotHelper != null) { |
| ScreenshotRequest request = |
| new ScreenshotRequest.Builder(screenshotType, source).build(); |
| mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */); |
| } |
| } |
| |
| 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("mNavigationBarPosition="); |
| pw.println(mNavigationBarPosition); |
| } |
| 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 (!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); |
| } |
| } |
| 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); |
| } |
| |
| pw.print(prefix); pw.println("Looper state:"); |
| mHandler.getLooper().dump(new PrintWriterPrinter(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 (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) { |
| mImmersiveModeConfirmation.release(); |
| } |
| 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 || !source.isVisible()) { |
| 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; |
| } |
| } |