src.com.android.server.wm.WindowState Maven / Gradle / Ivy
Show all versions of android-all Show documentation
/*
* Copyright (C) 2011 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.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_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_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
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_PHONE;
import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
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_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.wm.AnimationSpecProto.MOVE;
import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.DisplayContent.logsGestureExclusionRestrictions;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
import static com.android.server.wm.MoveAnimationSpecProto.FROM;
import static com.android.server.wm.MoveAnimationSpecProto.TO;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT;
import static com.android.server.wm.WindowStateProto.ANIMATOR;
import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
import static com.android.server.wm.WindowStateProto.DESTROYING;
import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_FRAME;
import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
import static com.android.server.wm.WindowStateProto.GLOBAL_SCALE;
import static com.android.server.wm.WindowStateProto.HAS_COMPAT_SCALE;
import static com.android.server.wm.WindowStateProto.HAS_SURFACE;
import static com.android.server.wm.WindowStateProto.IS_ON_SCREEN;
import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY;
import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
import static com.android.server.wm.WindowStateProto.SURFACE_POSITION;
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.TouchOcclusionMode;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeReason;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IApplicationToken;
import android.view.IWindow;
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewTreeObserver;
import android.view.WindowInfo;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.window.ClientWindowFrames;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.utils.CoordinateTransforms;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/** A window in the window manager. */
class WindowState extends WindowContainer implements WindowManagerPolicy.WindowState,
InsetsControlTarget, InputTarget {
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
// The minimal size of a window within the usable area of the freeform root task.
// TODO(multi-window): fix the min sizes when we have minimum width/height support,
// use hard-coded min sizes for now.
static final int MINIMUM_VISIBLE_WIDTH_IN_DP = 48;
static final int MINIMUM_VISIBLE_HEIGHT_IN_DP = 32;
// The thickness of a window resize handle outside the window bounds on the free form workspace
// to capture touch events in that area.
static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
static final int EXCLUSION_LEFT = 0;
static final int EXCLUSION_RIGHT = 1;
final WindowManagerPolicy mPolicy;
final Context mContext;
final Session mSession;
final IWindow mClient;
final int mAppOp;
// UserId and appId of the owner. Don't display windows of non-current user.
final int mOwnerUid;
/**
* Requested userId, if this is not equals with the userId from mOwnerUid, then this window is
* created for secondary user.
* Use this member instead of get userId from mOwnerUid while query for visibility.
*/
final int mShowUserId;
/** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
final boolean mOwnerCanAddInternalSystemWindow;
final WindowId mWindowId;
@NonNull WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
ActivityRecord mActivityRecord;
/** Non-null if this is a starting window. */
StartingData mStartingData;
// mAttrs.flags is tested in animation without being locked. If the bits tested are ever
// modified they will need to be locked.
final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
final DeathRecipient mDeathRecipient;
private boolean mIsChildWindow;
final int mBaseLayer;
final int mSubLayer;
final boolean mLayoutAttached;
final boolean mIsImWindow;
final boolean mIsWallpaper;
private final boolean mIsFloatingLayer;
int mViewVisibility;
/**
* Flags to disable system UI functions. This can only be set by the one which has the
* status bar permission.
*
* @see View.SystemUiVisibility
*/
int mDisableFlags;
/**
* The visibility flag of the window based on policy like {@link WindowManagerPolicy}.
* Normally set by calling {@link #show} and {@link #hide}.
*
* TODO: b/131253938 This will eventually be split into individual visibility policy flags.
*/
static final int LEGACY_POLICY_VISIBILITY = 1;
/**
* The visibility flag that determines whether this window is visible for the current user.
*/
private static final int VISIBLE_FOR_USER = 1 << 1;
private static final int POLICY_VISIBILITY_ALL = VISIBLE_FOR_USER | LEGACY_POLICY_VISIBILITY;
/**
* The Bitwise-or of flags that contribute to visibility of the WindowState
*/
private int mPolicyVisibility = POLICY_VISIBILITY_ALL;
/**
* Whether {@link #LEGACY_POLICY_VISIBILITY} flag should be set after a transition animation.
* For example, {@link #LEGACY_POLICY_VISIBILITY} might be set during an exit animation to hide
* it and then unset when the value of {@link #mLegacyPolicyVisibilityAfterAnim} is false
* after the exit animation is done.
*
* TODO: b/131253938 Determine whether this can be changed to use a visibility flag instead.
*/
boolean mLegacyPolicyVisibilityAfterAnim = true;
// overlay window is hidden because the owning app is suspended
private boolean mHiddenWhileSuspended;
private boolean mAppOpVisibility = true;
boolean mPermanentlyHidden; // the window should never be shown again
// This is a non-system overlay window that is currently force hidden.
private boolean mForceHideNonSystemOverlayWindow;
boolean mAppFreezing;
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
private int mResizeMode;
private boolean mRedrawForSyncReported;
/**
* {@code true} when the client was still drawing for sync when the sync-set was finished or
* cancelled. This can happen if the window goes away during a sync. In this situation we need
* to make sure to still apply the postDrawTransaction when it finishes to prevent the client
* from getting stuck in a bad state.
*/
boolean mClientWasDrawingForSync = false;
/**
* Special mode that is intended only for the rounded corner overlay: during rotation
* transition, we un-rotate the window token such that the window appears as it did before the
* rotation.
*/
final boolean mForceSeamlesslyRotate;
SeamlessRotator mPendingSeamlessRotate;
long mFinishSeamlessRotateFrameNumber;
private RemoteCallbackList mFocusCallbacks;
/**
* The window size that was requested by the application. These are in
* the application's coordinate space (without compatibility scale applied).
*/
int mRequestedWidth;
int mRequestedHeight;
private int mLastRequestedWidth;
private int mLastRequestedHeight;
int mLayer;
boolean mHaveFrame;
boolean mObscured;
int mLayoutSeq = -1;
/** @see #addEmbeddedDisplayContent(DisplayContent dc) */
private final ArraySet mEmbeddedDisplayContents = new ArraySet<>();
/**
* Used to store last reported to client configuration and check if we have newer available.
* We'll send configuration to client only if it is different from the last applied one and
* client won't perform unnecessary updates.
*/
private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
/** @see #isLastConfigReportedToClient() */
private boolean mLastConfigReportedToClient;
private final Configuration mTempConfiguration = new Configuration();
/**
* Set to true if we are waiting for this window to receive its
* given internal insets before laying out other windows based on it.
*/
boolean mGivenInsetsPending;
/**
* These are the content insets that were given during layout for
* this window, to be applied to windows behind it.
*/
final Rect mGivenContentInsets = new Rect();
/**
* These are the visible insets that were given during layout for
* this window, to be applied to windows behind it.
*/
final Rect mGivenVisibleInsets = new Rect();
/**
* This is the given touchable area relative to the window frame, or null if none.
*/
final Region mGivenTouchableRegion = new Region();
/**
* Flag indicating whether the touchable region should be adjusted by
* the visible insets; if false the area outside the visible insets is
* NOT touchable, so we must use those to adjust the frame during hit
* tests.
*/
int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
// Current transformation being applied.
float mGlobalScale=1;
float mLastGlobalScale=1;
float mInvGlobalScale=1;
float mOverrideScale = 1;
float mHScale=1, mVScale=1;
float mLastHScale=1, mLastVScale=1;
// An offset in pixel of the surface contents from the window position. Used for Wallpaper
// to provide the effect of scrolling within a large surface. We just use these values as
// a cache.
int mXOffset = 0;
int mYOffset = 0;
// A scale factor for the surface contents, that will be applied from the center of the visible
// region.
float mWallpaperScale = 1f;
final Matrix mTmpMatrix = new Matrix();
final float[] mTmpMatrixArray = new float[9];
private final WindowFrames mWindowFrames = new WindowFrames();
private final ClientWindowFrames mClientWindowFrames = new ClientWindowFrames();
/** The frames used to compute a temporal layout appearance. */
private WindowFrames mSimulatedWindowFrames;
/** Usually the same as {@link #getBounds()}. */
private final Rect mInsetFrame = new Rect();
/**
* List of rects where system gestures should be ignored.
*
* Coordinates are relative to the window's position.
*/
private final List mExclusionRects = new ArrayList<>();
// 0 = left, 1 = right
private final int[] mLastRequestedExclusionHeight = {0, 0};
private final int[] mLastGrantedExclusionHeight = {0, 0};
private final long[] mLastExclusionLogUptimeMillis = {0, 0};
private boolean mLastShownChangedReported;
// If a window showing a wallpaper: the requested offset for the
// wallpaper; if a wallpaper window: the currently applied offset.
float mWallpaperX = -1;
float mWallpaperY = -1;
// If a window showing a wallpaper: the requested zoom out for the
// wallpaper; if a wallpaper window: the currently applied zoom.
float mWallpaperZoomOut = -1;
// If a wallpaper window: whether the wallpaper should be scaled when zoomed, if set
// to false, mWallpaperZoom will be ignored here and just passed to the WallpaperService.
boolean mShouldScaleWallpaper;
// If a window showing a wallpaper: what fraction of the offset
// range corresponds to a full virtual screen.
float mWallpaperXStep = -1;
float mWallpaperYStep = -1;
// If a window showing a wallpaper: a raw pixel offset to forcibly apply
// to its window; if a wallpaper window: not used.
int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
/**
* This is set after IWindowSession.relayout() has been called at
* least once for the window. It allows us to detect the situation
* where we don't yet have a surface, but should have one soon, so
* we can give the window focus before waiting for the relayout.
*/
boolean mRelayoutCalled;
boolean mInRelayout;
/**
* If the application has called relayout() with changes that can
* impact its window's size, we need to perform a layout pass on it
* even if it is not currently visible for layout. This is set
* when in that case until the layout is done.
*/
boolean mLayoutNeeded;
/**
* If the application is not currently visible but requires a layout,
* then make sure we call performSurfacePlacement as well. This is set
* in layout if mLayoutNeeded is set until surface placement is done.
*/
boolean mSurfacePlacementNeeded;
/**
* The animation types that will call {@link #onExitAnimationDone} so {@link #mAnimatingExit}
* is guaranteed to be cleared.
*/
static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
| ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
/** Currently running an exit animation? */
boolean mAnimatingExit;
/** Currently on the mDestroySurface list? */
boolean mDestroying;
/** Completely remove from window manager after exit animation? */
boolean mRemoveOnExit;
/**
* Whether the app died while it was visible, if true we might need
* to continue to show it until it's restarted.
*/
boolean mAppDied;
/**
* Set when the orientation is changing and this window has not yet
* been updated for the new orientation.
*/
private boolean mOrientationChanging;
/** The time when the window was last requested to redraw for orientation change. */
private long mOrientationChangeRedrawRequestTime;
/**
* Sometimes in addition to the mOrientationChanging
* flag we report that the orientation is changing
* due to a mismatch in current and reported configuration.
*
* In the case of timeout we still need to make sure we
* leave the orientation changing state though, so we
* use this as a special time out escape hatch.
*/
private boolean mOrientationChangeTimedOut;
/**
* The orientation during the last visible call to relayout. If our
* current orientation is different, the window can't be ready
* to be shown.
*/
int mLastVisibleLayoutRotation = -1;
/**
* Set when we need to report the orientation change to client to trigger a relayout.
*/
boolean mReportOrientationChanged;
/**
* How long we last kept the screen frozen.
*/
int mLastFreezeDuration;
/** Is this window now (or just being) removed? */
boolean mRemoved;
/**
* It is save to remove the window and destroy the surface because the client requested removal
* or some other higher level component said so (e.g. activity manager).
* TODO: We should either have different booleans for the removal reason or use a bit-field.
*/
boolean mWindowRemovalAllowed;
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
InputChannel mInputChannel;
/**
* The token will be assigned to {@link InputWindowHandle#token} if this window can receive
* input event. Note that the token of associated input window handle can be cleared if this
* window becomes unable to receive input, but this field will remain until the input channel
* is actually disposed.
*/
IBinder mInputChannelToken;
// Used to improve performance of toString()
private String mStringNameCache;
private CharSequence mLastTitle;
private boolean mWasExiting;
final WindowStateAnimator mWinAnimator;
boolean mHasSurface = false;
// This window will be replaced due to relaunch. This allows window manager
// to differentiate between simple removal of a window and replacement. In the latter case it
// will preserve the old window until the new one is drawn.
boolean mWillReplaceWindow = false;
// If true, the replaced window was already requested to be removed.
private boolean mReplacingRemoveRequested = false;
// Whether the replacement of the window should trigger app transition animation.
private boolean mAnimateReplacingWindow = false;
// If not null, the window that will be used to replace the old one. This is being set when
// the window is added and unset when this window reports its first draw.
private WindowState mReplacementWindow = null;
// For the new window in the replacement transition, if we have
// requested to replace without animation, then we should
// make sure we also don't apply an enter animation for
// the new window.
boolean mSkipEnterAnimationForSeamlessReplacement = false;
// Whether this window is being moved via the resize API
private boolean mMovedByResize;
/**
* Wake lock for drawing.
* Even though it's slightly more expensive to do so, we will use a separate wake lock
* for each app that is requesting to draw while dozing so that we can accurately track
* who is preventing the system from suspending.
* This lock is only acquired on first use.
*/
private PowerManager.WakeLock mDrawLock;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
private final Point mTmpPoint = new Point();
private final Transaction mTmpTransaction;
/**
* If a window is on a display which has been re-parented to a view in another window,
* use this offset to indicate the correct location.
*/
private final Point mLastReportedDisplayOffset = new Point();
/**
* Whether the window was resized by us while it was gone for layout.
*/
boolean mResizedWhileGone = false;
/**
* During seamless rotation we have two phases, first the old window contents
* are rotated to look as if they didn't move in the new coordinate system. Then we
* have to freeze updates to this layer (to preserve the transformation) until
* the resize actually occurs. This is true from when the transformation is set
* and false until the transaction to resize is sent.
*/
boolean mSeamlesslyRotated = false;
/**
* The insets state of sources provided by windows above the current window.
*/
final InsetsState mAboveInsetsState = new InsetsState();
/**
* The insets sources provided by this window.
*/
final SparseArray mProvidedInsetsSources = new SparseArray<>();
/**
* Surface insets from the previous call to relayout(), used to track
* if we are changing the Surface insets.
*/
final Rect mLastSurfaceInsets = new Rect();
/**
* A flag set by the {@link WindowState} parent to indicate that the parent has examined this
* {@link WindowState} in its overall drawing context. This book-keeping allows the parent to
* make sure all children have been considered.
*/
private boolean mDrawnStateEvaluated;
private final Point mSurfacePosition = new Point();
/**
* A region inside of this window to be excluded from touch.
*/
private final Region mTapExcludeRegion = new Region();
/**
* Used for testing because the real PowerManager is final.
*/
private PowerManagerWrapper mPowerManagerWrapper;
/**
* A frame number in which changes requested in this layout will be rendered.
*/
private long mFrameNumber = -1;
private static final StringBuilder sTmpSB = new StringBuilder();
/**
* Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
* of z-order and 1 otherwise.
*/
private static final Comparator sWindowSubLayerComparator =
new Comparator() {
@Override
public int compare(WindowState w1, WindowState w2) {
final int layer1 = w1.mSubLayer;
final int layer2 = w2.mSubLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
// We insert the child window into the list ordered by
// the sub-layer. For same sub-layers, the negative one
// should go below others; the positive one should go
// above others.
return -1;
}
return 1;
};
};
/**
* Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
* container.
*/
private boolean mIsDimming = false;
private @Nullable InsetsSourceProvider mControllableInsetProvider;
private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
/**
* Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
* (e.g app exiting transition)
*/
private InsetsState mFrozenInsetsState;
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
/**
* This information is passed to SurfaceFlinger to decide which window should have a priority
* when deciding about the refresh rate of the display. All windows have the lowest priority by
* default. The variable is cached, so we do not send too many updates to SF.
*/
int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
/**
* This is the frame rate which is passed to SurfaceFlinger if the window set a
* preferredDisplayModeId or is part of the high refresh rate deny list.
* The variable is cached, so we do not send too many updates to SF.
*/
float mAppPreferredFrameRate = 0f;
static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
private final WindowProcessController mWpcForDisplayAreaConfigChanges;
/**
* We split the draw handlers in to a "pending" and "ready" list, in order to solve
* sequencing problems. Think of it this way, let's say I update a windows orientation
* (in configuration), and then I call applyWithNextDraw. What I'm hoping for is to
* apply with the draw that contains the orientation change. However, since the client
* can call finishDrawing at any time, it could be about to call a previous call to
* finishDrawing (or maybe its already called it, we just haven't handled it). Since this
* frame was already completed it had no time to include the orientation change we made.
* To solve this problem we accumulate draw handlers in mPendingDrawHandlers, and then force
* the client to call relayout. Only the frame post relayout will contain the configuration
* change since the window has to relayout), and so in relayout we drain mPendingDrawHandlers
* into mReadyDrawHandlers. Finally once we get to finishDrawing we know everything in
* mReadyDrawHandlers corresponds to state which was observed by the client and we can
* invoke the consumers.
*
* To see in more detail that this works, we can look at it like this:
*
* The client is in one of these states:
*
* 1. Asleep
* 2. Traversal scheduled
* 3. Starting traversal
* 4. In relayout
* 5. Already drawing
*
* The property we want to implement with the draw handlers is:
* If WM code makes a change to client observable state (e.g. configuration),
* and registers a draw handler (without releasing the WM lock in between),
* the FIRST frame reflecting that change, will be in the Transaction passed
* to the draw handler.
*
* We describe the expected sequencing in each of the possible client states.
* We aim to "prove" that the WM can call applyWithNextDraw() with the client
* starting in any state, and achieve the desired result.
*
* 1. Asleep: The client will wake up in response to MSG_RESIZED, call relayout,
* observe the changes. Relayout will return BLAST_SYNC, and the client will
* send the transaction to finishDrawing. Since the client was asleep. This
* will be the first finishDrawing reflecting the change.
* 2, 3: traversal scheduled/starting traversal: These two states can be considered
* together. Each has two sub-states:
* a) Traversal will call relayout. In this case we proceed like the starting
* from asleep case.
* b) Traversal will not call relayout. In this case, the client produced
* frame will not include the change. Because there is no call to relayout
* there is no call to prepareDrawHandlers() and even if the client calls
* finish drawing the draw handler will not be invoked. We have to wait
* on the client to receive MSG_RESIZED, and will sync on the next frame
* 4. In relayout. In this case we are careful to prepare draw handlers and check
* whether to return the BLAST flag at the end of relayoutWindow. This means if you
* add a draw handler while the client is in relayout, BLAST_SYNC will be
* immediately returned, and the client will submit the frame corresponding
* to what returns from layout. When we prepare the draw handlers we clear the
* flag which would later cause us to report draw for sync. Since we reported
* sync through relayout (by luck the client was calling relayout perhaps)
* there is no need for a MSG_RESIZED.
* 5. Already drawing. This works much like cases 2 and 3. If there is no call to
* finishDrawing then of course the draw handlers will not be invoked and we just
* wait on the next frame for sync. If there is a call to finishDrawing,
* the draw handler will not have been prepared (since we did not call relayout)
* and we will have to wait on the next frame.
*
* By this logic we can see no matter which of the client states we are in when the
* draw handler is added, it will always execute on the expected frame.
*/
private final List> mPendingDrawHandlers
= new ArrayList<>();
private final List> mReadyDrawHandlers
= new ArrayList<>();
private final Consumer mSeamlessRotationFinishedConsumer = t -> {
finishSeamlessRotation(t);
updateSurfacePosition(t);
};
private final Consumer mSetSurfacePositionConsumer = t -> {
if (mSurfaceControl != null && mSurfaceControl.isValid()) {
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
}
};
/**
* @see #setSurfaceTranslationY(int)
*/
private int mSurfaceTranslationY;
/**
* Returns the visibility of the given {@link InternalInsetsType type} requested by the client.
*
* @param type the given {@link InternalInsetsType type}.
* @return {@code true} if the type is requested visible.
*/
@Override
public boolean getRequestedVisibility(@InternalInsetsType int type) {
return mRequestedVisibilities.getVisibility(type);
}
/**
* Returns all the requested visibilities.
*
* @return an {@link InsetsVisibilities} as the requested visibilities.
*/
InsetsVisibilities getRequestedVisibilities() {
return mRequestedVisibilities;
}
/**
* @see #getRequestedVisibility(int)
*/
void setRequestedVisibilities(InsetsVisibilities visibilities) {
mRequestedVisibilities.set(visibilities);
}
/**
* Set a freeze state for the window to ignore dispatching its insets state to the client.
*
* Used to keep the insets state for some use cases. (e.g. app exiting transition)
*/
void freezeInsetsState() {
if (mFrozenInsetsState == null) {
mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */);
}
}
void clearFrozenInsetsState() {
mFrozenInsetsState = null;
}
InsetsState getFrozenInsetsState() {
return mFrozenInsetsState;
}
/**
* Check if the insets state of the window is ready to dispatch to the client when invoking
* {@link InsetsStateController#notifyInsetsChanged}.
*/
boolean isReadyToDispatchInsetsState() {
return isVisibleRequested() && mFrozenInsetsState == null;
}
void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
@Rotation int rotation, boolean requested) {
// Invisible windows and the wallpaper do not participate in the seamless rotation animation
if (!isVisibleNow() || mIsWallpaper) {
return;
}
if (mToken.hasFixedRotationTransform()) {
// The transform of its surface is handled by fixed rotation.
return;
}
final Task task = getTask();
if (task != null && task.inPinnedWindowingMode()) {
// It is handled by PinnedTaskController. Note that the windowing mode of activity
// and windows may still be fullscreen.
return;
}
if (mPendingSeamlessRotate != null) {
oldRotation = mPendingSeamlessRotate.getOldRotation();
}
// Skip performing seamless rotation when the controlled insets is IME with visible state.
if (mControllableInsetProvider != null
&& mControllableInsetProvider.getSource().getType() == ITYPE_IME) {
return;
}
if (mForceSeamlesslyRotate || requested) {
if (mControllableInsetProvider != null) {
mControllableInsetProvider.startSeamlessRotation();
}
mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo(),
false /* applyFixedTransformationHint */);
// The surface position is going to be unrotated according to the last position.
// Make sure the source position is up-to-date.
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
mPendingSeamlessRotate.unrotate(transaction, this);
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
true /* seamlesslyRotated */);
applyWithNextDraw(mSeamlessRotationFinishedConsumer);
}
}
void cancelSeamlessRotation() {
finishSeamlessRotation(getPendingTransaction());
}
void finishSeamlessRotation(SurfaceControl.Transaction t) {
if (mPendingSeamlessRotate == null) {
return;
}
mPendingSeamlessRotate.finish(t, this);
mFinishSeamlessRotateFrameNumber = getFrameNumber();
mPendingSeamlessRotate = null;
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
false /* seamlesslyRotated */);
if (mControllableInsetProvider != null) {
mControllableInsetProvider.finishSeamlessRotation();
}
}
List getSystemGestureExclusion() {
return mExclusionRects;
}
/**
* Sets the system gesture exclusion rects.
*
* @return {@code true} if anything changed
*/
boolean setSystemGestureExclusion(List exclusionRects) {
if (mExclusionRects.equals(exclusionRects)) {
return false;
}
mExclusionRects.clear();
mExclusionRects.addAll(exclusionRects);
return true;
}
boolean isImplicitlyExcludingAllSystemGestures() {
final boolean stickyHideNav =
mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
&& !getRequestedVisibility(ITYPE_NAVIGATION_BAR);
return stickyHideNav && mWmService.mConstants.mSystemGestureExcludedByPreQStickyImmersive
&& mActivityRecord != null && mActivityRecord.mTargetSdk < Build.VERSION_CODES.Q;
}
void setLastExclusionHeights(int side, int requested, int granted) {
boolean changed = mLastGrantedExclusionHeight[side] != granted
|| mLastRequestedExclusionHeight[side] != requested;
if (changed) {
if (mLastShownChangedReported) {
logExclusionRestrictions(side);
}
mLastGrantedExclusionHeight[side] = granted;
mLastRequestedExclusionHeight[side] = requested;
}
}
interface PowerManagerWrapper {
void wakeUp(long time, @WakeReason int reason, String details);
boolean isInteractive();
}
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
@Override
public void wakeUp(long time, @WakeReason int reason, String details) {
service.mPowerManager.wakeUp(time, reason, details);
}
@Override
public boolean isInteractive() {
return service.mPowerManager.isInteractive();
}
});
}
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
super(service);
mTmpTransaction = service.mTransactionFactory.get();
mSession = s;
mClient = c;
mAppOp = appOp;
mToken = token;
mActivityRecord = mToken.asActivityRecord();
mOwnerUid = ownerId;
mShowUserId = showUserId;
mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
mWindowId = new WindowId(this);
mAttrs.copyFrom(a);
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
DeathRecipient deathRecipient = new DeathRecipient();
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
mActivityRecord != null
? mActivityRecord.getInputApplicationHandle(false /* update */) : null,
getDisplayId()));
mInputWindowHandle.setOwnerPid(s.mPid);
mInputWindowHandle.setOwnerUid(s.mUid);
mInputWindowHandle.setName(getName());
mInputWindowHandle.setPackageName(mAttrs.packageName);
mInputWindowHandle.setLayoutParamsType(mAttrs.type);
// Check private trusted overlay flag and window type to set trustedOverlay variable of
// input window handle.
mInputWindowHandle.setTrustedOverlay(
((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
&& mOwnerCanAddInternalSystemWindow)
|| InputMonitor.isTrustedOverlay(mAttrs.type));
if (DEBUG) {
Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
}
try {
c.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
mDeathRecipient = null;
mIsChildWindow = false;
mLayoutAttached = false;
mIsImWindow = false;
mIsWallpaper = false;
mIsFloatingLayer = false;
mBaseLayer = 0;
mSubLayer = 0;
mWinAnimator = null;
mWpcForDisplayAreaConfigChanges = null;
return;
}
mDeathRecipient = deathRecipient;
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
mIsChildWindow = true;
mLayoutAttached = mAttrs.type !=
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
|| parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
} else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.getWindowLayerLw(this)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = 0;
mIsChildWindow = false;
mLayoutAttached = false;
mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
}
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
if (mActivityRecord != null && mActivityRecord.mShowForAllUsers) {
// Windows for apps that can show for all users should also show when the device is
// locked.
mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
}
mWinAnimator = new WindowStateAnimator(this);
mWinAnimator.mAlpha = a.alpha;
mRequestedWidth = 0;
mRequestedHeight = 0;
mLastRequestedWidth = 0;
mLastRequestedHeight = 0;
mLayer = 0;
mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
mAttrs.packageName, s.mUid);
// Make sure we initial all fields before adding to parentWindow, to prevent exception
// during onDisplayChanged.
if (mIsChildWindow) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
parentWindow.addChild(this, sWindowSubLayerComparator);
}
// System process or invalid process cannot register to display area config change.
mWpcForDisplayAreaConfigChanges = (s.mPid == MY_PID || s.mPid < 0)
? null
: service.mAtmService.getProcessController(s.mPid, s.mUid);
}
int getTouchOcclusionMode() {
if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
return TouchOcclusionMode.USE_OPACITY;
}
if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL) || inTransition()) {
return TouchOcclusionMode.USE_OPACITY;
}
return TouchOcclusionMode.BLOCK_UNTRUSTED;
}
void attach() {
if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
mSession.windowAddedLocked();
}
/**
* @return {@code true} if the application runs in size compatibility mode or has an app level
* scaling override set. This method always returns {@code false} on child window because it
* should follow parent's scale.
* @see CompatModePackages#getCompatScale
* @see android.content.res.CompatibilityInfo#supportsScreen
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
return (mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord)) && !mIsChildWindow;
}
/**
* @return {@code true} if the application runs in size compatibility mode.
* @see android.content.res.CompatibilityInfo#supportsScreen
* @see ActivityRecord#hasSizeCompatBounds()
*/
static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken windowToken) {
return (attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0
|| (windowToken != null && windowToken.hasSizeCompatBounds()
// Exclude starting window because it is not displayed by the application.
&& attrs.type != TYPE_APPLICATION_STARTING);
}
/**
* Returns whether this {@link WindowState} has been considered for drawing by its parent.
*/
boolean getDrawnStateEvaluated() {
return mDrawnStateEvaluated;
}
/**
* Sets whether this {@link WindowState} has been considered for drawing by its parent. Should
* be cleared when detached from parent.
*/
void setDrawnStateEvaluated(boolean evaluated) {
mDrawnStateEvaluated = evaluated;
}
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
super.onParentChanged(newParent, oldParent);
setDrawnStateEvaluated(false /*evaluated*/);
getDisplayContent().reapplyMagnificationSpec();
}
/** Returns the uid of the app that owns this window. */
int getOwningUid() {
return mOwnerUid;
}
@Override
public String getOwningPackage() {
return mAttrs.packageName;
}
@Override
public boolean canAddInternalSystemWindow() {
return mOwnerCanAddInternalSystemWindow;
}
/**
* Returns {@code true} if the window owner has the permission to acquire a sleep token when
* it's visible. That is, they have the permission
* {@link androidManifest.permission#DEVICE_POWER}.
*/
boolean canAcquireSleepToken() {
return mSession.mCanAcquireSleepToken;
}
/**
* Subtracts the insets calculated by intersecting {@param layoutFrame} with {@param insetFrame}
* from {@param frame}. In other words, it applies the insets that would result if
* {@param frame} would be shifted to {@param layoutFrame} and then applying the insets from
* {@param insetFrame}. Also it respects {@param displayFrame} in case window has minimum
* width/height applied and insets should be overridden.
*/
private void subtractInsets(Rect frame, Rect layoutFrame, Rect insetFrame, Rect displayFrame) {
final int left = Math.max(0, insetFrame.left - Math.max(layoutFrame.left, displayFrame.left));
final int top = Math.max(0, insetFrame.top - Math.max(layoutFrame.top, displayFrame.top));
final int right = Math.max(0, Math.min(layoutFrame.right, displayFrame.right) - insetFrame.right);
final int bottom = Math.max(0, Math.min(layoutFrame.bottom, displayFrame.bottom) - insetFrame.bottom);
frame.inset(left, top, right, bottom);
}
void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) {
computeFrame(displayFrames);
// Update the source frame to provide insets to other windows during layout. If the
// simulated frames exist, then this is not computing a stable result so just skip.
if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) {
mControllableInsetProvider.updateSourceFrame();
}
}
/**
* Perform standard frame computation. The result can be obtained with getFrame() if so desired.
*/
void computeFrame(DisplayFrames displayFrames) {
if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
// want to apply any more changes to it, so it remains in this state until new window
// appears.
return;
}
mHaveFrame = true;
final Task task = getTask();
final boolean isFullscreenAndFillsArea = !inMultiWindowMode() && matchesDisplayAreaBounds();
final boolean windowsAreFloating = task != null && task.isFloating();
final DisplayContent dc = getDisplayContent();
final DisplayInfo displayInfo = getDisplayInfo();
final WindowFrames windowFrames = getLayoutingWindowFrames();
mInsetFrame.set(getBounds());
// Denotes the actual frame used to calculate the insets and to perform the layout. When
// resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
// insets temporarily. By the notion of a task having a different layout frame, we can
// achieve that while still moving the task around.
final Rect layoutContainingFrame;
final Rect layoutDisplayFrame;
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();
final InsetsControlTarget imeTarget = dc.getImeTarget(IME_TARGET_LAYERING);
final boolean isInputMethodAdjustTarget = windowsAreFloating
? imeTarget != null && task == imeTarget.getWindow().getTask()
: isImeLayeringTarget();
final boolean isImeTarget =
imeWin != null && imeWin.isVisibleNow() && isInputMethodAdjustTarget;
if (isFullscreenAndFillsArea || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
windowFrames.mContainingFrame.set(windowFrames.mParentFrame);
layoutDisplayFrame = windowFrames.mDisplayFrame;
layoutContainingFrame = windowFrames.mParentFrame;
layoutXDiff = 0;
layoutYDiff = 0;
} else {
windowFrames.mContainingFrame.set(getBounds());
// IME is up and obscuring this window. Adjust the window position so it is visible.
if (isImeTarget) {
if (inFreeformWindowingMode()) {
// Push the freeform window up to make room for the IME. However, don't push
// it up past the bottom of the top bar.
final InsetsState state = dc.getInsetsStateController().getRawInsetsState();
final Rect visibleFrame = mTmpRect;
visibleFrame.set(state.getDisplayFrame());
visibleFrame.inset(state.calculateInsets(visibleFrame,
systemBars() | ime() | displayCutout(), false /* ignoreVisibility */));
final int bottomOverlap =
windowFrames.mContainingFrame.bottom - visibleFrame.bottom;
if (bottomOverlap > 0) {
final int distanceToTop = Math.max(windowFrames.mContainingFrame.top
- visibleFrame.top, 0);
int offs = Math.min(bottomOverlap, distanceToTop);
windowFrames.mContainingFrame.offset(0, -offs);
mInsetFrame.offset(0, -offs);
}
} else if (!inPinnedWindowingMode() && windowFrames.mContainingFrame.bottom
> windowFrames.mParentFrame.bottom) {
// But in docked we want to behave like fullscreen and behave as if the task
// were given smaller bounds for the purposes of layout. Skip adjustments for
// the root pinned task, they are handled separately in the
// PinnedTaskController.
windowFrames.mContainingFrame.bottom = windowFrames.mParentFrame.bottom;
}
}
if (windowsAreFloating) {
// In floating modes (e.g. freeform, pinned) we have only to set the rectangle
// if it wasn't set already. No need to intersect it with the (visible)
// "content frame" since it is allowed to be outside the visible desktop.
if (windowFrames.mContainingFrame.isEmpty()) {
windowFrames.mContainingFrame.set(windowFrames.mDisplayFrame);
}
}
layoutDisplayFrame = mTmpRect2;
layoutDisplayFrame.set(windowFrames.mDisplayFrame);
windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
layoutYDiff = mInsetFrame.top - windowFrames.mContainingFrame.top;
layoutContainingFrame = mInsetFrame;
mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
subtractInsets(windowFrames.mDisplayFrame, layoutContainingFrame, layoutDisplayFrame,
mTmpRect);
if (!layoutInParentFrame()) {
subtractInsets(windowFrames.mContainingFrame, layoutContainingFrame,
windowFrames.mParentFrame, mTmpRect);
subtractInsets(mInsetFrame, layoutContainingFrame, windowFrames.mParentFrame,
mTmpRect);
}
layoutDisplayFrame.intersect(layoutContainingFrame);
}
final int pw = windowFrames.mContainingFrame.width();
final int ph = windowFrames.mContainingFrame.height();
if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
mLastRequestedWidth = mRequestedWidth;
mLastRequestedHeight = mRequestedHeight;
windowFrames.setContentChanged(true);
}
final int fw = windowFrames.mFrame.width();
final int fh = windowFrames.mFrame.height();
applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame,
displayFrames);
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
mMovedByResize = true;
}
}
// Offset the actual frame by the amount layout frame is off.
windowFrames.offsetFrames(-layoutXDiff, -layoutYDiff);
windowFrames.mCompatFrame.set(windowFrames.mFrame);
if (hasCompatScale()) {
// Also the scaled frame that we report to the app needs to be
// adjusted to be in its coordinate space.
windowFrames.mCompatFrame.scale(mInvGlobalScale);
}
if (mIsWallpaper && (fw != windowFrames.mFrame.width()
|| fh != windowFrames.mFrame.height())) {
dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
}
// Calculate relative frame
windowFrames.mRelFrame.set(windowFrames.mFrame);
WindowContainer parent = getParent();
int parentLeft = 0;
int parentTop = 0;
if (mIsChildWindow) {
parentLeft = ((WindowState) parent).mWindowFrames.mFrame.left;
parentTop = ((WindowState) parent).mWindowFrames.mFrame.top;
} else if (parent != null) {
final Rect parentBounds = parent.getBounds();
parentLeft = parentBounds.left;
parentTop = parentBounds.top;
}
windowFrames.mRelFrame.offsetTo(windowFrames.mFrame.left - parentLeft,
windowFrames.mFrame.top - parentTop);
if (DEBUG_LAYOUT || DEBUG) {
Slog.v(TAG, "Resolving (mRequestedWidth="
+ mRequestedWidth + ", mRequestedheight="
+ mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+ "): frame=" + windowFrames.mFrame.toShortString()
+ " " + mAttrs.getTitle());
}
}
@Override
public Rect getBounds() {
// The window bounds are used for layout in screen coordinates. If the token has bounds for
// size compatibility mode, its configuration bounds are app based coordinates which should
// not be used for layout.
return mToken.hasSizeCompatBounds() ? mToken.getBounds() : super.getBounds();
}
/** Retrieves the current frame of the window that the application sees. */
Rect getFrame() {
return mWindowFrames.mFrame;
}
/** Accessor for testing */
Rect getRelativeFrame() {
return mWindowFrames.mRelFrame;
}
/**
* Gets the frame that excludes the area of side insets according to the layout parameter from
* {@link WindowManager.LayoutParams#setFitInsetsSides}.
*/
Rect getDisplayFrame() {
return mWindowFrames.mDisplayFrame;
}
Rect getParentFrame() {
return mWindowFrames.mParentFrame;
}
Rect getContainingFrame() {
return mWindowFrames.mContainingFrame;
}
void getCompatFrameSize(Rect outFrame) {
outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
}
@Override
public WindowManager.LayoutParams getAttrs() {
return mAttrs;
}
WindowManager.LayoutParams getLayoutingAttrs(int rotation) {
if (!INSETS_LAYOUT_GENERALIZATION) {
return mAttrs;
}
final WindowManager.LayoutParams[] paramsForRotation = mAttrs.paramsForRotation;
if (paramsForRotation == null || paramsForRotation.length != 4
|| paramsForRotation[rotation] == null) {
return mAttrs;
}
return paramsForRotation[rotation];
}
/** Retrieves the flags used to disable system UI functions. */
int getDisableFlags() {
return mDisableFlags;
}
/** Gets the layer at which this window's surface will be Z-ordered. */
int getSurfaceLayer() {
return mLayer;
}
@Override
public int getBaseType() {
return getTopParentWindow().mAttrs.type;
}
@Override
public IApplicationToken getAppToken() {
return mActivityRecord != null ? mActivityRecord.appToken : null;
}
/** Returns true if this window is participating in voice interaction. */
boolean isVoiceInteraction() {
return mActivityRecord != null && mActivityRecord.mVoiceInteraction;
}
boolean setReportResizeHints() {
return mWindowFrames.setReportResizeHints();
}
/**
* Adds the window to the resizing list if any of the parameters we use to track the window
* dimensions or insets have changed.
*/
void updateResizingWindowIfNeeded() {
final WindowStateAnimator winAnimator = mWinAnimator;
if (!mHasSurface || getDisplayContent().mLayoutSeq != mLayoutSeq || isGoneForLayout()) {
return;
}
boolean didFrameInsetsChange = setReportResizeHints();
// The latest configuration will be returned by the out parameter of relayout, so it is
// unnecessary to report resize if this window is running relayout.
final boolean configChanged = !mInRelayout && !isLastConfigReportedToClient();
if (DEBUG_CONFIGURATION && configChanged) {
Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
}
final boolean dragResizingChanged = isDragResizeChanged()
&& !isDragResizingChangeReported();
if (DEBUG) {
Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+ " dragResizingChanged=" + dragResizingChanged
+ " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
}
// We update mLastFrame always rather than in the conditional with the last inset
// variables, because mFrameSizeChanged only tracks the width and height changing.
updateLastFrames();
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
if (didFrameInsetsChange
|| configChanged
|| dragResizingChanged
|| mReportOrientationChanged
|| shouldSendRedrawForSync()) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize reasons for w=%s: %s configChanged=%b "
+ "dragResizingChanged=%b reportOrientationChanged=%b",
this, mWindowFrames.getInsetsChangedInfo(),
configChanged, dragResizingChanged, mReportOrientationChanged);
// If it's a dead window left on screen, and the configuration changed, there is nothing
// we can do about it. Remove the window now.
if (mActivityRecord != null && mAppDied) {
mActivityRecord.removeDeadWindows();
return;
}
onResizeHandled();
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// If the orientation is changing, or we're starting or ending a drag resizing action,
// then we need to hold off on unfreezing the display until this window has been
// redrawn; to do that, we need to go through the process of getting informed by the
// application when it has finished drawing.
if (getOrientationChanging() || dragResizingChanged) {
if (dragResizingChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize start waiting for draw, "
+ "mDrawState=DRAW_PENDING in %s, surfaceController %s",
this, winAnimator.mSurfaceController);
}
winAnimator.mDrawState = DRAW_PENDING;
if (mActivityRecord != null) {
mActivityRecord.clearAllDrawn();
}
}
if (!mWmService.mResizingWindows.contains(this)) {
ProtoLog.v(WM_DEBUG_RESIZE, "Resizing window %s", this);
mWmService.mResizingWindows.add(this);
}
} else if (getOrientationChanging()) {
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
winAnimator.mSurfaceController);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
}
}
}
boolean getOrientationChanging() {
// In addition to the local state flag, we must also consider the difference in the last
// reported configuration vs. the current state. If the client code has not been informed of
// the change, logic dependent on having finished processing the orientation, such as
// unfreezing, could be improperly triggered.
// TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
// this is not necessarily what the client has processed yet. Find a
// better indicator consistent with the client.
return (mOrientationChanging || (isVisible()
&& getConfiguration().orientation != getLastReportedConfiguration().orientation))
&& !mSeamlesslyRotated
&& !mOrientationChangeTimedOut;
}
void setOrientationChanging(boolean changing) {
mOrientationChangeTimedOut = false;
if (mOrientationChanging == changing) {
return;
}
mOrientationChanging = changing;
if (changing) {
mLastFreezeDuration = 0;
if (mWmService.mRoot.mOrientationChangeComplete
&& mDisplayContent.waitForUnfreeze(this)) {
mWmService.mRoot.mOrientationChangeComplete = false;
}
} else {
// The orientation change is completed. If it was hidden by the animation, reshow it.
mDisplayContent.finishFadeRotationAnimation(this);
}
}
void orientationChangeTimedOut() {
mOrientationChangeTimedOut = true;
}
@Override
DisplayContent getDisplayContent() {
return mToken.getDisplayContent();
}
@Override
void onDisplayChanged(DisplayContent dc) {
if (dc != null && mDisplayContent != null && dc != mDisplayContent
&& getImeInputTarget() == this) {
dc.updateImeInputAndControlTarget(getImeInputTarget());
mDisplayContent.setImeInputTarget(null);
}
super.onDisplayChanged(dc);
// Window was not laid out for this display yet, so make sure mLayoutSeq does not match.
if (dc != null && mInputWindowHandle.getDisplayId() != dc.getDisplayId()) {
mLayoutSeq = dc.mLayoutSeq - 1;
mInputWindowHandle.setDisplayId(dc.getDisplayId());
}
}
/** @return The display frames in use by this window. */
DisplayFrames getDisplayFrames(DisplayFrames originalFrames) {
final DisplayFrames diplayFrames = mToken.getFixedRotationTransformDisplayFrames();
if (diplayFrames != null) {
return diplayFrames;
}
return originalFrames;
}
DisplayInfo getDisplayInfo() {
final DisplayInfo displayInfo = mToken.getFixedRotationTransformDisplayInfo();
if (displayInfo != null) {
return displayInfo;
}
return getDisplayContent().getDisplayInfo();
}
/**
* Returns the insets state for the window. Its sources may be the copies with visibility
* modification according to the state of transient bars.
*/
InsetsState getInsetsState() {
return getDisplayContent().getInsetsPolicy().getInsetsForWindow(this);
}
/**
* Returns the insets state for the client and scales the frames if the client is in the size
* compatible mode.
*/
InsetsState getCompatInsetsState() {
InsetsState state = getInsetsState();
if (hasCompatScale()) {
state = new InsetsState(state, true);
state.scale(mInvGlobalScale);
}
return state;
}
/**
* Returns the insets state for the window and applies the requested visibility.
*/
InsetsState getInsetsStateWithVisibilityOverride() {
final InsetsState state = new InsetsState(getInsetsState());
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
final boolean requestedVisible = getRequestedVisibility(type);
InsetsSource source = state.peekSource(type);
if (source != null && source.isVisible() != requestedVisible) {
source = new InsetsSource(source);
source.setVisible(requestedVisible);
state.addSource(source);
}
}
return state;
}
@Override
public int getDisplayId() {
final DisplayContent displayContent = getDisplayContent();
if (displayContent == null) {
return Display.INVALID_DISPLAY;
}
return displayContent.getDisplayId();
}
@Override
public WindowState getWindowState() {
return this;
}
@Override
public IWindow getIWindow() {
return mClient;
}
@Override
public int getPid() {
return mSession.mPid;
}
Task getTask() {
return mActivityRecord != null ? mActivityRecord.getTask() : null;
}
@Nullable TaskFragment getTaskFragment() {
return mActivityRecord != null ? mActivityRecord.getTaskFragment() : null;
}
@Nullable Task getRootTask() {
final Task task = getTask();
if (task != null) {
return task.getRootTask();
}
// Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
// associate them with some root task to enable dimming.
final DisplayContent dc = getDisplayContent();
return mAttrs.type >= FIRST_SYSTEM_WINDOW
&& dc != null ? dc.getDefaultTaskDisplayArea().getRootHomeTask() : null;
}
/**
* This is a form of rectangle "difference". It cut off each dimension of rect by the amount
* that toRemove is "pushing into" it from the outside. Any dimension that fully contains
* toRemove won't change.
*/
private void cutRect(Rect rect, Rect toRemove) {
if (toRemove.isEmpty()) return;
if (toRemove.top < rect.bottom && toRemove.bottom > rect.top) {
if (toRemove.right >= rect.right && toRemove.left >= rect.left) {
rect.right = toRemove.left;
} else if (toRemove.left <= rect.left && toRemove.right <= rect.right) {
rect.left = toRemove.right;
}
}
if (toRemove.left < rect.right && toRemove.right > rect.left) {
if (toRemove.bottom >= rect.bottom && toRemove.top >= rect.top) {
rect.bottom = toRemove.top;
} else if (toRemove.top <= rect.top && toRemove.bottom <= rect.bottom) {
rect.top = toRemove.bottom;
}
}
}
/**
* Retrieves the visible bounds of the window.
* @param bounds The rect which gets the bounds.
*/
void getVisibleBounds(Rect bounds) {
final Task task = getTask();
boolean intersectWithRootTaskBounds = task != null && task.cropWindowsToRootTaskBounds();
bounds.setEmpty();
mTmpRect.setEmpty();
if (intersectWithRootTaskBounds) {
final Task rootTask = task.getRootTask();
if (rootTask != null) {
rootTask.getDimBounds(mTmpRect);
} else {
intersectWithRootTaskBounds = false;
}
if (inSplitScreenPrimaryWindowingMode()) {
// If this is in the primary split and the root home task is the top visible task in
// the secondary split, it means this is "minimized" and thus must prevent
// overlapping with home.
// TODO(b/158242495): get rid of this when drag/drop can use surface bounds.
final Task rootSecondary =
task.getDisplayArea().getRootSplitScreenSecondaryTask();
if (rootSecondary.isActivityTypeHome() || rootSecondary.isActivityTypeRecents()) {
final WindowContainer topTask = rootSecondary.getTopChild();
if (topTask.isVisible()) {
cutRect(mTmpRect, topTask.getBounds());
}
}
}
}
bounds.set(mWindowFrames.mFrame);
bounds.inset(getInsetsStateWithVisibilityOverride().calculateVisibleInsets(
bounds, mAttrs.softInputMode));
if (intersectWithRootTaskBounds) {
bounds.intersect(mTmpRect);
}
}
public long getInputDispatchingTimeoutMillis() {
return mActivityRecord != null
? mActivityRecord.mInputDispatchingTimeoutMillis
: DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
}
/**
* Returns true if, at any point, the application token associated with this window has actually
* displayed any windows. This is most useful with the "starting up" window to determine if any
* windows were displayed when it is closed.
*
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
return mActivityRecord != null
&& (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
}
boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
if (dsdx < .99999f || dsdx > 1.00001f) return false;
if (dtdy < .99999f || dtdy > 1.00001f) return false;
if (dtdx < -.000001f || dtdx > .000001f) return false;
if (dsdy < -.000001f || dsdy > .000001f) return false;
return true;
}
void prelayout() {
if (hasCompatScale()) {
if (mOverrideScale != 1f) {
mGlobalScale = mToken.hasSizeCompatBounds()
? mToken.getSizeCompatScale() * mOverrideScale
: mOverrideScale;
} else {
mGlobalScale = mToken.getSizeCompatScale();
}
mInvGlobalScale = 1 / mGlobalScale;
} else {
mGlobalScale = mInvGlobalScale = 1;
}
}
@Override
boolean hasContentToDisplay() {
if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
|| (isAnimating(TRANSITION | PARENTS)
&& !getDisplayContent().mAppTransition.isTransitionSet()))) {
return true;
}
return super.hasContentToDisplay();
}
private boolean isVisibleByPolicyOrInsets() {
return isVisibleByPolicy()
// If we don't have a provider, this window isn't used as a window generating
// insets, so nobody can hide it over the inset APIs.
&& (mControllableInsetProvider == null
|| mControllableInsetProvider.isClientVisible());
}
@Override
boolean isVisible() {
return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicyOrInsets();
}
@Override
boolean isVisibleRequested() {
final boolean localVisibleRequested =
wouldBeVisibleRequestedIfPolicyIgnored() && isVisibleByPolicyOrInsets();
if (localVisibleRequested && shouldCheckTokenVisibleRequested()) {
return mToken.isVisibleRequested();
}
return localVisibleRequested;
}
/**
* Returns {@code true} if {@link WindowToken#isVisibleRequested()} should be considered
* before dispatching the latest configuration. Currently only {@link
* ActivityRecord#isVisibleRequested()} and {@link WallpaperWindowToken#isVisibleRequested()}
* implement explicit visible-requested.
*/
boolean shouldCheckTokenVisibleRequested() {
return mActivityRecord != null || mToken.asWallpaperToken() != null;
}
/**
* Ensures that all the policy visibility bits are set.
* @return {@code true} if all flags about visiblity are set
*/
boolean isVisibleByPolicy() {
return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL;
}
void clearPolicyVisibilityFlag(int policyVisibilityFlag) {
mPolicyVisibility &= ~policyVisibilityFlag;
mWmService.scheduleAnimationLocked();
}
void setPolicyVisibilityFlag(int policyVisibilityFlag) {
mPolicyVisibility |= policyVisibilityFlag;
mWmService.scheduleAnimationLocked();
}
private boolean isLegacyPolicyVisibility() {
return (mPolicyVisibility & LEGACY_POLICY_VISIBILITY) != 0;
}
/**
* @return {@code true} if the window would be visible if we'd ignore policy visibility,
* {@code false} otherwise.
*/
boolean wouldBeVisibleIfPolicyIgnored() {
if (!mHasSurface || isParentWindowHidden() || mAnimatingExit || mDestroying) {
return false;
}
final boolean isWallpaper = mToken.asWallpaperToken() != null;
return !isWallpaper || mToken.isVisible();
}
private boolean wouldBeVisibleRequestedIfPolicyIgnored() {
final WindowState parent = getParentWindow();
final boolean isParentHiddenRequested = parent != null && !parent.isVisibleRequested();
if (isParentHiddenRequested || mAnimatingExit || mDestroying) {
return false;
}
final boolean isWallpaper = mToken.asWallpaperToken() != null;
return !isWallpaper || mToken.isVisibleRequested();
}
/**
* Is this window visible, ignoring its app token? It is not visible if there is no surface,
* or we are in the process of running an exit animation that will remove the surface.
*/
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
return (mActivityRecord == null || mActivityRecord.mVisibleRequested
|| mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
}
/**
* The same as isVisible(), but follows the current hidden state of the associated app token,
* not the pending requested hidden state.
*/
boolean isVisibleNow() {
return (mToken.isVisible() || mAttrs.type == TYPE_APPLICATION_STARTING)
&& isVisible();
}
/**
* Can this window possibly be a drag/drop target? The test here is
* a combination of the above "visible now" with the check that the
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
/**
* Is this window capable of being visible (policy and content), in a visible part of the
* hierarchy, and, if an activity window, the activity is visible-requested. Note, this means
* if the activity is going-away, this will be {@code false} even when the window is visible.
*
* The 'adding' part refers to the period of time between IWindowSession.add() and the first
* relayout() -- which, for activities, is the same as visibleRequested.
*
* TODO(b/206005136): This is very similar to isVisibleRequested(). Investigate merging them.
*/
boolean isVisibleRequestedOrAdding() {
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
&& (atoken == null || atoken.mVisibleRequested)
&& !mAnimatingExit && !mDestroying;
}
/**
* Is this window currently on-screen? It is on-screen either if it
* is visible or it is currently running an animation before no longer
* being visible.
*/
boolean isOnScreen() {
if (!mHasSurface || mDestroying || !isVisibleByPolicy()) {
return false;
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
return ((!isParentWindowHidden() && atoken.isVisible())
|| isAnimating(TRANSITION | PARENTS));
}
final WallpaperWindowToken wtoken = mToken.asWallpaperToken();
if (wtoken != null) {
return !isParentWindowHidden() && wtoken.isVisible();
}
return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
}
boolean isDreamWindow() {
return mActivityRecord != null
&& mActivityRecord.getActivityType() == ACTIVITY_TYPE_DREAM;
}
boolean isSecureLocked() {
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
return true;
}
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId,
mOwnerCanAddInternalSystemWindow);
}
/**
* Whether this window's drawn state might affect the drawn states of the app token.
*
* @return true if the window should be considered while evaluating allDrawn flags.
*/
boolean mightAffectAllDrawn() {
final boolean isAppType = mWinAnimator.mAttrType == TYPE_BASE_APPLICATION
|| mWinAnimator.mAttrType == TYPE_DRAWN_APPLICATION;
return (isOnScreen() || isAppType) && !mAnimatingExit && !mDestroying;
}
/**
* Whether this window is "interesting" when evaluating allDrawn. If it's interesting,
* it must be drawn before allDrawn can become true.
*/
boolean isInteresting() {
return mActivityRecord != null && !mAppDied
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE;
}
/**
* Like isOnScreen(), but we don't return true if the window is part
* of a transition that has not yet been started.
*/
boolean isReadyForDisplay() {
if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
return false;
}
final boolean parentAndClientVisible = !isParentWindowHidden()
&& mViewVisibility == View.VISIBLE && mToken.isVisible();
return mHasSurface && isVisibleByPolicy() && !mDestroying
&& (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
}
boolean isFullyTransparent() {
return mAttrs.alpha == 0f;
}
/**
* @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
* for example) will be affected by the flags specified in this window. This is the
* case when the surface is on screen but not exiting.
*/
boolean canAffectSystemUiFlags() {
if (isFullyTransparent()) {
return false;
}
if (mActivityRecord == null) {
final boolean shown = mWinAnimator.getShown();
final boolean exiting = mAnimatingExit || mDestroying;
return shown && !exiting;
} else {
final Task task = getTask();
final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
return canFromTask && mActivityRecord.isVisible();
}
}
/**
* Like isOnScreen, but returns false if the surface hasn't yet
* been drawn.
*/
boolean isDisplayed() {
final ActivityRecord atoken = mActivityRecord;
return isDrawn() && isVisibleByPolicy()
&& ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
|| isAnimating(TRANSITION | PARENTS));
}
/**
* Return true if this window or its app token is currently animating.
*/
@Override
public boolean isAnimatingLw() {
return isAnimating(TRANSITION | PARENTS);
}
/** Returns {@code true} if this window considered to be gone for purposes of layout. */
boolean isGoneForLayout() {
final ActivityRecord atoken = mActivityRecord;
return mViewVisibility == View.GONE
|| !mRelayoutCalled
// We can't check isVisible here because it will also check the client visibility
// for WindowTokens. Even if the client is not visible, we still need to perform
// a layout since they can request relayout when client visibility is false.
// TODO (b/157682066) investigate if we can clean up isVisible
|| (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
|| (atoken != null && !atoken.mVisibleRequested)
|| isParentWindowGoneForLayout()
|| (mAnimatingExit && !isAnimatingLw())
|| mDestroying;
}
/**
* Returns true if the window has a surface that it has drawn a
* complete UI in to.
*/
public boolean isDrawFinishedLw() {
return mHasSurface && !mDestroying &&
(mWinAnimator.mDrawState == COMMIT_DRAW_PENDING
|| mWinAnimator.mDrawState == READY_TO_SHOW
|| mWinAnimator.mDrawState == HAS_DRAWN);
}
/**
* Returns true if the window has a surface that it has drawn a complete UI in to. Note that
* this is different from {@link #hasDrawn()} in that it also returns true if the window is
* READY_TO_SHOW, but was not yet promoted to HAS_DRAWN.
*/
boolean isDrawn() {
return mHasSurface && !mDestroying &&
(mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
}
/**
* Return true if the window is opaque and fully drawn. This indicates
* it may obscure windows behind it.
*/
private boolean isOpaqueDrawn() {
// When there is keyguard, wallpaper could be placed over the secure app
// window but invisible. We need to check wallpaper visibility explicitly
// to determine if it's occluding apps.
final boolean isWallpaper = mToken.asWallpaperToken() != null;
return ((!isWallpaper && mAttrs.format == PixelFormat.OPAQUE)
|| (isWallpaper && mToken.isVisible()))
&& isDrawn() && !isAnimating(TRANSITION | PARENTS);
}
/** @see WindowManagerInternal#waitForAllWindowsDrawn */
void requestDrawIfNeeded(List outWaitingForDrawn) {
if (!isVisible()) {
return;
}
if (mActivityRecord != null) {
if (mActivityRecord.allDrawn) {
// The allDrawn of activity is reset when the visibility is changed to visible, so
// the content should be ready if allDrawn is set.
return;
}
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
if (isDrawn()) {
// Unnecessary to redraw a drawn starting window.
return;
}
} else if (mActivityRecord.mStartingWindow != null) {
// If the activity has an active starting window, there is no need to wait for the
// main window.
return;
}
} else if (!mPolicy.isKeyguardHostWindow(mAttrs)) {
return;
// Always invalidate keyguard host window to make sure it shows the latest content
// because its visibility may not be changed.
}
mWinAnimator.mDrawState = DRAW_PENDING;
// Force add to {@link WindowManagerService#mResizingWindows}.
forceReportingResized();
outWaitingForDrawn.add(this);
}
@Override
void onMovedByResize() {
ProtoLog.d(WM_DEBUG_RESIZE, "onMovedByResize: Moving %s", this);
mMovedByResize = true;
super.onMovedByResize();
}
void onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).onAppVisibilityChanged(visible, runningAppAnimation);
}
final boolean isVisibleNow = isVisibleNow();
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Starting window that's exiting will be removed when the animation finishes.
// Mark all relevant flags for that onExitAnimationDone will proceed all the way
// to actually remove it.
if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) {
mAnimatingExit = true;
mRemoveOnExit = true;
mWindowRemovalAllowed = true;
}
} else if (visible != isVisibleNow) {
// Run exit animation if:
// 1. App visibility and WS visibility are different
// 2. App is not running an animation
// 3. WS is currently visible
if (!runningAppAnimation && isVisibleNow) {
final AccessibilityController accessibilityController =
mWmService.mAccessibilityController;
final int winTransit = TRANSIT_EXIT;
mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
if (accessibilityController.hasCallbacks()) {
accessibilityController.onWindowTransition(this, winTransit);
}
}
setDisplayLayoutNeeded();
}
}
boolean onSetAppExiting(boolean animateExit) {
final DisplayContent displayContent = getDisplayContent();
boolean changed = false;
if (!animateExit) {
// Hide the window permanently if no window exist animation is performed, so we can
// avoid the window surface becoming visible again unexpectedly during the next
// relayout.
mPermanentlyHidden = true;
hide(false /* doAnimation */, false /* requestAnim */);
}
if (isVisibleNow() && animateExit) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT);
}
changed = true;
if (displayContent != null) {
displayContent.setLayoutNeeded();
}
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
changed |= c.onSetAppExiting(animateExit);
}
return changed;
}
@Override
void onResize() {
final ArrayList resizingWindows = mWmService.mResizingWindows;
if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
resizingWindows.add(this);
}
if (isGoneForLayout()) {
mResizedWhileGone = true;
}
super.onResize();
}
/**
* If the window has moved due to its containing content frame changing, then notify the
* listeners and optionally animate it. Simply checking a change of position is not enough,
* because being move due to dock divider is not a trigger for animation.
*/
void handleWindowMovedIfNeeded() {
if (!hasMoved()) {
return;
}
// Frame has moved, containing content frame has also moved, and we're not currently
// animating... let's do something.
final int left = mWindowFrames.mFrame.left;
final int top = mWindowFrames.mFrame.top;
if (canPlayMoveAnimation()) {
startMoveAnimation(left, top);
}
if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
}
updateLocationInParentDisplayIfNeeded();
try {
mClient.moved(left, top);
} catch (RemoteException e) {
}
mMovedByResize = false;
}
private boolean canPlayMoveAnimation() {
// During the transition from pip to fullscreen, the activity windowing mode is set to
// fullscreen at the beginning while the task is kept in pinned mode. Skip the move
// animation in such case since the transition is handled in SysUI.
final boolean hasMovementAnimation = getTask() == null
? getWindowConfiguration().hasMovementAnimations()
: getTask().getWindowConfiguration().hasMovementAnimations();
return mToken.okToAnimate()
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
&& !isDragResizing()
&& hasMovementAnimation
&& !mWinAnimator.mLastHidden
&& !mSeamlesslyRotated;
}
/**
* Return whether this window has moved. (Only makes
* sense to call from performLayoutAndPlaceSurfacesLockedInner().)
*/
private boolean hasMoved() {
return mHasSurface && (mWindowFrames.hasContentChanged() || mMovedByResize)
&& !mAnimatingExit
&& (mWindowFrames.mRelFrame.top != mWindowFrames.mLastRelFrame.top
|| mWindowFrames.mRelFrame.left != mWindowFrames.mLastRelFrame.left)
&& (!mIsChildWindow || !getParentWindow().hasMoved())
&& !mTransitionController.isCollecting();
}
boolean isObscuringDisplay() {
Task task = getTask();
if (task != null && task.getRootTask() != null && !task.getRootTask().fillsParent()) {
return false;
}
return isOpaqueDrawn() && fillsDisplay();
}
boolean fillsDisplay() {
final DisplayInfo displayInfo = getDisplayInfo();
return mWindowFrames.mFrame.left <= 0 && mWindowFrames.mFrame.top <= 0
&& mWindowFrames.mFrame.right >= displayInfo.appWidth
&& mWindowFrames.mFrame.bottom >= displayInfo.appHeight;
}
boolean matchesDisplayAreaBounds() {
final Rect rotatedDisplayBounds = mToken.getFixedRotationTransformDisplayBounds();
if (rotatedDisplayBounds != null) {
// If the rotated display bounds are available, the window bounds are also rotated.
return rotatedDisplayBounds.equals(getBounds());
}
final DisplayArea displayArea = getDisplayArea();
if (displayArea == null) {
return getDisplayContent().getBounds().equals(getBounds());
}
return displayArea.getBounds().equals(getBounds());
}
/**
* @return {@code true} if last applied config was reported to the client already, {@code false}
* otherwise.
*/
boolean isLastConfigReportedToClient() {
return mLastConfigReportedToClient;
}
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
if (getDisplayContent().getImeTarget(IME_TARGET_INPUT) != this && !isImeLayeringTarget()) {
super.onConfigurationChanged(newParentConfig);
return;
}
mTempConfiguration.setTo(getConfiguration());
super.onConfigurationChanged(newParentConfig);
final boolean windowConfigChanged = mTempConfiguration.windowConfiguration
.diff(newParentConfig.windowConfiguration, false) != 0;
// When the window configuration changed, we need to update the IME control target in
// case the app may lose the IME inets control when exiting from split-screen mode, or the
// IME parent may failed to attach to the app during rotating the screen.
// See DisplayContent#shouldImeAttachedToApp, DisplayContent#isImeControlledByApp
if (windowConfigChanged) {
getDisplayContent().updateImeControlTarget();
}
}
@Override
void onMergedOverrideConfigurationChanged() {
super.onMergedOverrideConfigurationChanged();
mLastConfigReportedToClient = false;
}
void onWindowReplacementTimeout() {
if (mWillReplaceWindow) {
// Since the window already timed out, remove it immediately now.
// Use WindowState#removeImmediately() instead of WindowState#removeIfPossible(), as
// the latter delays removal on certain conditions, which will leave the stale window
// in the root task and marked mWillReplaceWindow=false, so the window will never be
// removed.
//
// Also removes child windows.
removeImmediately();
} else {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
c.onWindowReplacementTimeout();
}
}
}
@Override
void removeImmediately() {
if (!mRemoved) {
// Destroy surface before super call. The general pattern is that the children need
// to be removed before the parent (so that the sync-engine tracking works). Since
// WindowStateAnimator is a "virtual" child, we have to do it manually here.
mWinAnimator.destroySurfaceLocked(getSyncTransaction());
}
super.removeImmediately();
if (mRemoved) {
// Nothing to do.
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"WS.removeImmediately: %s Already removed...", this);
return;
}
mRemoved = true;
mWillReplaceWindow = false;
if (mReplacementWindow != null) {
mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
}
final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
// Remove the IME screenshot surface if the layering target is not animating.
dc.removeImeScreenshotIfPossible();
// Make sure to set mImeLayeringTarget as null when the removed window is the
// IME target, in case computeImeTarget may use the outdated target.
dc.setImeLayeringTarget(null);
dc.computeImeTarget(true /* updateImeTarget */);
}
if (dc.getImeTarget(IME_TARGET_INPUT) == this) {
dc.updateImeInputAndControlTarget(null);
}
final int type = mAttrs.type;
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
dc.mTapExcludedWindows.remove(this);
}
// Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will
// not do anything.
dc.mTapExcludeProvidingWindows.remove(this);
dc.getDisplayPolicy().removeWindowLw(this);
disposeInputChannel();
mSession.windowRemovedLocked();
try {
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
} catch (RuntimeException e) {
// Ignore if it has already been removed (usually because
// we are doing this as part of processing a death note.)
}
mWmService.postWindowRemoveCleanupLocked(this);
}
@Override
void removeIfPossible() {
super.removeIfPossible();
removeIfPossible(false /*keepVisibleDeadWindow*/);
}
private void removeIfPossible(boolean keepVisibleDeadWindow) {
mWindowRemovalAllowed = true;
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
if (startingWindow) {
ProtoLog.d(WM_DEBUG_STARTING_WINDOW, "Starting window removed %s", this);
// Cancel the remove starting window animation on shell. The main window might changed
// during animating, checking for all windows would be safer.
if (mActivityRecord != null) {
mActivityRecord.forAllWindowsUnchecked(w -> {
if (w.isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
w.cancelAnimation();
return true;
}
return false;
}, true);
}
} else if (mAttrs.type == TYPE_BASE_APPLICATION
&& isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
// Cancel the remove starting window animation in case the binder dead before remove
// splash window.
cancelAnimation();
}
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
mWinAnimator.mSurfaceController,
Debug.getCallers(5));
final long origId = Binder.clearCallingIdentity();
try {
disposeInputChannel();
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mWillReplaceWindow=%b mDisplayFrozen=%b callers=%s",
this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
mWillReplaceWindow,
mWmService.mDisplayFrozen, Debug.getCallers(6));
// Visibility of the removed window. Will be used later to update orientation later on.
boolean wasVisible = false;
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
// since the animation wouldn't be seen.
if (mHasSurface && mToken.okToAnimate()) {
if (mWillReplaceWindow) {
// This window is going to be replaced. We need to keep it around until the new one
// gets added, then we will get rid of this one.
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Preserving %s until the new one is added", this);
// TODO: We are overloading mAnimatingExit flag to prevent the window state from
// been removed. We probably need another flag to indicate that window removal
// should be deffered vs. overloading the flag that says we are playing an exit
// animation.
mAnimatingExit = true;
mReplacingRemoveRequested = true;
return;
}
// If we are not currently running the exit animation, we need to see about starting one
wasVisible = isWinVisibleLw();
if (keepVisibleDeadWindow) {
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Not removing %s because app died while it's visible", this);
mAppDied = true;
setDisplayLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
// Set up a replacement input channel since the app is now dead.
// We need to catch tapping on the dead window to restart the app.
openInputChannel(null);
getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/);
return;
}
if (wasVisible) {
final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
// Try starting an animation.
if (mWinAnimator.applyAnimationLocked(transit, false)) {
mAnimatingExit = true;
// mAnimatingExit affects canAffectSystemUiFlags(). Run layout such that
// any change from that is performed immediately.
setDisplayLayoutNeeded();
mWmService.requestTraversal();
}
if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onWindowTransition(this, transit);
}
}
final boolean isAnimating = mAnimatingExit
|| isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES);
final boolean lastWindowIsStartingWindow = startingWindow && mActivityRecord != null
&& mActivityRecord.isLastWindow(this);
// We delay the removal of a window if it has a showing surface that can be used to run
// exit animation and it is marked as exiting.
// Also, If isn't the an animating starting window that is the last window in the app.
// We allow the removal of the non-animating starting window now as there is no
// additional window or animation that will trigger its removal.
if (mWinAnimator.getShown() && !lastWindowIsStartingWindow && isAnimating) {
// Make isSelfOrAncestorWindowAnimatingExit return true so onExitAnimationDone
// can proceed to remove this window.
mAnimatingExit = true;
// The exit animation is running or should run... wait for it!
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Not removing %s due to exit animation", this);
setupWindowForRemoveOnExit();
if (mActivityRecord != null) {
mActivityRecord.updateReportedVisibilityLocked();
}
return;
}
}
removeImmediately();
// Removing a visible window will effect the computed orientation
// So just update orientation if needed.
if (wasVisible) {
final DisplayContent displayContent = getDisplayContent();
if (displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
}
mWmService.updateFocusedWindowLocked(isFocused()
? UPDATE_FOCUS_REMOVING_FOCUS
: UPDATE_FOCUS_NORMAL,
true /*updateInputWindows*/);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
private void setupWindowForRemoveOnExit() {
mRemoveOnExit = true;
setDisplayLayoutNeeded();
getDisplayContent().getDisplayPolicy().removeWindowLw(this);
// Request a focus update as this window's input channel is already gone. Otherwise
// we could have no focused window in input manager.
final boolean focusChanged = mWmService.updateFocusedWindowLocked(
UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
mWmService.mWindowPlacerLocked.performSurfacePlacement();
if (focusChanged) {
getDisplayContent().getInputMonitor().updateInputWindowsLw(false /*force*/);
}
}
void setHasSurface(boolean hasSurface) {
mHasSurface = hasSurface;
}
/**
* Checks whether one of the Windows in a Display embedded in this Window can be an IME target.
*/
private boolean canWindowInEmbeddedDisplayBeImeTarget() {
final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size();
for (int i = embeddedDisplayContentsSize - 1; i >= 0; i--) {
final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
if (edc.forAllWindows(WindowState::canBeImeTarget, true)) {
return true;
}
}
return false;
}
boolean canBeImeTarget() {
// If any of the embedded windows can be the IME target, this window will be the final IME
// target. This is because embedded windows are on a different display in WM so it would
// cause confusion trying to set the IME to a window on a different display. Instead, just
// make the host window the IME target.
if (canWindowInEmbeddedDisplayBeImeTarget()) {
return true;
}
if (mIsImWindow) {
// IME windows can't be IME targets. IME targets are required to be below the IME
// windows and that wouldn't be possible if the IME window is its own target...silly.
return false;
}
if (inPinnedWindowingMode()) {
return false;
}
if (mAttrs.type == TYPE_SCREENSHOT) {
// Disallow screenshot windows from being IME targets
return false;
}
final boolean windowsAreFocusable = mActivityRecord == null || mActivityRecord.windowsAreFocusable();
if (!windowsAreFocusable) {
// This window can't be an IME target if the app's windows should not be focusable.
return false;
}
final Task rootTask = getRootTask();
if (rootTask != null && !rootTask.isFocusable()) {
// Ignore when the root task shouldn't receive input event.
// (i.e. the minimized root task in split screen mode.)
return false;
}
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Ignore mayUseInputMethod for starting window for now.
// TODO(b/159911356): Remove this special casing (originally added in commit e75d872).
} else {
// TODO(b/145812508): Clean this up in S, may depend on b/141738570
// The current logic lets windows become the "ime target" even though they are
// not-focusable and can thus never actually start input.
// Ideally, this would reject windows where mayUseInputMethod() == false, but this
// also impacts Z-ordering of and delivery of IME insets to child windows, which means
// that simply disallowing non-focusable windows would break apps.
// See b/159438771, b/144619551.
final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
// Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are
// set or both are cleared...and not a starting window.
if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) {
return false;
}
}
// Don't allow transient-launch activities to take IME.
if (rootTask != null && mActivityRecord != null
&& mTransitionController.isTransientLaunch(mActivityRecord)) {
return false;
}
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG_WM, "isVisibleRequestedOrAdding " + this + ": "
+ isVisibleRequestedOrAdding());
if (!isVisibleRequestedOrAdding()) {
Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
+ " policyVisAfterAnim=" + mLegacyPolicyVisibilityAfterAnim
+ " parentHidden=" + isParentWindowHidden()
+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
if (mActivityRecord != null) {
Slog.i(TAG_WM, " mActivityRecord.visibleRequested="
+ mActivityRecord.mVisibleRequested);
}
}
}
return isVisibleRequestedOrAdding();
}
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mWmService.mH.getLooper());
}
@Override
public void onInputEvent(InputEvent event) {
finishInputEvent(event, true);
}
}
/** Fake event receiver for windows that died visible. */
private DeadWindowEventReceiver mDeadWindowEventReceiver;
void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
String name = getName();
mInputChannel = mWmService.mInputManager.createInputChannel(name);
mInputChannelToken = mInputChannel.getToken();
mInputWindowHandle.setToken(mInputChannelToken);
mWmService.mInputToWindowMap.put(mInputChannelToken, this);
if (outInputChannel != null) {
mInputChannel.copyTo(outInputChannel);
} else {
// If the window died visible, we setup a fake input channel, so that taps
// can still detected by input monitor channel, and we can relaunch the app.
// Create fake event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
}
}
/**
* Move the touch gesture from the currently touched window on this display to this window.
*/
public boolean transferTouch() {
return mWmService.mInputManager.transferTouch(mInputChannelToken);
}
void disposeInputChannel() {
if (mDeadWindowEventReceiver != null) {
mDeadWindowEventReceiver.dispose();
mDeadWindowEventReceiver = null;
}
if (mInputChannelToken != null) {
// Unregister server channel first otherwise it complains about broken channel.
mWmService.mInputManager.removeInputChannel(mInputChannelToken);
mWmService.mKeyInterceptionInfoForToken.remove(mInputChannelToken);
mWmService.mInputToWindowMap.remove(mInputChannelToken);
mInputChannelToken = null;
}
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
mInputWindowHandle.setToken(null);
}
/** Returns true if the replacement window was removed. */
boolean removeReplacedWindowIfNeeded(WindowState replacement) {
if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawn()) {
replacement.mSkipEnterAnimationForSeamlessReplacement = false;
removeReplacedWindow();
return true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
if (c.removeReplacedWindowIfNeeded(replacement)) {
return true;
}
}
return false;
}
private void removeReplacedWindow() {
ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Removing replaced window: %s", this);
mWillReplaceWindow = false;
mAnimateReplacingWindow = false;
mReplacingRemoveRequested = false;
mReplacementWindow = null;
if (mAnimatingExit || !mAnimateReplacingWindow) {
removeImmediately();
}
}
boolean setReplacementWindowIfNeeded(WindowState replacementCandidate) {
boolean replacementSet = false;
if (mWillReplaceWindow && mReplacementWindow == null
&& getWindowTag().toString().equals(replacementCandidate.getWindowTag().toString())) {
mReplacementWindow = replacementCandidate;
replacementCandidate.mSkipEnterAnimationForSeamlessReplacement = !mAnimateReplacingWindow;
replacementSet = true;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
replacementSet |= c.setReplacementWindowIfNeeded(replacementCandidate);
}
return replacementSet;
}
void setDisplayLayoutNeeded() {
final DisplayContent dc = getDisplayContent();
if (dc != null) {
dc.setLayoutNeeded();
}
}
@Override
void switchUser(int userId) {
super.switchUser(userId);
if (showToCurrentUser()) {
setPolicyVisibilityFlag(VISIBLE_FOR_USER);
} else {
if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
+ ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
clearPolicyVisibilityFlag(VISIBLE_FOR_USER);
}
}
void getSurfaceTouchableRegion(Region region, WindowManager.LayoutParams attrs) {
final boolean modal = attrs.isModal();
if (modal) {
if (mActivityRecord != null) {
// Limit the outer touch to the activity root task region.
updateRegionForModalActivityWindow(region);
} else {
// Give it a large touchable region at first because it was touch modal. The window
// might be moved on the display, so the touchable region should be large enough to
// ensure it covers the whole display, no matter where it is moved.
getDisplayContent().getBounds(mTmpRect);
final int dw = mTmpRect.width();
final int dh = mTmpRect.height();
region.set(-dw, -dh, dw + dw, dh + dh);
}
subtractTouchExcludeRegionIfNeeded(region);
} else {
// Not modal
getTouchableRegion(region);
}
// Translate to surface based coordinates.
final Rect frame = mWindowFrames.mFrame;
if (frame.left != 0 || frame.top != 0) {
region.translate(-frame.left, -frame.top);
}
// TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
// scaling but the existing logic doesn't expect that. The result is that the already-
// scaled region ends up getting sent to surfaceflinger which then applies the scale
// (again). Until this is resolved, apply an inverse-scale here.
if (mInvGlobalScale != 1.f) {
region.scale(mInvGlobalScale);
}
}
/**
* Expands the given rectangle by the region of window resize handle for freeform window.
* @param inOutRect The rectangle to update.
*/
private void adjustRegionInFreefromWindowMode(Rect inOutRect) {
if (!inFreeformWindowingMode()) {
return;
}
// For freeform windows, we need the touch region to include the whole
// surface for the shadows.
final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
final int delta = WindowManagerService.dipToPixel(
RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
inOutRect.inset(-delta, -delta);
}
/**
* Updates the region for a window in an Activity that was a touch modal. This will limit
* the outer touch to the activity root task region.
* @param outRegion The region to update.
*/
private void updateRegionForModalActivityWindow(Region outRegion) {
// If the inner bounds of letterbox is available, then it will be used as the
// touchable region so it won't cover the touchable letterbox and the touch
// events can slip to activity from letterbox.
mActivityRecord.getLetterboxInnerBounds(mTmpRect);
if (mTmpRect.isEmpty()) {
final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
if (transformedBounds != null) {
// Task is in the same orientation as display, so the rotated bounds should be
// chosen as the touchable region. Then when the surface layer transforms the
// region to display space, the orientation will be consistent.
mTmpRect.set(transformedBounds);
} else {
// If this is a modal window we need to dismiss it if it's not full screen
// and the touch happens outside of the frame that displays the content. This
// means we need to intercept touches outside of that window. The dim layer
// user associated with the window (task or root task) will give us the good
// bounds, as they would be used to display the dim layer.
final TaskFragment taskFragment = getTaskFragment();
if (taskFragment != null) {
final Task task = taskFragment.asTask();
if (task != null) {
task.getDimBounds(mTmpRect);
} else {
mTmpRect.set(taskFragment.getBounds());
}
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
}
}
adjustRegionInFreefromWindowMode(mTmpRect);
outRegion.set(mTmpRect);
cropRegionToRootTaskBoundsIfNeeded(outRegion);
}
void checkPolicyVisibilityChange() {
if (isLegacyPolicyVisibility() != mLegacyPolicyVisibilityAfterAnim) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Policy visibility changing after anim in " +
mWinAnimator + ": " + mLegacyPolicyVisibilityAfterAnim);
}
if (mLegacyPolicyVisibilityAfterAnim) {
setPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
} else {
clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
}
if (!isVisibleByPolicy()) {
mWinAnimator.hide(getGlobalTransaction(), "checkPolicyVisibilityChange");
if (isFocused()) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
"setAnimationLocked: setting mFocusMayChange true");
mWmService.mFocusMayChange = true;
}
setDisplayLayoutNeeded();
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
// we allow the display to be enabled now.
mWmService.enableScreenIfNeededLocked();
}
}
}
void setRequestedSize(int requestedWidth, int requestedHeight) {
if ((mRequestedWidth != requestedWidth || mRequestedHeight != requestedHeight)) {
mLayoutNeeded = true;
mRequestedWidth = requestedWidth;
mRequestedHeight = requestedHeight;
}
}
void prepareWindowToDisplayDuringRelayout(boolean wasVisible) {
// We need to turn on screen regardless of visibility.
final boolean hasTurnScreenOnFlag = (mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0
|| (mActivityRecord != null && mActivityRecord.canTurnScreenOn());
// The screen will turn on if the following conditions are met
// 1. The window has the flag FLAG_TURN_SCREEN_ON or ActivityRecord#canTurnScreenOn.
// 2. The WMS allows theater mode.
// 3. No AWT or the AWT allows the screen to be turned on. This should only be true once
// per resume to prevent the screen getting getting turned on for each relayout. Set
// currentLaunchCanTurnScreenOn will be set to false so the window doesn't turn the screen
// on again during this resume.
// 4. When the screen is not interactive. This is because when the screen is already
// interactive, the value may persist until the next animation, which could potentially
// be occurring while turning off the screen. This would lead to the screen incorrectly
// turning back on.
if (hasTurnScreenOnFlag) {
boolean allowTheaterMode = mWmService.mAllowTheaterModeWakeFromLayout
|| Settings.Global.getInt(mWmService.mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0) == 0;
boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn();
if (allowTheaterMode && canTurnScreenOn
&& (mWmService.mAtmInternal.isDreaming()
|| !mPowerManagerWrapper.isInteractive())) {
if (DEBUG_VISIBILITY || DEBUG_POWER) {
Slog.v(TAG, "Relayout window turning screen on: " + this);
}
mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
}
if (mActivityRecord != null) {
mActivityRecord.setCurrentLaunchCanTurnScreenOn(false);
}
}
// If we were already visible, skip rest of preparation.
if (wasVisible) {
if (DEBUG_VISIBILITY) Slog.v(TAG,
"Already visible and does not turn on screen, skip preparing: " + this);
return;
}
if ((mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
== SOFT_INPUT_ADJUST_RESIZE) {
mLayoutNeeded = true;
}
if (isDrawn() && mToken.okToAnimate()) {
mWinAnimator.applyEnterAnimationLocked();
}
}
private Configuration getProcessGlobalConfiguration() {
// For child windows we want to use the pid for the parent window in case the the child
// window was added from another process.
final WindowState parentWindow = getParentWindow();
final int pid = parentWindow != null ? parentWindow.mSession.mPid : mSession.mPid;
final Configuration processConfig =
mWmService.mAtmService.getGlobalConfigurationForPid(pid);
return processConfig;
}
private Configuration getLastReportedConfiguration() {
return mLastReportedConfiguration.getMergedConfiguration();
}
void adjustStartingWindowFlags() {
if (mAttrs.type == TYPE_BASE_APPLICATION && mActivityRecord != null
&& mActivityRecord.mStartingWindow != null) {
// Special handling of starting window over the base
// window of the app: propagate lock screen flags to it,
// to provide the correct semantics while starting.
final int mask = FLAG_SHOW_WHEN_LOCKED | FLAG_DISMISS_KEYGUARD
| FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
WindowManager.LayoutParams sa = mActivityRecord.mStartingWindow.mAttrs;
sa.flags = (sa.flags & ~mask) | (mAttrs.flags & mask);
}
}
void setWindowScale(int requestedWidth, int requestedHeight) {
final boolean scaledWindow = (mAttrs.flags & FLAG_SCALED) != 0;
if (scaledWindow) {
// requested{Width|Height} Surface's physical size
// attrs.{width|height} Size on screen
// TODO: We don't check if attrs != null here. Is it implicitly checked?
mHScale = (mAttrs.width != requestedWidth) ?
(mAttrs.width / (float)requestedWidth) : 1.0f;
mVScale = (mAttrs.height != requestedHeight) ?
(mAttrs.height / (float)requestedHeight) : 1.0f;
} else {
mHScale = mVScale = 1;
}
}
private class DeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
try {
boolean resetSplitScreenResizing = false;
synchronized (mWmService.mGlobalLock) {
final WindowState win = mWmService
.windowForClientLocked(mSession, mClient, false);
Slog.i(TAG, "WIN DEATH: " + win);
if (win != null) {
final DisplayContent dc = getDisplayContent();
if (win.mActivityRecord != null && win.mActivityRecord.findMainWindow() == win) {
mWmService.mTaskSnapshotController.onAppDied(win.mActivityRecord);
}
win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
} else if (mHasSurface) {
Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
WindowState.this.removeIfPossible();
}
}
if (resetSplitScreenResizing) {
try {
// Note: this calls into ActivityManager, so we must *not* hold the window
// manager lock while calling this.
mWmService.mActivityTaskManager.setSplitScreenResizing(false);
} catch (RemoteException e) {
// Local call, shouldn't return RemoteException.
throw e.rethrowAsRuntimeException();
}
}
} catch (IllegalArgumentException ex) {
// This will happen if the window has already been removed.
}
}
}
/**
* Returns true if this window is visible and belongs to a dead app and shouldn't be removed,
* because we want to preserve its location on screen to be re-activated later when the user
* interacts with it.
*/
private boolean shouldKeepVisibleDeadAppWindow() {
if (!isWinVisibleLw() || mActivityRecord == null || !mActivityRecord.isClientVisible()) {
// Not a visible app window or the app isn't dead.
return false;
}
if (mAttrs.token != mClient.asBinder()) {
// The window was add by a client using another client's app token. We don't want to
// keep the dead window around for this case since this is meant for 'real' apps.
return false;
}
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// We don't keep starting windows since they were added by the window manager before
// the app even launched.
return false;
}
return getWindowConfiguration().keepVisibleDeadAppWindowOnScreen();
}
/** Returns {@code true} if this window desires key events. */
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
}
public String canReceiveKeysReason(boolean fromUserTouch) {
return "fromTouch= " + fromUserTouch
+ " isVisibleRequestedOrAdding=" + isVisibleRequestedOrAdding()
+ " mViewVisibility=" + mViewVisibility
+ " mRemoveOnExit=" + mRemoveOnExit
+ " flags=" + mAttrs.flags
+ " appWindowsAreFocusable="
+ (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
+ " canReceiveTouchInput=" + canReceiveTouchInput()
+ " displayIsOnTop=" + getDisplayContent().isOnTop()
+ " displayIsTrusted=" + getDisplayContent().isTrusted();
}
public boolean canReceiveKeys(boolean fromUserTouch) {
final boolean canReceiveKeys = isVisibleRequestedOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
&& canReceiveTouchInput();
if (!canReceiveKeys) {
return false;
}
// Do not allow untrusted virtual display to receive keys unless user intentionally
// touches the display.
return fromUserTouch || getDisplayContent().isOnTop()
|| getDisplayContent().isTrusted();
}
@Override
public boolean canShowWhenLocked() {
final boolean showBecauseOfActivity =
mActivityRecord != null && mActivityRecord.canShowWhenLocked();
final boolean showBecauseOfWindow = (getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0;
return showBecauseOfActivity || showBecauseOfWindow;
}
/**
* @return {@code true} if this window can receive touches based on among other things,
* windowing state and recents animation state.
**/
boolean canReceiveTouchInput() {
if (mActivityRecord == null || mActivityRecord.getTask() == null) {
return true;
}
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.mVisibleRequested
&& !isRecentsAnimationConsumingAppInput();
}
/**
* Returns {@code true} if the window is animating to home as part of the recents animation and
* it is consuming input from the app.
*/
private boolean isRecentsAnimationConsumingAppInput() {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
return recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(mActivityRecord);
}
/**
* Returns {@code true} if this window has been shown on screen at some time in the past.
*
* @deprecated Use {@link #isDrawn} or any of the other drawn/visibility methods.
*/
@Deprecated
boolean hasDrawn() {
return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
}
/**
* Can be called to undo the effect of {@link #hide}, allowing a window to be shown as long
* as the client would also like it to be shown.
*/
boolean show(boolean doAnimation, boolean requestAnim) {
if (isLegacyPolicyVisibility() && mLegacyPolicyVisibilityAfterAnim) {
// Already showing.
return false;
}
if (!showToCurrentUser()) {
return false;
}
if (!mAppOpVisibility) {
// Being hidden due to app op request.
return false;
}
if (mPermanentlyHidden) {
// Permanently hidden until the app exists as apps aren't prepared
// to handle their windows being removed from under them.
return false;
}
if (mHiddenWhileSuspended) {
// Being hidden due to owner package being suspended.
return false;
}
if (mForceHideNonSystemOverlayWindow) {
// This is an alert window that is currently force hidden.
return false;
}
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this);
if (doAnimation) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility="
+ isLegacyPolicyVisibility()
+ " animating=" + isAnimating(TRANSITION | PARENTS));
if (!mToken.okToAnimate()) {
doAnimation = false;
} else if (isLegacyPolicyVisibility() && !isAnimating(TRANSITION | PARENTS)) {
// Check for the case where we are currently visible and
// not animating; we do not want to do animation at such a
// point to become visible when we already are.
doAnimation = false;
}
}
setPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
mLegacyPolicyVisibilityAfterAnim = true;
if (doAnimation) {
mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true);
}
if (requestAnim) {
mWmService.scheduleAnimationLocked();
}
if ((mAttrs.flags & FLAG_NOT_FOCUSABLE) == 0) {
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */);
}
return true;
}
/** Forces the window to be hidden, regardless of whether the client like it shown. */
boolean hide(boolean doAnimation, boolean requestAnim) {
if (doAnimation) {
if (!mToken.okToAnimate()) {
doAnimation = false;
}
}
boolean current =
doAnimation ? mLegacyPolicyVisibilityAfterAnim : isLegacyPolicyVisibility();
if (!current) {
// Already hiding.
return false;
}
if (doAnimation) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
if (!isAnimating(TRANSITION | PARENTS)) {
doAnimation = false;
}
}
mLegacyPolicyVisibilityAfterAnim = false;
final boolean isFocused = isFocused();
if (!doAnimation) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
// we allow the display to be enabled now.
mWmService.enableScreenIfNeededLocked();
if (isFocused) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
"WindowState.hideLw: setting mFocusMayChange true");
mWmService.mFocusMayChange = true;
}
}
if (requestAnim) {
mWmService.scheduleAnimationLocked();
}
if (isFocused) {
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */);
}
return true;
}
void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
if (mSession.mCanAddInternalSystemWindow
|| (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
return;
}
if (mAttrs.type == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay()
&& mSession.mCanCreateSystemApplicationOverlay) {
return;
}
if (mForceHideNonSystemOverlayWindow == forceHide) {
return;
}
mForceHideNonSystemOverlayWindow = forceHide;
if (forceHide) {
hide(true /* doAnimation */, true /* requestAnim */);
} else {
show(true /* doAnimation */, true /* requestAnim */);
}
}
void setHiddenWhileSuspended(boolean hide) {
if (mOwnerCanAddInternalSystemWindow
|| (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
return;
}
if (mHiddenWhileSuspended == hide) {
return;
}
mHiddenWhileSuspended = hide;
if (hide) {
hide(true /* doAnimation */, true /* requestAnim */);
} else {
show(true /* doAnimation */, true /* requestAnim */);
}
}
private void setAppOpVisibilityLw(boolean state) {
if (mAppOpVisibility != state) {
mAppOpVisibility = state;
if (state) {
// If the policy visibility had last been to hide, then this
// will incorrectly show at this point since we lost that
// information. Not a big deal -- for the windows that have app
// ops modifies they should only be hidden by policy due to the
// lock screen, and the user won't be changing this if locked.
// Plus it will quickly be fixed the next time we do a layout.
show(true /* doAnimation */, true /* requestAnim */);
} else {
hide(true /* doAnimation */, true /* requestAnim */);
}
}
}
void initAppOpsState() {
if (mAppOp == OP_NONE || !mAppOpVisibility) {
return;
}
// If the app op was MODE_DEFAULT we would have checked the permission
// and add the window only if the permission was granted. Therefore, if
// the mode is MODE_DEFAULT we want the op to succeed as the window is
// shown.
final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, getOwningUid(),
getOwningPackage(), true /* startIfModeDefault */, null /* featureId */,
"init-default-visibility");
if (mode != MODE_ALLOWED && mode != MODE_DEFAULT) {
setAppOpVisibilityLw(false);
}
}
void resetAppOpsState() {
if (mAppOp != OP_NONE && mAppOpVisibility) {
mWmService.mAppOps.finishOp(mAppOp, getOwningUid(), getOwningPackage(),
null /* featureId */);
}
}
void updateAppOpsState() {
if (mAppOp == OP_NONE) {
return;
}
final int uid = getOwningUid();
final String packageName = getOwningPackage();
if (mAppOpVisibility) {
// There is a race between the check and the finish calls but this is fine
// as this would mean we will get another change callback and will reconcile.
int mode = mWmService.mAppOps.checkOpNoThrow(mAppOp, uid, packageName);
if (mode != MODE_ALLOWED && mode != MODE_DEFAULT) {
mWmService.mAppOps.finishOp(mAppOp, uid, packageName, null /* featureId */);
setAppOpVisibilityLw(false);
}
} else {
final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, uid, packageName,
true /* startIfModeDefault */, null /* featureId */, "attempt-to-be-visible");
if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
setAppOpVisibilityLw(true);
}
}
}
public void hidePermanentlyLw() {
if (!mPermanentlyHidden) {
mPermanentlyHidden = true;
hide(true /* doAnimation */, true /* requestAnim */);
}
}
public void pokeDrawLockLw(long timeout) {
if (isVisibleRequestedOrAdding()) {
if (mDrawLock == null) {
// We want the tag name to be somewhat stable so that it is easier to correlate
// in wake lock statistics. So in particular, we don't want to include the
// window's hash code as in toString().
final CharSequence tag = getWindowTag();
mDrawLock = mWmService.mPowerManager.newWakeLock(DRAW_WAKE_LOCK, "Window:" + tag);
mDrawLock.setReferenceCounted(false);
mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
}
// Each call to acquire resets the timeout.
if (DEBUG_POWER) {
Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by "
+ mAttrs.packageName);
}
mDrawLock.acquire(timeout);
} else if (DEBUG_POWER) {
Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window "
+ "owned by " + mAttrs.packageName);
}
}
/** Checks whether the process hosting this window is currently alive. */
boolean isAlive() {
return mClient.asBinder().isBinderAlive();
}
boolean isClosing() {
return mAnimatingExit || (mActivityRecord != null && mActivityRecord.isClosingOrEnteringPip());
}
void addWinAnimatorToList(ArrayList animators) {
animators.add(mWinAnimator);
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
c.addWinAnimatorToList(animators);
}
}
void sendAppVisibilityToClients() {
super.sendAppVisibilityToClients();
final boolean clientVisible = mToken.isClientVisible();
// TODO(shell-transitions): This is currently only applicable to app windows, BUT we
// want to extend the "starting" concept to other windows.
if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) {
// Don't hide the starting window.
return;
}
try {
if (DEBUG_VISIBILITY) Slog.v(TAG,
"Setting visibility of " + this + ": " + clientVisible);
mClient.dispatchAppVisibility(clientVisible);
} catch (RemoteException e) {
}
}
void onStartFreezingScreen() {
mAppFreezing = true;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
c.onStartFreezingScreen();
}
}
boolean onStopFreezingScreen() {
boolean unfrozeWindows = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
unfrozeWindows |= c.onStopFreezingScreen();
}
if (!mAppFreezing) {
return unfrozeWindows;
}
mAppFreezing = false;
if (mHasSurface && !getOrientationChanging()
&& mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"set mOrientationChanging of %s", this);
setOrientationChanging(true);
}
mLastFreezeDuration = 0;
setDisplayLayoutNeeded();
return true;
}
boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
boolean destroyedSomething = false;
// Copying to a different list as multiple children can be removed.
final ArrayList childWindows = new ArrayList<>(mChildren);
for (int i = childWindows.size() - 1; i >= 0; --i) {
final WindowState c = childWindows.get(i);
destroyedSomething |= c.destroySurface(cleanupOnResume, appStopped);
}
if (!(appStopped || mWindowRemovalAllowed || cleanupOnResume)) {
return destroyedSomething;
}
if (mDestroying) {
ProtoLog.e(WM_DEBUG_ADD_REMOVE, "win=%s"
+ " destroySurfaces: appStopped=%b"
+ " win.mWindowRemovalAllowed=%b"
+ " win.mRemoveOnExit=%b", this, appStopped,
mWindowRemovalAllowed, mRemoveOnExit);
if (!cleanupOnResume || mRemoveOnExit) {
destroySurfaceUnchecked();
}
if (mRemoveOnExit) {
removeImmediately();
}
if (cleanupOnResume) {
requestUpdateWallpaperIfNeeded();
}
mDestroying = false;
destroyedSomething = true;
// Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
// traversal in case we are waiting on this window to start the transition.
if (getDisplayContent().mAppTransition.isTransitionSet()
&& getDisplayContent().mOpeningApps.contains(mActivityRecord)) {
mWmService.mWindowPlacerLocked.requestTraversal();
}
}
return destroyedSomething;
}
// Destroy or save the application surface without checking
// various indicators of whether the client has released the surface.
// This is in general unsafe, and most callers should use {@link #destroySurface}
void destroySurfaceUnchecked() {
mWinAnimator.destroySurfaceLocked(mTmpTransaction);
mTmpTransaction.apply();
// Clear animating flags now, since the surface is now gone. (Note this is true even
// if the surface is saved, to outside world the surface is still NO_SURFACE.)
mAnimatingExit = false;
}
void onSurfaceShownChanged(boolean shown) {
if (mLastShownChangedReported == shown) {
return;
}
mLastShownChangedReported = shown;
if (shown) {
initExclusionRestrictions();
} else {
logExclusionRestrictions(EXCLUSION_LEFT);
logExclusionRestrictions(EXCLUSION_RIGHT);
}
// Exclude toast because legacy apps may show toast window by themselves, so the misused
// apps won't always be considered as foreground state.
// Exclude private presentations as they can only be shown on private virtual displays and
// shouldn't be the cause of an app be considered foreground.
if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST
&& mAttrs.type != TYPE_PRIVATE_PRESENTATION) {
mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
}
if (mIsImWindow && mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onImeSurfaceShownChanged(this, shown);
}
}
private void logExclusionRestrictions(int side) {
if (!logsGestureExclusionRestrictions(this)
|| SystemClock.uptimeMillis() < mLastExclusionLogUptimeMillis[side]
+ mWmService.mConstants.mSystemGestureExclusionLogDebounceTimeoutMillis) {
// Drop the log if we have just logged; this is okay, because what we would have logged
// was true only for a short duration.
return;
}
final long now = SystemClock.uptimeMillis();
final long duration = now - mLastExclusionLogUptimeMillis[side];
mLastExclusionLogUptimeMillis[side] = now;
final int requested = mLastRequestedExclusionHeight[side];
final int granted = mLastGrantedExclusionHeight[side];
FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
mAttrs.packageName, requested, requested - granted /* rejected */,
side + 1 /* Sides are 1-indexed in atoms.proto */,
(getConfiguration().orientation == ORIENTATION_LANDSCAPE),
isSplitScreenWindowingMode(getWindowingMode()), (int) duration);
}
private void initExclusionRestrictions() {
final long now = SystemClock.uptimeMillis();
mLastExclusionLogUptimeMillis[EXCLUSION_LEFT] = now;
mLastExclusionLogUptimeMillis[EXCLUSION_RIGHT] = now;
}
/** @return {@code true} if this window can be shown to all users. */
boolean showForAllUsers() {
// If this switch statement is modified, modify the comment in the declarations of
// the type in {@link WindowManager.LayoutParams} as well.
switch (mAttrs.type) {
default:
// These are the windows that by default are shown only to the user that created
// them. If this needs to be overridden, set
// {@link WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS} in
// {@link WindowManager.LayoutParams}. Note that permission
// {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well.
if ((mAttrs.privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS) == 0) {
return false;
}
break;
// These are the windows that by default are shown to all users. However, to
// protect against spoofing, check permissions below.
case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
case TYPE_APPLICATION_STARTING:
case TYPE_BOOT_PROGRESS:
case TYPE_DISPLAY_OVERLAY:
case TYPE_INPUT_CONSUMER:
case TYPE_KEYGUARD_DIALOG:
case TYPE_MAGNIFICATION_OVERLAY:
case TYPE_NAVIGATION_BAR:
case TYPE_NAVIGATION_BAR_PANEL:
case TYPE_PHONE:
case TYPE_POINTER:
case TYPE_PRIORITY_PHONE:
case TYPE_SEARCH_BAR:
case TYPE_STATUS_BAR:
case TYPE_NOTIFICATION_SHADE:
case TYPE_STATUS_BAR_ADDITIONAL:
case TYPE_STATUS_BAR_SUB_PANEL:
case TYPE_SYSTEM_DIALOG:
case TYPE_VOLUME_OVERLAY:
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
case TYPE_DOCK_DIVIDER:
break;
}
// Only the system can show free windows to all users.
return mOwnerCanAddInternalSystemWindow;
}
@Override
boolean showToCurrentUser() {
// Child windows are evaluated based on their parent window.
final WindowState win = getTopParentWindow();
if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
&& win.mActivityRecord != null && win.mActivityRecord.mShowForAllUsers) {
// All window frames that are fullscreen extend above status bar, but some don't extend
// below navigation bar. Thus, check for display frame for top/left and stable frame for
// bottom right.
if (win.getFrame().left <= win.getDisplayFrame().left
&& win.getFrame().top <= win.getDisplayFrame().top
&& win.getFrame().right >= win.getDisplayFrame().right
&& win.getFrame().bottom >= win.getDisplayFrame().bottom) {
// Is a fullscreen window, like the clock alarm. Show to everyone.
return true;
}
}
return win.showForAllUsers()
|| mWmService.isCurrentProfile(win.mShowUserId);
}
private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
outRegion.set(
frame.left + inset.left, frame.top + inset.top,
frame.right - inset.right, frame.bottom - inset.bottom);
}
/** Get the touchable region in global coordinates. */
void getTouchableRegion(Region outRegion) {
final Rect frame = mWindowFrames.mFrame;
switch (mTouchableInsets) {
default:
case TOUCHABLE_INSETS_FRAME:
outRegion.set(frame);
break;
case TOUCHABLE_INSETS_CONTENT:
applyInsets(outRegion, frame, mGivenContentInsets);
break;
case TOUCHABLE_INSETS_VISIBLE:
applyInsets(outRegion, frame, mGivenVisibleInsets);
break;
case TOUCHABLE_INSETS_REGION: {
outRegion.set(mGivenTouchableRegion);
if (frame.left != 0 || frame.top != 0) {
outRegion.translate(frame.left, frame.top);
}
break;
}
}
cropRegionToRootTaskBoundsIfNeeded(outRegion);
subtractTouchExcludeRegionIfNeeded(outRegion);
}
/**
* Get the effective touchable region in global coordinates.
*
* In contrast to {@link #getTouchableRegion}, this takes into account
* {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.}
*/
void getEffectiveTouchableRegion(Region outRegion) {
final DisplayContent dc = getDisplayContent();
if (mAttrs.isModal() && dc != null) {
outRegion.set(dc.getBounds());
cropRegionToRootTaskBoundsIfNeeded(outRegion);
subtractTouchExcludeRegionIfNeeded(outRegion);
} else {
getTouchableRegion(outRegion);
}
}
private void cropRegionToRootTaskBoundsIfNeeded(Region region) {
final Task task = getTask();
if (task == null || !task.cropWindowsToRootTaskBounds()) {
return;
}
final Task rootTask = task.getRootTask();
if (rootTask == null || rootTask.mCreatedByOrganizer) {
return;
}
rootTask.getDimBounds(mTmpRect);
adjustRegionInFreefromWindowMode(mTmpRect);
region.op(mTmpRect, Region.Op.INTERSECT);
}
/**
* If this window has areas that cannot be touched, we subtract those areas from its touchable
* region.
*/
private void subtractTouchExcludeRegionIfNeeded(Region touchableRegion) {
if (mTapExcludeRegion.isEmpty()) {
return;
}
final Region touchExcludeRegion = Region.obtain();
getTapExcludeRegion(touchExcludeRegion);
if (!touchExcludeRegion.isEmpty()) {
touchableRegion.op(touchExcludeRegion, Region.Op.DIFFERENCE);
}
touchExcludeRegion.recycle();
}
/**
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
void reportFocusChangedSerialized(boolean focused) {
if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i= 0; i--) {
final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
edc.notifyLocationInParentDisplayChanged();
}
}
// If this window is in a embedded display which is re-parented to another window,
// we may need to update its correct on-screen location.
final DisplayContent dc = getDisplayContent();
if (dc.getParentWindow() == null) {
return;
}
final Point offset = dc.getLocationInParentDisplay();
if (mLastReportedDisplayOffset.equals(offset)) {
return;
}
mLastReportedDisplayOffset.set(offset.x, offset.y);
try {
mClient.locationInParentDisplayChanged(mLastReportedDisplayOffset);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to update offset from DisplayContent", e);
}
}
/**
* Called when the insets state changed.
*/
void notifyInsetsChanged() {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
try {
mClient.insetsChanged(getCompatInsetsState(),
hasMoved(),
mWindowFrames.isFrameSizeChangeReported());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset state change w=" + this, e);
}
}
@Override
public void notifyInsetsControlChanged() {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
if (mAppDied || mRemoved) {
return;
}
final InsetsStateController stateController =
getDisplayContent().getInsetsStateController();
try {
mClient.insetsControlChanged(getCompatInsetsState(),
stateController.getControlsForDispatch(this),
hasMoved(),
mWindowFrames.isFrameSizeChangeReported());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e);
}
}
@Override
public WindowState getWindow() {
return this;
}
@Override
public void showInsets(@InsetsType int types, boolean fromIme) {
try {
mClient.showInsets(types, fromIme);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
}
}
@Override
public void hideInsets(@InsetsType int types, boolean fromIme) {
try {
mClient.hideInsets(types, fromIme);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
}
}
@Override
public boolean canShowTransient() {
return (mAttrs.insetsFlags.behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
}
private int getRootTaskId() {
final Task rootTask = getRootTask();
if (rootTask == null) {
return INVALID_TASK_ID;
}
return rootTask.mTaskId;
}
public void registerFocusObserver(IWindowFocusObserver observer) {
synchronized (mWmService.mGlobalLock) {
if (mFocusCallbacks == null) {
mFocusCallbacks = new RemoteCallbackList();
}
mFocusCallbacks.register(observer);
}
}
public void unregisterFocusObserver(IWindowFocusObserver observer) {
synchronized (mWmService.mGlobalLock) {
if (mFocusCallbacks != null) {
mFocusCallbacks.unregister(observer);
}
}
}
boolean isFocused() {
return getDisplayContent().mCurrentFocus == this;
}
/** Is this window in a container that takes up the entire screen space? */
private boolean inAppWindowThatMatchesParentBounds() {
return mActivityRecord == null || (mActivityRecord.matchParentBounds() && !inMultiWindowMode());
}
/**
* @return true if activity bounds are letterboxed or letterboxed for diplay cutout.
*
* Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
return mActivityRecord != null
&& (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
}
/** Returns {@code true} if the window is letterboxed for the display cutout. */
boolean isLetterboxedForDisplayCutout() {
if (mActivityRecord == null) {
// Only windows with an ActivityRecord are letterboxed.
return false;
}
if (!mWindowFrames.parentFrameWasClippedByDisplayCutout()) {
// Cutout didn't make a difference, no letterbox
return false;
}
if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
// Layout in cutout, no letterbox.
return false;
}
if (!mAttrs.isFullscreen()) {
// Not filling the parent frame, no letterbox
return false;
}
// Otherwise we need a letterbox if the layout was smaller than the app window token allowed
// it to be.
return !frameCoversEntireAppTokenBounds();
}
/**
* @return true if this window covers the entire bounds of its app window token
* @throws NullPointerException if there is no app window token for this window
*/
private boolean frameCoversEntireAppTokenBounds() {
mTmpRect.set(mActivityRecord.getBounds());
mTmpRect.intersectUnchecked(mWindowFrames.mFrame);
return mActivityRecord.getBounds().equals(mTmpRect);
}
/**
* @return {@code true} if bar shown within a given frame is allowed to be fully transparent
* when the current window is displayed.
*/
boolean isFullyTransparentBarAllowed(Rect frame) {
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@Override
void setWaitingForDrawnIfResizingChanged() {
if (isDragResizeChanged()) {
mWmService.mRoot.mWaitingForDrawn.add(this);
}
super.setWaitingForDrawnIfResizingChanged();
}
/**
* @return Whether we reported a drag resize change to the application or not already.
*/
private boolean isDragResizingChangeReported() {
return mDragResizingChangeReported;
}
/**
* Resets the state whether we reported a drag resize change to the app.
*/
@Override
void resetDragResizingChangeReported() {
mDragResizingChangeReported = false;
super.resetDragResizingChangeReported();
}
int getResizeMode() {
return mResizeMode;
}
private boolean computeDragResizing() {
final Task task = getTask();
if (task == null) {
return false;
}
if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()
&& !task.getRootTask().mCreatedByOrganizer) {
return false;
}
// TODO(157912944): formalize drag-resizing so that exceptions aren't hardcoded like this
if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
// The current sys-ui implementations never live-resize home, so to prevent WSA from
// creating/destroying surfaces (which messes up sync-transactions), skip HOME tasks.
return false;
}
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
// Floating windows never enter drag resize mode.
return false;
}
if (task.isDragResizing()) {
return true;
}
return getDisplayContent().mDividerControllerLocked.isResizing()
&& !task.inFreeformWindowingMode() && !isGoneForLayout();
}
void setDragResizing() {
final boolean resizing = computeDragResizing();
if (resizing == mDragResizing) {
return;
}
mDragResizing = resizing;
final Task task = getTask();
if (task != null && task.isDragResizing()) {
mResizeMode = task.getDragResizeMode();
} else {
mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
? DRAG_RESIZE_MODE_DOCKED_DIVIDER
: DRAG_RESIZE_MODE_FREEFORM;
}
}
boolean isDragResizing() {
return mDragResizing;
}
boolean isDockedResizing() {
return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
|| (isChildWindow() && getParentWindow().isDockedResizing());
}
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
boolean isVisible = isVisible();
if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
return;
}
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(DISPLAY_ID, getDisplayId());
proto.write(STACK_ID, getRootTaskId());
mAttrs.dumpDebug(proto, ATTRIBUTES);
mGivenContentInsets.dumpDebug(proto, GIVEN_CONTENT_INSETS);
mWindowFrames.dumpDebug(proto, WINDOW_FRAMES);
mAttrs.surfaceInsets.dumpDebug(proto, SURFACE_INSETS);
dumpPointProto(mSurfacePosition, proto, SURFACE_POSITION);
mWinAnimator.dumpDebug(proto, ANIMATOR);
proto.write(ANIMATING_EXIT, mAnimatingExit);
proto.write(REQUESTED_WIDTH, mRequestedWidth);
proto.write(REQUESTED_HEIGHT, mRequestedHeight);
proto.write(VIEW_VISIBILITY, mViewVisibility);
proto.write(HAS_SURFACE, mHasSurface);
proto.write(IS_READY_FOR_DISPLAY, isReadyForDisplay());
proto.write(REMOVE_ON_EXIT, mRemoveOnExit);
proto.write(DESTROYING, mDestroying);
proto.write(REMOVED, mRemoved);
proto.write(IS_ON_SCREEN, isOnScreen());
proto.write(IS_VISIBLE, isVisible);
proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber);
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
proto.end(token);
}
@Override
long getProtoFieldId() {
return WINDOW;
}
@Override
public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
proto.write(USER_ID, mShowUserId);
final CharSequence title = getWindowTag();
if (title != null) {
proto.write(TITLE, title.toString());
}
proto.end(token);
}
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
pw.print(prefix + "mDisplayId=" + getDisplayId());
if (getRootTask() != null) {
pw.print(" rootTaskId=" + getRootTaskId());
}
pw.println(" mSession=" + mSession
+ " mClient=" + mClient.asBinder());
pw.println(prefix + "mOwnerUid=" + mOwnerUid
+ " showForAllUsers=" + showForAllUsers()
+ " package=" + mAttrs.packageName
+ " appop=" + AppOpsManager.opToName(mAppOp));
pw.println(prefix + "mAttrs=" + mAttrs.toString(prefix));
pw.println(prefix + "Requested w=" + mRequestedWidth
+ " h=" + mRequestedHeight
+ " mLayoutSeq=" + mLayoutSeq);
if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
pw.println(prefix + "LastRequested w=" + mLastRequestedWidth
+ " h=" + mLastRequestedHeight);
}
if (mIsChildWindow || mLayoutAttached) {
pw.println(prefix + "mParentWindow=" + getParentWindow()
+ " mLayoutAttached=" + mLayoutAttached);
}
if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) {
pw.println(prefix + "mIsImWindow=" + mIsImWindow
+ " mIsWallpaper=" + mIsWallpaper
+ " mIsFloatingLayer=" + mIsFloatingLayer);
}
if (dumpAll) {
pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
pw.print(" mSubLayer="); pw.print(mSubLayer);
}
if (dumpAll) {
pw.println(prefix + "mToken=" + mToken);
if (mActivityRecord != null) {
pw.println(prefix + "mActivityRecord=" + mActivityRecord);
pw.print(prefix + "mAppDied=" + mAppDied);
pw.print(prefix + "drawnStateEvaluated=" + getDrawnStateEvaluated());
pw.println(prefix + "mightAffectAllDrawn=" + mightAffectAllDrawn());
}
pw.println(prefix + "mViewVisibility=0x" + Integer.toHexString(mViewVisibility)
+ " mHaveFrame=" + mHaveFrame
+ " mObscured=" + mObscured);
if (mDisableFlags != 0) {
pw.println(prefix + "mDisableFlags=" + ViewDebug.flagsToString(
View.class, "mSystemUiVisibility", mDisableFlags));
}
}
if (!isVisibleByPolicy() || !mLegacyPolicyVisibilityAfterAnim || !mAppOpVisibility
|| isParentWindowHidden() || mPermanentlyHidden || mForceHideNonSystemOverlayWindow
|| mHiddenWhileSuspended) {
pw.println(prefix + "mPolicyVisibility=" + isVisibleByPolicy()
+ " mLegacyPolicyVisibilityAfterAnim=" + mLegacyPolicyVisibilityAfterAnim
+ " mAppOpVisibility=" + mAppOpVisibility
+ " parentHidden=" + isParentWindowHidden()
+ " mPermanentlyHidden=" + mPermanentlyHidden
+ " mHiddenWhileSuspended=" + mHiddenWhileSuspended
+ " mForceHideNonSystemOverlayWindow=" + mForceHideNonSystemOverlayWindow);
}
if (!mRelayoutCalled || mLayoutNeeded) {
pw.println(prefix + "mRelayoutCalled=" + mRelayoutCalled
+ " mLayoutNeeded=" + mLayoutNeeded);
}
if (dumpAll) {
pw.println(prefix + "mGivenContentInsets=" + mGivenContentInsets.toShortString(sTmpSB)
+ " mGivenVisibleInsets=" + mGivenVisibleInsets.toShortString(sTmpSB));
if (mTouchableInsets != 0 || mGivenInsetsPending) {
pw.println(prefix + "mTouchableInsets=" + mTouchableInsets
+ " mGivenInsetsPending=" + mGivenInsetsPending);
Region region = new Region();
getTouchableRegion(region);
pw.println(prefix + "touchable region=" + region);
}
pw.println(prefix + "mFullConfiguration=" + getConfiguration());
pw.println(prefix + "mLastReportedConfiguration=" + getLastReportedConfiguration());
}
pw.println(prefix + "mHasSurface=" + mHasSurface
+ " isReadyForDisplay()=" + isReadyForDisplay()
+ " mWindowRemovalAllowed=" + mWindowRemovalAllowed);
if (hasCompatScale()) {
pw.println(prefix + "mCompatFrame=" + mWindowFrames.mCompatFrame.toShortString(sTmpSB));
}
if (dumpAll) {
mWindowFrames.dump(pw, prefix);
pw.println(prefix + " surface=" + mAttrs.surfaceInsets.toShortString(sTmpSB));
}
super.dump(pw, prefix, dumpAll);
pw.println(prefix + mWinAnimator + ":");
mWinAnimator.dump(pw, prefix + " ", dumpAll);
if (mAnimatingExit || mRemoveOnExit || mDestroying || mRemoved) {
pw.println(prefix + "mAnimatingExit=" + mAnimatingExit
+ " mRemoveOnExit=" + mRemoveOnExit
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
if (getOrientationChanging() || mAppFreezing || mReportOrientationChanged) {
pw.println(prefix + "mOrientationChanging=" + mOrientationChanging
+ " configOrientationChanging="
+ (getLastReportedConfiguration().orientation != getConfiguration().orientation)
+ " mAppFreezing=" + mAppFreezing
+ " mReportOrientationChanged=" + mReportOrientationChanged);
}
if (mLastFreezeDuration != 0) {
pw.print(prefix + "mLastFreezeDuration=");
TimeUtils.formatDuration(mLastFreezeDuration, pw);
pw.println();
}
pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
+ " seamlesslyRotate: pending=");
if (mPendingSeamlessRotate != null) {
mPendingSeamlessRotate.dump(pw);
} else {
pw.print("null");
}
pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber);
if (mHScale != 1 || mVScale != 1) {
pw.println(prefix + "mHScale=" + mHScale
+ " mVScale=" + mVScale);
}
if (mWallpaperX != -1 || mWallpaperY != -1) {
pw.println(prefix + "mWallpaperX=" + mWallpaperX
+ " mWallpaperY=" + mWallpaperY);
}
if (mWallpaperXStep != -1 || mWallpaperYStep != -1) {
pw.println(prefix + "mWallpaperXStep=" + mWallpaperXStep
+ " mWallpaperYStep=" + mWallpaperYStep);
}
if (mWallpaperZoomOut != -1) {
pw.println(prefix + "mWallpaperZoomOut=" + mWallpaperZoomOut);
}
if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE
|| mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
pw.println(prefix + "mWallpaperDisplayOffsetX=" + mWallpaperDisplayOffsetX
+ " mWallpaperDisplayOffsetY=" + mWallpaperDisplayOffsetY);
}
if (mDrawLock != null) {
pw.println(prefix + "mDrawLock=" + mDrawLock);
}
if (isDragResizing()) {
pw.println(prefix + "isDragResizing=" + isDragResizing());
}
if (computeDragResizing()) {
pw.println(prefix + "computeDragResizing=" + computeDragResizing());
}
pw.println(prefix + "isOnScreen=" + isOnScreen());
pw.println(prefix + "isVisible=" + isVisible());
if (!mEmbeddedDisplayContents.isEmpty()) {
pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents);
}
if (dumpAll) {
final String visibilityString = mRequestedVisibilities.toString();
if (!visibilityString.isEmpty()) {
pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
}
@Override
String getName() {
return Integer.toHexString(System.identityHashCode(this))
+ " " + getWindowTag();
}
CharSequence getWindowTag() {
CharSequence tag = mAttrs.getTitle();
if (tag == null || tag.length() <= 0) {
tag = mAttrs.packageName;
}
return tag;
}
@Override
public String toString() {
final CharSequence title = getWindowTag();
if (mStringNameCache == null || mLastTitle != title || mWasExiting != mAnimatingExit) {
mLastTitle = title;
mWasExiting = mAnimatingExit;
mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+ " u" + mShowUserId
+ " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
}
return mStringNameCache;
}
private void applyGravityAndUpdateFrame(WindowFrames windowFrames, Rect containingFrame,
Rect displayFrame, DisplayFrames displayFrames) {
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
final WindowManager.LayoutParams attrs = getLayoutingAttrs(displayFrames.mRotation);
final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
// a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
// for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
// FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
// screen, but SurfaceViews want to be always at a specific location so we don't fit it to
// the display.
final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
final boolean hasCompatScale = hasCompatScale();
if ((attrs.flags & FLAG_SCALED) != 0 || mAttrs != attrs) {
// For the window with different layout attrs for different rotations, we need to avoid
// using requested size. Otherwise, when finishing a simulated rotation, the information
// coming from WindowManagerServices to the ViewRootImpl may not contain the correct
// value for the new rotation, and there will be a quick flash of wrong layout when the
// simulated activity faded out.
if (attrs.width < 0) {
w = pw;
} else if (hasCompatScale) {
w = (int) (attrs.width * mGlobalScale + .5f);
} else {
w = attrs.width;
}
if (attrs.height < 0) {
h = ph;
} else if (hasCompatScale) {
h = (int) (attrs.height * mGlobalScale + .5f);
} else {
h = attrs.height;
}
} else {
if (attrs.width == MATCH_PARENT) {
w = pw;
} else if (hasCompatScale) {
w = (int) (mRequestedWidth * mGlobalScale + .5f);
} else {
w = mRequestedWidth;
}
if (attrs.height == MATCH_PARENT) {
h = ph;
} else if (hasCompatScale) {
h = (int) (mRequestedHeight * mGlobalScale + .5f);
} else {
h = mRequestedHeight;
}
}
if (hasCompatScale) {
x = attrs.x * mGlobalScale;
y = attrs.y * mGlobalScale;
} else {
x = attrs.x;
y = attrs.y;
}
if (inNonFullscreenContainer && !layoutInParentFrame()) {
// Make sure window fits in containing frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);
h = Math.min(h, ph);
}
if (mIsChildWindow) {
final WindowState parent = getTopParentWindow();
if (parent.hasCompatScale()) {
// Scale the containing and display frames because they are in screen coordinates.
// The position of frames are already relative to parent so only size is scaled.
mTmpRect.set(containingFrame);
containingFrame = mTmpRect;
CoordinateTransforms.scaleRectSize(containingFrame, parent.mInvGlobalScale);
if (fitToDisplay) {
mTmpRect2.set(displayFrame);
displayFrame = mTmpRect2;
CoordinateTransforms.scaleRectSize(displayFrame, parent.mInvGlobalScale);
}
}
}
// Set mFrame
Gravity.apply(attrs.gravity, w, h, containingFrame,
(int) (x + attrs.horizontalMargin * pw),
(int) (y + attrs.verticalMargin * ph), windowFrames.mFrame);
// Now make sure the window fits in the overall display frame.
if (fitToDisplay) {
Gravity.applyDisplay(attrs.gravity, displayFrame, windowFrames.mFrame);
}
// We need to make sure we update the CompatFrame as it is used for
// cropping decisions, etc, on systems where we lack a decor layer.
windowFrames.mCompatFrame.set(windowFrames.mFrame);
if (hasCompatScale) {
// See comparable block in computeFrameLw.
windowFrames.mCompatFrame.scale(mInvGlobalScale);
}
}
boolean isChildWindow() {
return mIsChildWindow;
}
boolean layoutInParentFrame() {
return mIsChildWindow
&& (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
}
/**
* Returns true if any window added by an application process that if of type
* {@link android.view.WindowManager.LayoutParams#TYPE_TOAST} or that requires that requires
* {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
* this window is visible.
*/
boolean hideNonSystemOverlayWindowsWhenVisible() {
return (mAttrs.privateFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0
&& mSession.mCanHideNonSystemOverlayWindows;
}
/** Returns the parent window if this is a child of another window, else null. */
WindowState getParentWindow() {
// NOTE: We are not calling getParent() directly as the WindowState might be a child of a
// WindowContainer that isn't a WindowState.
return (mIsChildWindow) ? ((WindowState) super.getParent()) : null;
}
/** Returns the topmost parent window if this is a child of another window, else this. */
WindowState getTopParentWindow() {
WindowState current = this;
WindowState topParent = current;
while (current != null && current.mIsChildWindow) {
current = current.getParentWindow();
// Parent window can be null if the child is detached from it's parent already, but
// someone still has a reference to access it. So, we return the top parent value we
// already have instead of null.
if (current != null) {
topParent = current;
}
}
return topParent;
}
boolean isParentWindowHidden() {
final WindowState parent = getParentWindow();
return parent != null && parent.mHidden;
}
private boolean isParentWindowGoneForLayout() {
final WindowState parent = getParentWindow();
return parent != null && parent.isGoneForLayout();
}
void setWillReplaceWindow(boolean animate) {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
c.setWillReplaceWindow(animate);
}
if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) != 0
|| mAttrs.type == TYPE_APPLICATION_STARTING) {
// We don't set replacing on starting windows since they are added by window manager and
// not the client so won't be replaced by the client.
return;
}
mWillReplaceWindow = true;
mReplacementWindow = null;
mAnimateReplacingWindow = animate;
}
void clearWillReplaceWindow() {
mWillReplaceWindow = false;
mReplacementWindow = null;
mAnimateReplacingWindow = false;
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
c.clearWillReplaceWindow();
}
}
boolean waitingForReplacement() {
if (mWillReplaceWindow) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
if (c.waitingForReplacement()) {
return true;
}
}
return false;
}
void requestUpdateWallpaperIfNeeded() {
final DisplayContent dc = getDisplayContent();
if (dc != null && hasWallpaper()) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
dc.setLayoutNeeded();
mWmService.mWindowPlacerLocked.requestTraversal();
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
c.requestUpdateWallpaperIfNeeded();
}
}
float translateToWindowX(float x) {
float winX = x - mWindowFrames.mFrame.left;
if (hasCompatScale()) {
winX *= mGlobalScale;
}
return winX;
}
float translateToWindowY(float y) {
float winY = y - mWindowFrames.mFrame.top;
if (hasCompatScale()) {
winY *= mGlobalScale;
}
return winY;
}
// During activity relaunch due to resize, we sometimes use window replacement
// for only child windows (as the main window is handled by window preservation)
// and the big surface.
//
// Though windows of TYPE_APPLICATION or TYPE_DRAWN_APPLICATION (as opposed to
// TYPE_BASE_APPLICATION) are not children in the sense of an attached window,
// we also want to replace them at such phases, as they won't be covered by window
// preservation, and in general we expect them to return following relaunch.
boolean shouldBeReplacedWithChildren() {
return mIsChildWindow || mAttrs.type == TYPE_APPLICATION
|| mAttrs.type == TYPE_DRAWN_APPLICATION;
}
void setWillReplaceChildWindows() {
if (shouldBeReplacedWithChildren()) {
setWillReplaceWindow(false /* animate */);
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
c.setWillReplaceChildWindows();
}
}
WindowState getReplacingWindow() {
if (mAnimatingExit && mWillReplaceWindow && mAnimateReplacingWindow) {
return this;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState c = mChildren.get(i);
final WindowState replacing = c.getReplacingWindow();
if (replacing != null) {
return replacing;
}
}
return null;
}
int getRotationAnimationHint() {
if (mActivityRecord != null) {
return mActivityRecord.mRotationAnimationHint;
} else {
return -1;
}
}
// This must be called while inside a transaction.
boolean performShowLocked() {
if (!showToCurrentUser()) {
if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + this + ", belonging to " + mOwnerUid);
clearPolicyVisibilityFlag(VISIBLE_FOR_USER);
return false;
}
logPerformShow("performShow on ");
final int drawState = mWinAnimator.mDrawState;
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
mActivityRecord.onFirstWindowDrawn(this);
} else {
mActivityRecord.onStartingWindowDrawn();
}
}
if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
return false;
}
logPerformShow("Showing ");
mWmService.enableScreenIfNeededLocked();
mWinAnimator.applyEnterAnimationLocked();
// Force the show in the next prepareSurfaceLocked() call.
mWinAnimator.mLastAlpha = -1;
if (DEBUG_ANIM) Slog.v(TAG,
"performShowLocked: mDrawState=HAS_DRAWN in " + this);
mWinAnimator.mDrawState = HAS_DRAWN;
mWmService.scheduleAnimationLocked();
if (mHidden) {
mHidden = false;
final DisplayContent displayContent = getDisplayContent();
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
if (c.mWinAnimator.mSurfaceController != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
// loop, this will cause it to restart with a new layout.
if (displayContent != null) {
displayContent.setLayoutNeeded();
}
}
}
}
return true;
}
private void logPerformShow(String prefix) {
if (DEBUG_VISIBILITY
|| (DEBUG_STARTING_WINDOW_VERBOSE && mAttrs.type == TYPE_APPLICATION_STARTING)) {
Slog.v(TAG, prefix + this
+ ": mDrawState=" + mWinAnimator.drawStateToString()
+ " readyForDisplay=" + isReadyForDisplay()
+ " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING)
+ " during animation: policyVis=" + isVisibleByPolicy()
+ " parentHidden=" + isParentWindowHidden()
+ " tok.visibleRequested="
+ (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
+ " animating=" + isAnimating(TRANSITION | PARENTS)
+ " tok animating="
+ (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION | PARENTS))
+ " Callers=" + Debug.getCallers(4));
}
}
WindowInfo getWindowInfo() {
WindowInfo windowInfo = WindowInfo.obtain();
windowInfo.displayId = getDisplayId();
windowInfo.type = mAttrs.type;
windowInfo.layer = mLayer;
windowInfo.token = mClient.asBinder();
if (mActivityRecord != null) {
windowInfo.activityToken = mActivityRecord.appToken.asBinder();
}
windowInfo.title = mAttrs.accessibilityTitle;
// Panel windows have no public way to set the a11y title directly. Use the
// regular title as a fallback.
final boolean isPanelWindow = (mAttrs.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW)
&& (mAttrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW);
// Accessibility overlays should have titles that work for accessibility, and can't set
// the a11y title themselves.
final boolean isAccessibilityOverlay =
windowInfo.type == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
if (TextUtils.isEmpty(windowInfo.title) && (isPanelWindow || isAccessibilityOverlay)) {
final CharSequence title = mAttrs.getTitle();
windowInfo.title = TextUtils.isEmpty(title) ? null : title;
}
windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
windowInfo.focused = isFocused();
Task task = getTask();
windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode();
windowInfo.taskId = task == null ? ActivityTaskManager.INVALID_TASK_ID : task.mTaskId;
windowInfo.hasFlagWatchOutsideTouch =
(mAttrs.flags & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0;
if (mIsChildWindow) {
windowInfo.parentToken = getParentWindow().mClient.asBinder();
}
final int childCount = mChildren.size();
if (childCount > 0) {
if (windowInfo.childTokens == null) {
windowInfo.childTokens = new ArrayList(childCount);
}
for (int j = 0; j < childCount; j++) {
final WindowState child = mChildren.get(j);
windowInfo.childTokens.add(child.mClient.asBinder());
}
}
return windowInfo;
}
@Override
boolean forAllWindows(ToBooleanFunction callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
return applyInOrderWithImeWindows(callback, traverseTopToBottom);
}
if (traverseTopToBottom) {
return forAllWindowTopToBottom(callback);
} else {
return forAllWindowBottomToTop(callback);
}
}
private boolean forAllWindowBottomToTop(ToBooleanFunction callback) {
// We want to consume the negative sublayer children first because they need to appear
// below the parent, then this window (the parent), and then the positive sublayer children
// because they need to appear above the parent.
int i = 0;
final int count = mChildren.size();
WindowState child = mChildren.get(i);
while (i < count && child.mSubLayer < 0) {
if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
if (i >= count) {
break;
}
child = mChildren.get(i);
}
if (applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
while (i < count) {
if (child.applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
i++;
if (i >= count) {
break;
}
child = mChildren.get(i);
}
return false;
}
private boolean forAllWindowTopToBottom(ToBooleanFunction callback) {
// We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
// because they need to appear above the parent.
int i = mChildren.size() - 1;
WindowState child = mChildren.get(i);
while (i >= 0 && child.mSubLayer >= 0) {
if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
if (applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
while (i >= 0) {
if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
return false;
}
private boolean applyImeWindowsIfNeeded(ToBooleanFunction callback,
boolean traverseTopToBottom) {
// No need to apply to IME window if the window is not the current IME layering target.
if (!isImeLayeringTarget()) {
return false;
}
// If we are in split screen which case we process the IME at the DisplayContent level to
// ensure it is above the docked divider.
// i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
// window will be ignored to traverse when the IME target is still in split-screen mode.
if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
&& getTask() != null) {
return false;
}
// Note that we don't process IME window if the IME input target is not on the screen.
// In case some unexpected IME visibility cases happen like starting the remote
// animation on the keyguard but seeing the IME window that originally on the app
// which behinds the keyguard.
final WindowState imeInputTarget = getImeInputTarget();
if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) {
return false;
}
return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
}
private boolean applyInOrderWithImeWindows(ToBooleanFunction callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
|| callback.apply(this)) {
return true;
}
} else {
if (callback.apply(this)
|| applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
return true;
}
}
return false;
}
WindowState getWindow(Predicate callback) {
if (mChildren.isEmpty()) {
return callback.test(this) ? this : null;
}
// We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
// because they need to appear above the parent.
int i = mChildren.size() - 1;
WindowState child = mChildren.get(i);
while (i >= 0 && child.mSubLayer >= 0) {
if (callback.test(child)) {
return child;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
if (callback.test(this)) {
return this;
}
while (i >= 0) {
if (callback.test(child)) {
return child;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
return null;
}
/**
* @return True if we our one of our ancestors has {@link #mAnimatingExit} set to true, false
* otherwise.
*/
@VisibleForTesting
boolean isSelfOrAncestorWindowAnimatingExit() {
WindowState window = this;
do {
if (window.mAnimatingExit) {
return true;
}
window = window.getParentWindow();
} while (window != null);
return false;
}
private boolean shouldFinishAnimatingExit() {
// Exit animation might be applied soon.
if (inTransition()) {
ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s",
this);
return false;
}
if (!mDisplayContent.okToAnimate()) {
return true;
}
// Exit animation is running.
if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) {
ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s",
this);
return false;
}
// If the wallpaper is currently behind this app window, we need to change both of
// them inside of a transaction to avoid artifacts.
if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) {
ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
"shouldWaitAnimatingExit: isWallpaperTarget: %s", this);
return false;
}
return true;
}
/**
* If this is window is stuck in the animatingExit status, resume clean up procedure blocked
* by the exit animation.
*/
void cleanupAnimatingExitWindow() {
// TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation
// and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall
// be called, but there seems to be a case that #onExitAnimationDone is not triggered, so
// a windows stuck in the animatingExit status.
if (mAnimatingExit && shouldFinishAnimatingExit()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s",
this);
onExitAnimationDone();
}
}
void onExitAnimationDone() {
if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
+ ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
+ " selfAnimating=" + isAnimating());
if (!mChildren.isEmpty()) {
// Copying to a different list as multiple children can be removed.
final ArrayList childWindows = new ArrayList<>(mChildren);
for (int i = childWindows.size() - 1; i >= 0; i--) {
childWindows.get(i).onExitAnimationDone();
}
}
if (mWinAnimator.mEnteringAnimation) {
mWinAnimator.mEnteringAnimation = false;
mWmService.requestTraversal();
// System windows don't have an activity and an app token as a result, but need a way
// to be informed about their entrance animation end.
if (mActivityRecord == null) {
try {
mClient.dispatchWindowShown();
} catch (RemoteException e) {
}
}
}
if (isAnimating()) {
return;
}
if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
}
if (!isSelfOrAncestorWindowAnimatingExit()) {
return;
}
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Exit animation finished in %s: remove=%b",
this, mRemoveOnExit);
mDestroying = true;
final boolean hasSurface = mWinAnimator.hasSurface();
// Use pendingTransaction here so hide is done the same transaction as the other
// animations when exiting
mWinAnimator.hide(getPendingTransaction(), "onExitAnimationDone");
// If we have an app token, we ask it to destroy the surface for us, so that it can take
// care to ensure the activity has actually stopped and the surface is not still in use.
// Otherwise we add the service to mDestroySurface and allow it to be processed in our next
// transaction.
if (mActivityRecord != null) {
mActivityRecord.destroySurfaces();
} else {
if (hasSurface) {
mWmService.mDestroySurface.add(this);
}
if (mRemoveOnExit) {
mWmService.mPendingRemove.add(this);
mRemoveOnExit = false;
}
}
mAnimatingExit = false;
getDisplayContent().mWallpaperController.hideWallpapers(this);
}
boolean clearAnimatingFlags() {
boolean didSomething = false;
// We don't want to clear it out for windows that get replaced, because the
// animation depends on the flag to remove the replaced window.
//
// We also don't clear the mAnimatingExit flag for windows which have the
// mRemoveOnExit flag. This indicates an explicit remove request has been issued
// by the client. We should let animation proceed and not clear this flag or
// they won't eventually be removed by WindowStateAnimator#finishExit.
if (!mWillReplaceWindow && !mRemoveOnExit) {
// Clear mAnimating flag together with mAnimatingExit. When animation
// changes from exiting to entering, we need to clear this flag until the
// new animation gets applied, so that isAnimationStarting() becomes true
// until then.
// Otherwise applySurfaceChangesTransaction will fail to skip surface
// placement for this window during this period, one or more frame will
// show up with wrong position or scale.
if (mAnimatingExit) {
mAnimatingExit = false;
didSomething = true;
}
if (mDestroying) {
mDestroying = false;
mWmService.mDestroySurface.remove(this);
didSomething = true;
}
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
didSomething |= (mChildren.get(i)).clearAnimatingFlags();
}
return didSomething;
}
public boolean isRtl() {
return getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
void updateReportedVisibility(UpdateReportedVisibilityResults results) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
c.updateReportedVisibility(results);
}
if (mAppFreezing || mViewVisibility != View.VISIBLE
|| mAttrs.type == TYPE_APPLICATION_STARTING
|| mDestroying) {
return;
}
if (DEBUG_VISIBILITY) {
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
+ " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ " a=" + isAnimating(TRANSITION | PARENTS));
}
}
results.numInteresting++;
if (isDrawn()) {
results.numDrawn++;
if (!isAnimating(TRANSITION | PARENTS)) {
results.numVisible++;
}
results.nowGone = false;
} else if (isAnimating(TRANSITION | PARENTS)) {
results.nowGone = false;
}
}
/**
* Expand the given rectangle by this windows surface insets. This
* takes you from the 'window size' to the 'surface size'.
* The surface insets are positive in each direction, so we inset by
* the inverse.
*/
void expandForSurfaceInsets(Rect r) {
r.inset(-mAttrs.surfaceInsets.left,
-mAttrs.surfaceInsets.top,
-mAttrs.surfaceInsets.right,
-mAttrs.surfaceInsets.bottom);
}
boolean surfaceInsetsChanging() {
return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets);
}
int relayoutVisibleWindow(int result) {
final boolean wasVisible = isVisible();
result |= (!wasVisible || !isDrawn()) ? RELAYOUT_RES_FIRST_TIME : 0;
if (mAnimatingExit) {
Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit="
+ mRemoveOnExit + ", mDestroying=" + mDestroying);
// Cancel the existing exit animation for the next enter animation.
if (isAnimating()) {
cancelAnimation();
}
mAnimatingExit = false;
}
if (mDestroying) {
mDestroying = false;
mWmService.mDestroySurface.remove(this);
}
if (!wasVisible) {
mWinAnimator.mEnterAnimationPending = true;
}
mLastVisibleLayoutRotation = getDisplayContent().getRotation();
mWinAnimator.mEnteringAnimation = true;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareToDisplay");
try {
prepareWindowToDisplayDuringRelayout(wasVisible);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (isDragResizeChanged()) {
setDragResizing();
}
final boolean freeformResizing = isDragResizing()
&& getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
final boolean dockedResizing = isDragResizing()
&& getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
result |= freeformResizing ? RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
result |= dockedResizing ? RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
return result;
}
/**
* @return True if this window has been laid out at least once; false otherwise.
*/
boolean isLaidOut() {
return mLayoutSeq != -1;
}
/**
* Add the DisplayContent of the embedded display which is re-parented to this window to
* the list of embedded displays.
*
* @param dc DisplayContent of the re-parented embedded display.
* @return {@code true} if the giving DisplayContent is added, {@code false} otherwise.
*/
boolean addEmbeddedDisplayContent(DisplayContent dc) {
return mEmbeddedDisplayContents.add(dc);
}
/**
* Remove the DisplayContent of the embedded display which is re-parented to this window from
* the list of embedded displays.
*
* @param dc DisplayContent of the re-parented embedded display.
* @return {@code true} if the giving DisplayContent is removed, {@code false} otherwise.
*/
boolean removeEmbeddedDisplayContent(DisplayContent dc) {
return mEmbeddedDisplayContents.remove(dc);
}
/** Updates the last frames and relative frames to the current ones. */
void updateLastFrames() {
mWindowFrames.mLastFrame.set(mWindowFrames.mFrame);
mWindowFrames.mLastRelFrame.set(mWindowFrames.mRelFrame);
}
/**
* Clears factors that would cause report-resize.
*/
void onResizeHandled() {
mWindowFrames.onResizeHandled();
}
@Override
protected boolean isSelfAnimating(int flags, int typesToCheck) {
if (mControllableInsetProvider != null) {
return false;
}
return super.isSelfAnimating(flags, typesToCheck);
}
void startAnimation(Animation anim) {
// If we are an inset provider, all our animations are driven by the inset client.
if (mControllableInsetProvider != null) {
return;
}
final DisplayInfo displayInfo = getDisplayInfo();
anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
displayInfo.appWidth, displayInfo.appHeight);
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
final AnimationAdapter adapter = new LocalAnimationAdapter(
new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
0 /* windowCornerRadius */),
mWmService.mSurfaceAnimationRunner);
startAnimation(getPendingTransaction(), adapter);
commitPendingTransaction();
}
private void startMoveAnimation(int left, int top) {
// If we are an inset provider, all our animations are driven by the inset client.
if (mControllableInsetProvider != null) {
return;
}
if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
final Point oldPosition = new Point();
final Point newPosition = new Point();
transformFrameToSurfacePosition(mWindowFrames.mLastFrame.left, mWindowFrames.mLastFrame.top,
oldPosition);
transformFrameToSurfacePosition(left, top, newPosition);
final AnimationAdapter adapter = new LocalAnimationAdapter(
new MoveAnimationSpec(oldPosition.x, oldPosition.y, newPosition.x, newPosition.y),
mWmService.mSurfaceAnimationRunner);
startAnimation(getPendingTransaction(), adapter);
}
private void startAnimation(Transaction t, AnimationAdapter adapter) {
startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION);
}
@Override
protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
super.onAnimationFinished(type, anim);
mWinAnimator.onAnimationFinished();
}
/**
* Retrieves the current transformation matrix of the window, relative to the display.
*
* @param float9 A temporary array of 9 floats.
* @param outMatrix Matrix to fill in the transformation.
*/
void getTransformationMatrix(float[] float9, Matrix outMatrix) {
float9[Matrix.MSCALE_X] = mGlobalScale;
float9[Matrix.MSKEW_Y] = 0;
float9[Matrix.MSKEW_X] = 0;
float9[Matrix.MSCALE_Y] = mGlobalScale;
transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
int x = mSurfacePosition.x + mTmpPoint.x;
int y = mSurfacePosition.y + mTmpPoint.y;
// We might be on a display which has been re-parented to a view in another window, so here
// computes the global location of our display.
DisplayContent dc = getDisplayContent();
while (dc != null && dc.getParentWindow() != null) {
final WindowState displayParent = dc.getParentWindow();
x += displayParent.mWindowFrames.mFrame.left
+ (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
y += displayParent.mWindowFrames.mFrame.top
+ (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
dc = displayParent.getDisplayContent();
}
// If changed, also adjust transformFrameToSurfacePosition
final WindowContainer parent = getParent();
if (isChildWindow()) {
final WindowState parentWindow = getParentWindow();
x += parentWindow.mWindowFrames.mFrame.left - parentWindow.mAttrs.surfaceInsets.left;
y += parentWindow.mWindowFrames.mFrame.top - parentWindow.mAttrs.surfaceInsets.top;
} else if (parent != null) {
final Rect parentBounds = parent.getBounds();
x += parentBounds.left;
y += parentBounds.top;
}
float9[Matrix.MTRANS_X] = x;
float9[Matrix.MTRANS_Y] = y;
float9[Matrix.MPERSP_0] = 0;
float9[Matrix.MPERSP_1] = 0;
float9[Matrix.MPERSP_2] = 1;
outMatrix.setValues(float9);
}
// TODO: Hack to work around the number of states ActivityRecord needs to access without having
// access to its windows children. Need to investigate re-writing
// {@link ActivityRecord#updateReportedVisibilityLocked} so this can be removed.
static final class UpdateReportedVisibilityResults {
int numInteresting;
int numVisible;
int numDrawn;
boolean nowGone = true;
void reset() {
numInteresting = 0;
numVisible = 0;
numDrawn = 0;
nowGone = true;
}
}
private static final class WindowId extends IWindowId.Stub {
private final WeakReference mOuter;
private WindowId(WindowState outer) {
// Use a weak reference for the outer class. This is important to prevent the following
// leak: Since we send this class to the client process, binder will keep it alive as
// long as the client keeps it alive. Now, if the window is removed, we need to clear
// out our reference so even though this class is kept alive we don't leak WindowState,
// which can keep a whole lot of classes alive.
mOuter = new WeakReference<>(outer);
}
@Override
public void registerFocusObserver(IWindowFocusObserver observer) {
final WindowState outer = mOuter.get();
if (outer != null) {
outer.registerFocusObserver(observer);
}
}
@Override
public void unregisterFocusObserver(IWindowFocusObserver observer) {
final WindowState outer = mOuter.get();
if (outer != null) {
outer.unregisterFocusObserver(observer);
}
}
@Override
public boolean isFocused() {
final WindowState outer = mOuter.get();
if (outer != null) {
synchronized (outer.mWmService.mGlobalLock) {
return outer.isFocused();
}
}
return false;
}
}
@Override
boolean shouldMagnify() {
if (mAttrs.type == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY
|| mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG
|| mAttrs.type == TYPE_MAGNIFICATION_OVERLAY
|| mAttrs.type == TYPE_NAVIGATION_BAR
// It's tempting to wonder: Have we forgotten the rounded corners overlay?
// worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
|| mAttrs.type == TYPE_NAVIGATION_BAR_PANEL) {
return false;
}
if ((mAttrs.privateFlags & PRIVATE_FLAG_NOT_MAGNIFIABLE) != 0) {
return false;
}
return true;
}
@Override
SurfaceSession getSession() {
if (mSession.mSurfaceSession != null) {
return mSession.mSurfaceSession;
} else {
return getParent().getSession();
}
}
@Override
boolean needsZBoost() {
final InsetsControlTarget target = getDisplayContent().getImeTarget(IME_TARGET_LAYERING);
if (mIsImWindow && target != null) {
final ActivityRecord activity = target.getWindow().mActivityRecord;
if (activity != null) {
return activity.needsZBoost();
}
}
return mWillReplaceWindow;
}
private boolean isStartingWindowAssociatedToTask() {
return mStartingData != null && mStartingData.mAssociatedTask != null;
}
private void applyDims() {
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
} else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || shouldDrawBlurBehind())
&& isVisibleNow() && !mHidden) {
// Only show the Dimmer when the following is satisfied:
// 1. The window has the flag FLAG_DIM_BEHIND or blur behind is requested
// 2. The WindowToken is not hidden so dims aren't shown when the window is exiting.
// 3. The WS is considered visible according to the isVisible() method
// 4. The WS is not hidden.
mIsDimming = true;
final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
final int blurRadius = shouldDrawBlurBehind() ? mAttrs.getBlurBehindRadius() : 0;
getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius);
}
}
private boolean shouldDrawBlurBehind() {
return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0
&& mWmService.mBlurController.getBlurEnabled();
}
/**
* Notifies SF about the priority of the window, if it changed. SF then uses this information
* to decide which window's desired rendering rate should have a priority when deciding about
* the refresh rate of the screen. Priority
* {@link RefreshRatePolicy#LAYER_PRIORITY_FOCUSED_WITH_MODE} is considered the highest.
*/
@VisibleForTesting
void updateFrameRateSelectionPriorityIfNeeded() {
RefreshRatePolicy refreshRatePolicy =
getDisplayContent().getDisplayPolicy().getRefreshRatePolicy();
final int priority = refreshRatePolicy.calculatePriority(this);
if (mFrameRateSelectionPriority != priority) {
mFrameRateSelectionPriority = priority;
getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
mFrameRateSelectionPriority);
}
// If refresh rate switching is disabled there is no point to set the frame rate on the
// surface as the refresh rate will be limited by display manager to a single value
// and SurfaceFlinger wouldn't be able to change it anyways.
if (mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType()
!= SWITCHING_TYPE_NONE) {
final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
if (mAppPreferredFrameRate != refreshRate) {
mAppPreferredFrameRate = refreshRate;
getPendingTransaction().setFrameRate(
mSurfaceControl, mAppPreferredFrameRate,
Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
}
}
}
private void updateScaleIfNeeded() {
float newHScale = mHScale * mGlobalScale * mWallpaperScale;
float newVScale = mVScale * mGlobalScale * mWallpaperScale;
if (mLastHScale != newHScale ||
mLastVScale != newVScale ) {
getPendingTransaction().setMatrix(getSurfaceControl(),
newHScale, 0, 0, newVScale);
mLastGlobalScale = mGlobalScale;
mLastHScale = newHScale;
mLastVScale = newVScale;
}
}
@Override
void prepareSurfaces() {
mIsDimming = false;
applyDims();
updateSurfacePositionNonOrganized();
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
if (isVisibleRequested()) updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
super.prepareSurfaces();
}
@Override
@VisibleForTesting
void updateSurfacePosition(Transaction t) {
if (mSurfaceControl == null) {
return;
}
if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
&& !mSurfacePlacementNeeded) {
// Since this relies on mWindowFrames, changes made while layout is deferred are
// likely to be invalid. Similarly, if it's goneForLayout, mWindowFrames may not be
// up-to-date and thus can't be relied on.
return;
}
mSurfacePlacementNeeded = false;
transformFrameToSurfacePosition(mWindowFrames.mFrame.left, mWindowFrames.mFrame.top,
mSurfacePosition);
if (mWallpaperScale != 1f) {
DisplayInfo displayInfo = getDisplayInfo();
Matrix matrix = mTmpMatrix;
matrix.setTranslate(mXOffset, mYOffset);
matrix.postScale(mWallpaperScale, mWallpaperScale, displayInfo.logicalWidth / 2f,
displayInfo.logicalHeight / 2f);
matrix.getValues(mTmpMatrixArray);
mSurfacePosition.offset(Math.round(mTmpMatrixArray[Matrix.MTRANS_X]),
Math.round(mTmpMatrixArray[Matrix.MTRANS_Y]));
} else {
mSurfacePosition.offset(mXOffset, mYOffset);
}
// Freeze position while we're unrotated, so the surface remains at the position it was
// prior to the rotation.
if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
&& !mLastSurfacePosition.equals(mSurfacePosition)) {
final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported();
final boolean surfaceInsetsChanged = surfaceInsetsChanging();
final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged;
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
if (surfaceInsetsChanged) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
}
if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation()
&& okToDisplay()) {
applyWithNextDraw(mSetSurfacePositionConsumer);
} else {
mSetSurfacePositionConsumer.accept(t);
}
}
}
void transformFrameToSurfacePosition(int left, int top, Point outPoint) {
outPoint.set(left, top);
// If changed, also adjust getTransformationMatrix
final WindowContainer parentWindowContainer = getParent();
if (isChildWindow()) {
// TODO: This probably falls apart at some point and we should
// actually compute relative coordinates.
// Since the parent was outset by its surface insets, we need to undo the outsetting
// with insetting by the same amount.
final WindowState parent = getParentWindow();
transformSurfaceInsetsPosition(mTmpPoint, parent.mAttrs.surfaceInsets);
outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
-parent.mWindowFrames.mFrame.top + mTmpPoint.y);
} else if (parentWindowContainer != null) {
final Rect parentBounds = isStartingWindowAssociatedToTask()
? mStartingData.mAssociatedTask.getBounds()
: parentWindowContainer.getBounds();
outPoint.offset(-parentBounds.left, -parentBounds.top);
}
Task rootTask = getRootTask();
// If we have root task outsets, that means the top-left
// will be outset, and we need to inset ourselves
// to account for it. If we actually have shadows we will
// then un-inset ourselves by the surfaceInsets.
if (rootTask != null) {
final int outset = rootTask.getTaskOutset();
outPoint.offset(outset, outset);
}
// Expand for surface insets. See WindowState.expandForSurfaceInsets.
transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
outPoint.offset(-mTmpPoint.x, -mTmpPoint.y);
outPoint.y += mSurfaceTranslationY;
}
/**
* The surface insets from layout parameter are in application coordinate. If the window is
* scaled, the insets also need to be scaled for surface position in global coordinate.
*/
private void transformSurfaceInsetsPosition(Point outPos, Rect surfaceInsets) {
if (!hasCompatScale()) {
outPos.x = surfaceInsets.left;
outPos.y = surfaceInsets.top;
return;
}
outPos.x = (int) (surfaceInsets.left * mGlobalScale + 0.5f);
outPos.y = (int) (surfaceInsets.top * mGlobalScale + 0.5f);
}
boolean needsRelativeLayeringToIme() {
// We use the relative layering when IME isn't attached to the app. Such as part of
// elevating the IME and windows above it's target above the docked divider in
// split-screen, or make the popupMenu to be above the IME when the parent window is the
// IME layering target in bubble/freeform mode.
if (mDisplayContent.shouldImeAttachedToApp()) {
return false;
}
if (isChildWindow()) {
// If we are a child of the input method target we need this promotion.
if (getParentWindow().isImeLayeringTarget()) {
return true;
}
} else if (mActivityRecord != null) {
// Likewise if we share a token with the Input method target and are ordered
// above it but not necessarily a child (e.g. a Dialog) then we also need
// this promotion.
final WindowState imeTarget = getImeLayeringTarget();
boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this
&& imeTarget.mToken == mToken
&& mAttrs.type != TYPE_APPLICATION_STARTING
&& getParent() != null
&& imeTarget.compareTo(this) <= 0;
return inTokenWithAndAboveImeTarget;
}
return false;
}
/**
* Get IME target that should host IME when this window's display has a parent.
* Note: IME is never hosted by a display that has a parent.
* When window calling
* {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} is unknown,
* use {@link DisplayContent#getImeControlTarget()} instead.
*
* @return {@link InsetsControlTarget} of host that controls the IME.
* When window is doesn't have a parent, it is returned as-is.
*/
InsetsControlTarget getImeControlTarget() {
final DisplayContent dc = getDisplayContent();
final WindowState parentWindow = dc.getParentWindow();
// If target's display has a parent, IME is displayed in the parent display.
return dc.getImeHostOrFallback(parentWindow != null ? parentWindow : this);
}
@Override
void assignLayer(Transaction t, int layer) {
if (isStartingWindowAssociatedToTask()) {
// The starting window should cover the task.
t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
return;
}
// See comment in assignRelativeLayerForImeTargetChild
if (needsRelativeLayeringToIme()) {
getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
return;
}
super.assignLayer(t, layer);
}
boolean isDimming() {
return mIsDimming;
}
@Override
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
if (isStartingWindowAssociatedToTask()) {
// Its surface is already put in task. Don't reparent when transferring starting window
// across activities.
return;
}
super.reparentSurfaceControl(t, newParent);
}
@Override
public SurfaceControl getAnimationLeashParent() {
if (isStartingWindowAssociatedToTask()) {
return mStartingData.mAssociatedTask.mSurfaceControl;
}
return super.getAnimationLeashParent();
}
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side and simply inherit
// the default implementation here.
public void assignChildLayers(Transaction t) {
// The surface of the main window might be preserved. So the child window on top of the main
// window should be also on top of the preserved surface.
int layer = PRESERVED_SURFACE_LAYER + 1;
for (int i = 0; i < mChildren.size(); i++) {
final WindowState w = mChildren.get(i);
// APPLICATION_MEDIA_OVERLAY needs to go above APPLICATION_MEDIA
// while they both need to go below the main window. However the
// relative layering of multiple APPLICATION_MEDIA/OVERLAY has never
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
} else {
w.assignLayer(t, layer);
}
w.assignChildLayers(t);
layer++;
}
}
/**
* Update a tap exclude region identified by provided id. The requested area will be clipped to
* the window bounds.
*/
void updateTapExcludeRegion(Region region) {
final DisplayContent currentDisplay = getDisplayContent();
if (currentDisplay == null) {
throw new IllegalStateException("Trying to update window not attached to any display.");
}
// Clear the tap excluded region if the region passed in is null or empty.
if (region == null || region.isEmpty()) {
mTapExcludeRegion.setEmpty();
// Remove this window from mTapExcludeProvidingWindows since it won't be providing
// tap exclude regions.
currentDisplay.mTapExcludeProvidingWindows.remove(this);
} else {
mTapExcludeRegion.set(region);
// Make sure that this window is registered as one that provides a tap exclude region
// for its containing display.
currentDisplay.mTapExcludeProvidingWindows.add(this);
}
// Trigger touch exclude region update on current display.
currentDisplay.updateTouchExcludeRegion();
// Trigger touchable region update for this window.
currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
}
/**
* Get the tap excluded region for this window in screen coordinates.
*
* @param outRegion The returned tap excluded region. It is on the screen coordinates.
*/
void getTapExcludeRegion(Region outRegion) {
mTmpRect.set(mWindowFrames.mFrame);
mTmpRect.offsetTo(0, 0);
outRegion.set(mTapExcludeRegion);
outRegion.op(mTmpRect, Region.Op.INTERSECT);
// The region is on the window coordinates, so it needs to be translated into screen
// coordinates. There's no need to scale since that will be done by native code.
outRegion.translate(mWindowFrames.mFrame.left, mWindowFrames.mFrame.top);
}
boolean hasTapExcludeRegion() {
return !mTapExcludeRegion.isEmpty();
}
boolean isImeLayeringTarget() {
return getDisplayContent().getImeTarget(IME_TARGET_LAYERING) == this;
}
WindowState getImeLayeringTarget() {
final InsetsControlTarget target = getDisplayContent().getImeTarget(IME_TARGET_LAYERING);
return target != null ? target.getWindow() : null;
}
WindowState getImeInputTarget() {
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_INPUT);
return target != null ? target.getWindow() : null;
}
long getFrameNumber() {
// Return the frame number in which changes requested in this layout will be rendered or
// -1 if we do not expect the frame to be rendered.
return getFrame().isEmpty() ? -1 : mFrameNumber;
}
void setFrameNumber(long frameNumber) {
mFrameNumber = frameNumber;
}
void forceReportingResized() {
mWindowFrames.forceReportingResized();
}
/** Returns the {@link WindowFrames} associated with this {@link WindowState}. */
WindowFrames getWindowFrames() {
return mWindowFrames;
}
/**
* If the simulated frame is set, the computed result won't be used in real layout. So this
* frames must be cleared when the simulated computation is done.
*/
void setSimulatedWindowFrames(WindowFrames windowFrames) {
mSimulatedWindowFrames = windowFrames;
}
/**
* Use this method only when the simulated frames may be set, so it is clearer that the calling
* path may be used to simulate layout.
*/
WindowFrames getLayoutingWindowFrames() {
return mSimulatedWindowFrames != null ? mSimulatedWindowFrames : mWindowFrames;
}
void resetContentChanged() {
mWindowFrames.setContentChanged(false);
}
/**
* Set's an {@link InsetsSourceProvider} to be associated with this window, but only if the
* provider itself is controllable, as one window can be the provider of more than one inset
* type (i.e. gesture insets). If this window is controllable, all its animations must be
* controlled by its control target, and the visibility of this window should be taken account
* into the state of the control target.
*
* @param insetProvider the provider which should not be visible to the client.
* @see InsetsStateController#getInsetsForWindow(WindowState)
*/
void setControllableInsetProvider(InsetsSourceProvider insetProvider) {
mControllableInsetProvider = insetProvider;
}
InsetsSourceProvider getControllableInsetProvider() {
return mControllableInsetProvider;
}
private final class MoveAnimationSpec implements AnimationSpec {
private final long mDuration;
private Interpolator mInterpolator;
private Point mFrom = new Point();
private Point mTo = new Point();
private MoveAnimationSpec(int fromX, int fromY, int toX, int toY) {
final Animation anim = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.window_move_from_decor);
mDuration = (long)
(anim.computeDurationHint() * mWmService.getWindowAnimationScaleLocked());
mInterpolator = anim.getInterpolator();
mFrom.set(fromX, fromY);
mTo.set(toX, toY);
}
@Override
public long getDuration() {
return mDuration;
}
@Override
public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
final float fraction = getFraction(currentPlayTime);
final float v = mInterpolator.getInterpolation(fraction);
t.setPosition(leash, mFrom.x + (mTo.x - mFrom.x) * v,
mFrom.y + (mTo.y - mFrom.y) * v);
}
@Override
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "from=" + mFrom
+ " to=" + mTo
+ " duration=" + mDuration);
}
@Override
public void dumpDebugInner(ProtoOutputStream proto) {
final long token = proto.start(MOVE);
dumpPointProto(mFrom, proto, FROM);
dumpPointProto(mTo, proto, TO);
proto.write(DURATION_MS, mDuration);
proto.end(token);
}
}
KeyInterceptionInfo getKeyInterceptionInfo() {
if (mKeyInterceptionInfo == null
|| mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
|| mKeyInterceptionInfo.layoutParamsType != getAttrs().type
|| mKeyInterceptionInfo.windowTitle != getWindowTag()) {
mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
getWindowTag().toString());
}
return mKeyInterceptionInfo;
}
@Override
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
// Containing frame will usually cover the whole screen, including dialog windows.
// For freeform workspace windows it will not cover the whole screen and it also
// won't exactly match the final freeform window frame (e.g. when overlapping with
// the status bar). In that case we need to use the final frame.
if (inFreeformWindowingMode()) {
outFrame.set(getFrame());
} else if (areAppWindowBoundsLetterboxed() || mToken.isFixedRotationTransforming()) {
// 1. The letterbox surfaces should be animated with the owner activity, so use task
// bounds to include them.
// 2. If the activity has fixed rotation transform, its windows are rotated in activity
// level. Because the animation runs before display is rotated, task bounds should
// represent the frames in display space coordinates.
outFrame.set(getTask().getBounds());
} else if (isDockedResizing()) {
// If we are animating while docked resizing, then use the root task bounds as the
// animation target (which will be different than the task bounds)
outFrame.set(getTask().getParent().getBounds());
} else {
outFrame.set(getContainingFrame());
}
outSurfaceInsets.set(getAttrs().surfaceInsets);
final InsetsState state = getInsetsStateWithVisibilityOverride();
outInsets.set(state.calculateInsets(outFrame, systemBars(),
false /* ignoreVisibility */).toRect());
outStableInsets.set(state.calculateInsets(outFrame, systemBars(),
true /* ignoreVisibility */).toRect());
}
void setViewVisibility(int viewVisibility) {
mViewVisibility = viewVisibility;
}
SurfaceControl getClientViewRootSurface() {
return mWinAnimator.getSurfaceControl();
}
@Override
boolean prepareSync() {
if (!super.prepareSync()) {
return false;
}
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
// perspective but at the WindowState level we need to wait for ourselves
// to draw even if the children draw first or don't need to sync, so we start
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
requestRedrawForSync();
return true;
}
@Override
boolean isSyncFinished() {
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE
&& !isVisibleRequested()) {
// Don't wait for GONE windows. However, we don't alter the state in case the window
// becomes un-gone while the syncset is still active.
return true;
}
return super.isSyncFinished();
}
@Override
void finishSync(Transaction outMergedTransaction, boolean cancel) {
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
mClientWasDrawingForSync = true;
}
super.finishSync(outMergedTransaction, cancel);
}
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
if (mOrientationChangeRedrawRequestTime > 0) {
final long duration =
SystemClock.elapsedRealtime() - mOrientationChangeRedrawRequestTime;
Slog.i(TAG, "finishDrawing of orientation change: " + this + " " + duration + "ms");
mOrientationChangeRedrawRequestTime = 0;
} else if (mActivityRecord != null && mActivityRecord.mRelaunchStartTime != 0
&& mActivityRecord.findMainWindow() == this) {
final long duration =
SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
mActivityRecord.mRelaunchStartTime = 0;
}
executeDrawHandlers(postDrawTransaction);
final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null;
mClientWasDrawingForSync = false;
if (!onSyncFinishedDrawing()) {
return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow);
}
if (mActivityRecord != null
&& mTransitionController.isShellTransitionsEnabled()
&& mAttrs.type == TYPE_APPLICATION_STARTING) {
mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
.notifyStartingWindowDrawn(mActivityRecord);
}
if (postDrawTransaction != null) {
mSyncTransaction.merge(postDrawTransaction);
}
mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */);
// We always want to force a traversal after a finish draw for blast sync.
return true;
}
void immediatelyNotifyBlastSync() {
prepareDrawHandlers();
finishDrawing(null);
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
if (!useBLASTSync()) return;
final Task task = getTask();
if (task != null) {
final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction();
if (t != null) {
mSyncTransaction.merge(t);
}
task.setMainWindowSizeChangeTransaction(null);
}
}
@Override
boolean fillsParent() {
return mAttrs.type == TYPE_APPLICATION_STARTING;
}
@Override
boolean showWallpaper() {
if (!isVisibleRequested()
// in multi-window mode, wallpaper is always visible at the back and not tied to
// the app (there is no wallpaper target).
|| inMultiWindowMode()) {
return false;
}
return hasWallpaper();
}
boolean hasWallpaper() {
return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground();
}
boolean hasWallpaperForLetterboxBackground() {
return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox();
}
/**
* When using the two WindowOrganizer sync-primitives (BoundsChangeTransaction, BLASTSync)
* it can be a little difficult to predict whether your change will actually trigger redrawing
* on the client side. To ease the burden on shell developers, we force send MSG_RESIZED
* for Windows involved in these Syncs
*/
private boolean shouldSendRedrawForSync() {
if (mRedrawForSyncReported) {
return false;
}
final Task task = getTask();
if (task != null && task.getMainWindowSizeChangeTransaction() != null) {
return true;
}
return useBLASTSync();
}
void requestRedrawForSync() {
mRedrawForSyncReported = false;
}
void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) {
outSize.setEmpty();
if ((attrs.flags & FLAG_SCALED) != 0) {
// For a scaled surface, we always want the requested size.
outSize.right = mRequestedWidth;
outSize.bottom = mRequestedHeight;
} else {
// When we're doing a drag-resizing, request a surface that's fullscreen size,
// so that we don't need to reallocate during the process. This also prevents
// buffer drops due to size mismatch.
if (isDragResizing()) {
final DisplayInfo displayInfo = getDisplayInfo();
outSize.right = displayInfo.logicalWidth;
outSize.bottom = displayInfo.logicalHeight;
} else {
getCompatFrameSize(outSize);
}
}
// This doesn't necessarily mean that there is an error in the system. The sizes might be
// incorrect, because it is before the first layout or draw.
if (outSize.width() < 1) {
outSize.right = 1;
}
if (outSize.height() < 1) {
outSize.bottom = 1;
}
// Adjust for surface insets.
outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
-attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
}
/**
* This method is used to control whether we return the BLAST_SYNC flag
* from relayoutWindow calls on this window (triggering the client to redirect
* it's next draw in to a transaction). If we have pending draw handlers, we are
* looking for the client to sync.
*
* See {@link WindowState#mPendingDrawHandlers}
*/
@Override
boolean useBLASTSync() {
return super.useBLASTSync() || (mPendingDrawHandlers.size() != 0);
}
/**
* Apply the transaction with the next window redraw. A full relayout/finishDrawing
* cycle must occur before completion. This means if you call the function while
* "in relayout", the results may be undefined but at all other times the function
* should sort of transparently work like this:
* 1. Make changes to WM hierarchy (say change app configuration)
* 2. Call applyWithNextDraw
* 3. After finishDrawing, our consumer will be passed the Transaction
* containing the buffer, and we can merge in additional operations.
* See {@link WindowState#mPendingDrawHandlers}
*/
void applyWithNextDraw(Consumer consumer) {
mPendingDrawHandlers.add(consumer);
requestRedrawForSync();
mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
BLAST_TIMEOUT_DURATION);
}
/**
* Called from relayout, to indicate the next "finishDrawing" will contain
* all changes applied by the time mPendingDrawHandlers was populated.
*
* See {@link WindowState#mPendingDrawHandlers}
*/
void prepareDrawHandlers() {
mReadyDrawHandlers.addAll(mPendingDrawHandlers);
mPendingDrawHandlers.clear();
}
/**
* Drain the draw handlers, called from finishDrawing()
* See {@link WindowState#mPendingDrawHandlers}
*/
boolean executeDrawHandlers(SurfaceControl.Transaction t) {
boolean hadHandlers = false;
boolean applyHere = false;
if (t == null) {
t = mTmpTransaction;
applyHere = true;
}
for (int i = 0; i < mReadyDrawHandlers.size(); i++) {
mReadyDrawHandlers.get(i).accept(t);
hadHandlers = true;
}
if (hadHandlers) {
mReadyDrawHandlers.clear();
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
}
if (applyHere) {
t.apply();
}
return hadHandlers;
}
/**
* Adds an additional translation offset to be applied when positioning the surface. Used to
* correct offsets in specific reparenting situations, e.g. the navigation bar window attached
* on the lower split-screen app.
*/
void setSurfaceTranslationY(int translationY) {
mSurfaceTranslationY = translationY;
}
@Override
@WindowManager.LayoutParams.WindowType int getWindowType() {
return mAttrs.type;
}
void markRedrawForSyncReported() {
mRedrawForSyncReported = true;
}
boolean setWallpaperOffset(int dx, int dy, float scale) {
if (mXOffset == dx && mYOffset == dy && Float.compare(mWallpaperScale, scale) == 0) {
return false;
}
mXOffset = dx;
mYOffset = dy;
mWallpaperScale = scale;
scheduleAnimation();
return true;
}
}