/*
|
* 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_STACK_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.os.PowerManager.DRAW_WAKE_LOCK;
|
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
import static android.view.Display.DEFAULT_DISPLAY;
|
import static android.view.SurfaceControl.Transaction;
|
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
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.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_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_NOT_TOUCH_MODAL;
|
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.FORMAT_CHANGED;
|
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_NO_MOVE_ANIMATION;
|
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.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_STARTING;
|
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
|
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_METHOD;
|
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_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_TOAST;
|
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.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
|
|
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.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.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
|
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_LAYOUT_REPEATS;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
|
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
|
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.DEBUG_WALLPAPER_LIGHT;
|
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.MAX_ANIMATION_DURATION;
|
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
|
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
|
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.WindowManagerService.localLOGV;
|
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.CHILD_WINDOWS;
|
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.HAS_SURFACE;
|
import static com.android.server.wm.WindowStateProto.IDENTIFIER;
|
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.SYSTEM_UI_VISIBILITY;
|
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.Nullable;
|
import android.app.AppOpsManager;
|
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.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.UserHandle;
|
import android.os.WorkSource;
|
import android.provider.Settings;
|
import android.text.TextUtils;
|
import android.util.DisplayMetrics;
|
import android.util.MergedConfiguration;
|
import android.util.Slog;
|
import android.util.TimeUtils;
|
import android.util.proto.ProtoOutputStream;
|
import android.view.Display;
|
import android.view.DisplayCutout;
|
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.Surface.Rotation;
|
import android.view.SurfaceControl;
|
import android.view.SurfaceSession;
|
import android.view.View;
|
import android.view.ViewTreeObserver;
|
import android.view.WindowInfo;
|
import android.view.WindowManager;
|
import android.view.animation.Animation;
|
import android.view.animation.AnimationUtils;
|
import android.view.animation.Interpolator;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.ToBooleanFunction;
|
import com.android.server.policy.WindowManagerPolicy;
|
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
|
import com.android.server.wm.utils.InsetUtils;
|
import com.android.server.wm.utils.WmDisplayCutout;
|
|
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.Predicate;
|
|
/** A window in the window manager. */
|
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
|
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
|
|
// The minimal size of a window within the usable area of the freeform stack.
|
// 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;
|
|
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;
|
/** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
|
final boolean mOwnerCanAddInternalSystemWindow;
|
final WindowId mWindowId;
|
WindowToken mToken;
|
// The same object as mToken if this is an app window and null for non-app windows.
|
AppWindowToken mAppToken;
|
|
// 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 mSeq;
|
int mViewVisibility;
|
int mSystemUiVisibility;
|
|
/**
|
* The visibility flag of the window based on policy like {@link WindowManagerPolicy}.
|
* Normally set by calling {@link #showLw} and {@link #hideLw}.
|
*
|
* 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.
|
boolean mWallpaperVisible; // for wallpaper, what was last vis report?
|
private boolean mDragResizing;
|
private boolean mDragResizingChangeReported = true;
|
private int mResizeMode;
|
/**
|
* 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<IWindowFocusObserver> 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;
|
|
/**
|
* 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();
|
|
/**
|
* The last content insets returned to the client in relayout. We use
|
* these in the bounds animation to ensure we only observe inset changes
|
* at the same time that a client resizes it's surface so that we may use
|
* the geometryAppliesWithResize synchronization mechanism to keep
|
* the contents in place.
|
*/
|
final Rect mLastRelayoutContentInsets = new Rect();
|
|
/**
|
* 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 mInvGlobalScale=1;
|
float mHScale=1, mVScale=1;
|
float mLastHScale=1, mLastVScale=1;
|
final Matrix mTmpMatrix = new Matrix();
|
|
private final WindowFrames mWindowFrames = new WindowFrames();
|
|
/**
|
* Usually empty. Set to the task's tempInsetFrame. See
|
*{@link android.app.IActivityTaskManager#resizeDockedStack}.
|
*/
|
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<Rect> mExclusionRects = new ArrayList<>();
|
|
// 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: 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;
|
|
/** 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;
|
|
/**
|
* 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 InputWindowHandle mInputWindowHandle;
|
InputChannel mInputChannel;
|
private InputChannel mClientChannel;
|
|
// Used to improve performance of toString()
|
private String mStringNameCache;
|
private CharSequence mLastTitle;
|
private boolean mWasExiting;
|
|
final WindowStateAnimator mWinAnimator;
|
|
boolean mHasSurface = false;
|
|
/** When true this window can be displayed on screens owther than mOwnerUid's */
|
private boolean mShowToOwnerOnly;
|
|
// Whether the window was visible when we set the app to invisible last time. WM uses
|
// this as a hint to restore the surface (if available) for early animation next time
|
// the app is brought visible.
|
private boolean mWasVisibleBeforeClientHidden;
|
|
// 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 Point mTmpPoint = 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;
|
|
/**
|
* 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 TapExcludeRegionHolder mTapExcludeRegionHolder;
|
|
/**
|
* 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<WindowState> sWindowSubLayerComparator =
|
new Comparator<WindowState>() {
|
@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 mInsetProvider;
|
|
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
|
|
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 (mPendingSeamlessRotate != null) {
|
oldRotation = mPendingSeamlessRotate.getOldRotation();
|
}
|
|
if (mForceSeamlesslyRotate || requested) {
|
mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
|
mPendingSeamlessRotate.unrotate(transaction, this);
|
mWmService.markForSeamlessRotation(this, true);
|
}
|
}
|
|
void finishSeamlessRotation(boolean timeout) {
|
if (mPendingSeamlessRotate != null) {
|
mPendingSeamlessRotate.finish(this, timeout);
|
mFinishSeamlessRotateFrameNumber = getFrameNumber();
|
mPendingSeamlessRotate = null;
|
mWmService.markForSeamlessRotation(this, false);
|
}
|
}
|
|
List<Rect> getSystemGestureExclusion() {
|
return mExclusionRects;
|
}
|
|
/**
|
* Sets the system gesture exclusion rects.
|
*
|
* @return {@code true} if anything changed
|
*/
|
boolean setSystemGestureExclusion(List<Rect> exclusionRects) {
|
if (mExclusionRects.equals(exclusionRects)) {
|
return false;
|
}
|
mExclusionRects.clear();
|
mExclusionRects.addAll(exclusionRects);
|
return true;
|
}
|
|
boolean isImplicitlyExcludingAllSystemGestures() {
|
final int immersiveStickyFlags =
|
SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
final boolean immersiveSticky =
|
(mSystemUiVisibility & immersiveStickyFlags) == immersiveStickyFlags;
|
return immersiveSticky && mWmService.mSystemGestureExcludedByPreQStickyImmersive
|
&& mAppToken != null && mAppToken.mTargetSdk < Build.VERSION_CODES.Q;
|
}
|
|
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, int seq, WindowManager.LayoutParams a,
|
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
|
this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId,
|
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, int seq, WindowManager.LayoutParams a,
|
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
|
PowerManagerWrapper powerManagerWrapper) {
|
super(service);
|
mSession = s;
|
mClient = c;
|
mAppOp = appOp;
|
mToken = token;
|
mAppToken = mToken.asAppWindowToken();
|
mOwnerUid = ownerId;
|
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();
|
mSeq = seq;
|
mPowerManagerWrapper = powerManagerWrapper;
|
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
|
if (localLOGV) 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;
|
mInputWindowHandle = null;
|
mWinAnimator = 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;
|
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + parentWindow);
|
parentWindow.addChild(this, sWindowSubLayerComparator);
|
|
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 (mAppToken != null && mAppToken.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;
|
mInputWindowHandle = new InputWindowHandle(
|
mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
|
getDisplayId());
|
}
|
|
void attach() {
|
if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
|
mSession.windowAddedLocked(mAttrs.packageName);
|
}
|
|
/**
|
* @return {@code true} if the application runs in size compatibility mode.
|
* @see android.content.res.CompatibilityInfo#supportsScreen
|
* @see ActivityRecord#inSizeCompatMode
|
*/
|
boolean inSizeCompatMode() {
|
return (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0
|
|| (mAppToken != null && mAppToken.inSizeCompatMode()
|
// Exclude starting window because it is not displayed by the application.
|
&& mAttrs.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() {
|
super.onParentChanged();
|
setDrawnStateEvaluated(false /*evaluated*/);
|
|
getDisplayContent().reapplyMagnificationSpec();
|
}
|
|
@Override
|
public int getOwningUid() {
|
return mOwnerUid;
|
}
|
|
@Override
|
public String getOwningPackage() {
|
return mAttrs.packageName;
|
}
|
|
@Override
|
public boolean canAddInternalSystemWindow() {
|
return mOwnerCanAddInternalSystemWindow;
|
}
|
|
@Override
|
public 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);
|
}
|
|
@Override
|
public Rect getDisplayedBounds() {
|
final Task task = getTask();
|
if (task != null) {
|
Rect bounds = task.getOverrideDisplayedBounds();
|
if (!bounds.isEmpty()) {
|
return bounds;
|
}
|
}
|
return super.getDisplayedBounds();
|
}
|
|
@Override
|
public void computeFrameLw() {
|
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 isFullscreenAndFillsDisplay = !inMultiWindowMode() && matchesDisplayBounds();
|
final boolean windowsAreFloating = task != null && task.isFloating();
|
final DisplayContent dc = getDisplayContent();
|
|
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 boolean isImeTarget =
|
imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget();
|
if (isFullscreenAndFillsDisplay || layoutInParentFrame()) {
|
// We use the parent frame as the containing frame for fullscreen and child windows
|
mWindowFrames.mContainingFrame.set(mWindowFrames.mParentFrame);
|
layoutDisplayFrame = mWindowFrames.mDisplayFrame;
|
layoutContainingFrame = mWindowFrames.mParentFrame;
|
layoutXDiff = 0;
|
layoutYDiff = 0;
|
} else {
|
mWindowFrames.mContainingFrame.set(getDisplayedBounds());
|
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
|
|
// If the bounds are frozen, we still want to translate the window freely and only
|
// freeze the size.
|
Rect frozen = mAppToken.mFrozenBounds.peek();
|
mWindowFrames.mContainingFrame.right =
|
mWindowFrames.mContainingFrame.left + frozen.width();
|
mWindowFrames.mContainingFrame.bottom =
|
mWindowFrames.mContainingFrame.top + frozen.height();
|
}
|
// 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 top of the screen.
|
final int bottomOverlap = mWindowFrames.mContainingFrame.bottom
|
- mWindowFrames.mVisibleFrame.bottom;
|
if (bottomOverlap > 0) {
|
final int distanceToTop = Math.max(mWindowFrames.mContainingFrame.top
|
- mWindowFrames.mDisplayFrame.top, 0);
|
int offs = Math.min(bottomOverlap, distanceToTop);
|
mWindowFrames.mContainingFrame.top -= offs;
|
}
|
} else if (!inPinnedWindowingMode() && mWindowFrames.mContainingFrame.bottom
|
> mWindowFrames.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 pinned stack, they are handled separately in the PinnedStackController.
|
mWindowFrames.mContainingFrame.bottom = mWindowFrames.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 (mWindowFrames.mContainingFrame.isEmpty()) {
|
mWindowFrames.mContainingFrame.set(mWindowFrames.mContentFrame);
|
}
|
}
|
|
final TaskStack stack = getStack();
|
if (inPinnedWindowingMode() && stack != null
|
&& stack.lastAnimatingBoundsWasToFullscreen()) {
|
// PIP edge case: When going from pinned to fullscreen, we apply a
|
// tempInsetFrame for the full task - but we're still at the start of the animation.
|
// To prevent a jump if there's a letterbox, restrict to the parent frame.
|
mInsetFrame.intersectUnchecked(mWindowFrames.mParentFrame);
|
mWindowFrames.mContainingFrame.intersectUnchecked(mWindowFrames.mParentFrame);
|
}
|
|
layoutDisplayFrame = new Rect(mWindowFrames.mDisplayFrame);
|
mWindowFrames.mDisplayFrame.set(mWindowFrames.mContainingFrame);
|
layoutXDiff = mInsetFrame.left - mWindowFrames.mContainingFrame.left;
|
layoutYDiff = mInsetFrame.top - mWindowFrames.mContainingFrame.top;
|
layoutContainingFrame = mInsetFrame;
|
mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
|
subtractInsets(mWindowFrames.mDisplayFrame, layoutContainingFrame, layoutDisplayFrame,
|
mTmpRect);
|
if (!layoutInParentFrame()) {
|
subtractInsets(mWindowFrames.mContainingFrame, layoutContainingFrame,
|
mWindowFrames.mParentFrame, mTmpRect);
|
subtractInsets(mInsetFrame, layoutContainingFrame, mWindowFrames.mParentFrame,
|
mTmpRect);
|
}
|
layoutDisplayFrame.intersect(layoutContainingFrame);
|
}
|
|
final int pw = mWindowFrames.mContainingFrame.width();
|
final int ph = mWindowFrames.mContainingFrame.height();
|
|
if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
|
mLastRequestedWidth = mRequestedWidth;
|
mLastRequestedHeight = mRequestedHeight;
|
mWindowFrames.setContentChanged(true);
|
}
|
|
final int fw = mWindowFrames.mFrame.width();
|
final int fh = mWindowFrames.mFrame.height();
|
|
applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
|
|
// Calculate the outsets before the content frame gets shrinked to the window frame.
|
mWindowFrames.calculateOutsets();
|
|
// Make sure the content and visible frames are inside of the
|
// final window frame.
|
if (windowsAreFloating && !mWindowFrames.mFrame.isEmpty()) {
|
final int visBottom = mWindowFrames.mVisibleFrame.bottom;
|
final int contentBottom = mWindowFrames.mContentFrame.bottom;
|
mWindowFrames.mContentFrame.set(mWindowFrames.mFrame);
|
mWindowFrames.mVisibleFrame.set(mWindowFrames.mContentFrame);
|
mWindowFrames.mStableFrame.set(mWindowFrames.mContentFrame);
|
if (isImeTarget && inFreeformWindowingMode()) {
|
// After displacing a freeform window to make room for the ime, any part of
|
// the window still covered by IME should be inset.
|
if (contentBottom + layoutYDiff < mWindowFrames.mContentFrame.bottom) {
|
mWindowFrames.mContentFrame.bottom = contentBottom + layoutYDiff;
|
}
|
if (visBottom + layoutYDiff < mWindowFrames.mVisibleFrame.bottom) {
|
mWindowFrames.mVisibleFrame.bottom = visBottom + layoutYDiff;
|
}
|
}
|
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
|
dc.getDockedDividerController().positionDockedStackedDivider(mWindowFrames.mFrame);
|
mWindowFrames.mContentFrame.set(mWindowFrames.mFrame);
|
if (!mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame)) {
|
mMovedByResize = true;
|
}
|
} else {
|
mWindowFrames.mContentFrame.set(
|
Math.max(mWindowFrames.mContentFrame.left, mWindowFrames.mFrame.left),
|
Math.max(mWindowFrames.mContentFrame.top, mWindowFrames.mFrame.top),
|
Math.min(mWindowFrames.mContentFrame.right, mWindowFrames.mFrame.right),
|
Math.min(mWindowFrames.mContentFrame.bottom, mWindowFrames.mFrame.bottom));
|
|
mWindowFrames.mVisibleFrame.set(
|
Math.max(mWindowFrames.mVisibleFrame.left, mWindowFrames.mFrame.left),
|
Math.max(mWindowFrames.mVisibleFrame.top, mWindowFrames.mFrame.top),
|
Math.min(mWindowFrames.mVisibleFrame.right, mWindowFrames.mFrame.right),
|
Math.min(mWindowFrames.mVisibleFrame.bottom, mWindowFrames.mFrame.bottom));
|
|
mWindowFrames.mStableFrame.set(
|
Math.max(mWindowFrames.mStableFrame.left, mWindowFrames.mFrame.left),
|
Math.max(mWindowFrames.mStableFrame.top, mWindowFrames.mFrame.top),
|
Math.min(mWindowFrames.mStableFrame.right, mWindowFrames.mFrame.right),
|
Math.min(mWindowFrames.mStableFrame.bottom, mWindowFrames.mFrame.bottom));
|
}
|
|
if (isFullscreenAndFillsDisplay && !windowsAreFloating) {
|
// Windows that are not fullscreen can be positioned outside of the display frame,
|
// but that is not a reason to provide them with overscan insets.
|
InsetUtils.insetsBetweenFrames(layoutContainingFrame, mWindowFrames.mOverscanFrame,
|
mWindowFrames.mOverscanInsets);
|
}
|
|
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
|
final WmDisplayCutout c = mWindowFrames.mDisplayCutout.calculateRelativeTo(
|
mWindowFrames.mDisplayFrame);
|
mWindowFrames.calculateDockedDividerInsets(c.getDisplayCutout().getSafeInsets());
|
} else {
|
getDisplayContent().getBounds(mTmpRect);
|
mWindowFrames.calculateInsets(
|
windowsAreFloating, isFullscreenAndFillsDisplay, mTmpRect);
|
}
|
|
mWindowFrames.setDisplayCutout(
|
mWindowFrames.mDisplayCutout.calculateRelativeTo(mWindowFrames.mFrame));
|
|
// Offset the actual frame by the amount layout frame is off.
|
mWindowFrames.offsetFrames(-layoutXDiff, -layoutYDiff);
|
|
mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);
|
if (inSizeCompatMode()) {
|
// If there is a size compatibility scale being applied to the
|
// window, we need to apply this to its insets so that they are
|
// reported to the app in its coordinate space.
|
mWindowFrames.scaleInsets(mInvGlobalScale);
|
|
// Also the scaled frame that we report to the app needs to be
|
// adjusted to be in its coordinate space.
|
mWindowFrames.mCompatFrame.scale(mInvGlobalScale);
|
}
|
|
if (mIsWallpaper && (fw != mWindowFrames.mFrame.width()
|
|| fh != mWindowFrames.mFrame.height())) {
|
final DisplayContent displayContent = getDisplayContent();
|
if (displayContent != null) {
|
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
|
getDisplayContent().mWallpaperController.updateWallpaperOffset(this,
|
displayInfo.logicalWidth, displayInfo.logicalHeight, false);
|
}
|
}
|
|
if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
|
"Resolving (mRequestedWidth="
|
+ mRequestedWidth + ", mRequestedheight="
|
+ mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
|
+ "): frame=" + mWindowFrames.mFrame.toShortString()
|
+ " " + mWindowFrames.getInsetsInfo()
|
+ " " + mAttrs.getTitle());
|
}
|
|
// TODO: Look into whether this override is still necessary.
|
@Override
|
public Rect getBounds() {
|
if (mAppToken != null) {
|
return mAppToken.getBounds();
|
} else {
|
return super.getBounds();
|
}
|
}
|
|
@Override
|
public Rect getFrameLw() {
|
return mWindowFrames.mFrame;
|
}
|
|
@Override
|
public Rect getDisplayFrameLw() {
|
return mWindowFrames.mDisplayFrame;
|
}
|
|
@Override
|
public Rect getOverscanFrameLw() {
|
return mWindowFrames.mOverscanFrame;
|
}
|
|
@Override
|
public Rect getContentFrameLw() {
|
return mWindowFrames.mContentFrame;
|
}
|
|
@Override
|
public Rect getVisibleFrameLw() {
|
return mWindowFrames.mVisibleFrame;
|
}
|
|
Rect getStableFrameLw() {
|
return mWindowFrames.mStableFrame;
|
}
|
|
Rect getDecorFrame() {
|
return mWindowFrames.mDecorFrame;
|
}
|
|
Rect getParentFrame() {
|
return mWindowFrames.mParentFrame;
|
}
|
|
Rect getContainingFrame() {
|
return mWindowFrames.mContainingFrame;
|
}
|
|
WmDisplayCutout getWmDisplayCutout() {
|
return mWindowFrames.mDisplayCutout;
|
}
|
|
void getCompatFrame(Rect outFrame) {
|
outFrame.set(mWindowFrames.mCompatFrame);
|
}
|
|
void getCompatFrameSize(Rect outFrame) {
|
outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
|
}
|
|
@Override
|
public boolean getGivenInsetsPendingLw() {
|
return mGivenInsetsPending;
|
}
|
|
@Override
|
public Rect getGivenContentInsetsLw() {
|
return mGivenContentInsets;
|
}
|
|
@Override
|
public Rect getGivenVisibleInsetsLw() {
|
return mGivenVisibleInsets;
|
}
|
|
@Override
|
public WindowManager.LayoutParams getAttrs() {
|
return mAttrs;
|
}
|
|
@Override
|
public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
|
return getDisplayContent().getNeedsMenu(this, bottom);
|
}
|
|
@Override
|
public int getSystemUiVisibility() {
|
return mSystemUiVisibility;
|
}
|
|
@Override
|
public int getSurfaceLayer() {
|
return mLayer;
|
}
|
|
@Override
|
public int getBaseType() {
|
return getTopParentWindow().mAttrs.type;
|
}
|
|
@Override
|
public IApplicationToken getAppToken() {
|
return mAppToken != null ? mAppToken.appToken : null;
|
}
|
|
@Override
|
public boolean isVoiceInteraction() {
|
return mAppToken != null && mAppToken.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 || isGoneForLayoutLw()) {
|
return;
|
}
|
|
final Task task = getTask();
|
// In the case of stack bound animations, the window frames will update (unlike other
|
// animations which just modify various transformation properties). We don't want to
|
// notify the client of frame changes in this case. Not only is it a lot of churn, but
|
// the frame may not correspond to the surface size or the onscreen area at various
|
// phases in the animation, and the client will become sad and confused.
|
if (task != null && task.mStack.isAnimatingBounds()) {
|
return;
|
}
|
|
boolean didFrameInsetsChange = setReportResizeHints();
|
boolean configChanged = !isLastConfigReportedToClient();
|
if (DEBUG_CONFIGURATION && configChanged) {
|
Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
|
}
|
|
final boolean dragResizingChanged = isDragResizeChanged()
|
&& !isDragResizingChangeReported();
|
|
if (localLOGV) 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.
|
mWindowFrames.mLastFrame.set(mWindowFrames.mFrame);
|
|
if (didFrameInsetsChange
|
|| winAnimator.mSurfaceResized
|
|| configChanged
|
|| dragResizingChanged
|
|| mReportOrientationChanged) {
|
if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
|
Slog.v(TAG_WM, "Resize reasons for w=" + this + ": "
|
+ " " + mWindowFrames.getInsetsChangedInfo()
|
+ " surfaceResized=" + winAnimator.mSurfaceResized
|
+ " configChanged=" + configChanged
|
+ " dragResizingChanged=" + dragResizingChanged
|
+ " reportOrientationChanged=" + 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 (mAppToken != null && mAppDied) {
|
mAppToken.removeDeadWindows();
|
return;
|
}
|
|
updateLastInsetValues();
|
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 (DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
|
Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
|
+ ", mDrawState=DRAW_PENDING in " + this
|
+ ", surfaceController " + winAnimator.mSurfaceController);
|
}
|
winAnimator.mDrawState = DRAW_PENDING;
|
if (mAppToken != null) {
|
mAppToken.clearAllDrawn();
|
}
|
}
|
if (!mWmService.mResizingWindows.contains(this)) {
|
if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM, "Resizing window " + this);
|
mWmService.mResizingWindows.add(this);
|
}
|
} else if (getOrientationChanging()) {
|
if (isDrawnLw()) {
|
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Orientation not waiting for draw in "
|
+ this + ", surfaceController " + 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) {
|
mOrientationChanging = changing;
|
mOrientationChangeTimedOut = false;
|
}
|
|
void orientationChangeTimedOut() {
|
mOrientationChangeTimedOut = true;
|
}
|
|
@Override
|
DisplayContent getDisplayContent() {
|
return mToken.getDisplayContent();
|
}
|
|
@Override
|
void onDisplayChanged(DisplayContent dc) {
|
super.onDisplayChanged(dc);
|
// Window was not laid out for this display yet, so make sure mLayoutSeq does not match.
|
if (dc != null && mInputWindowHandle.displayId != dc.getDisplayId()) {
|
mLayoutSeq = dc.mLayoutSeq - 1;
|
mInputWindowHandle.displayId = dc.getDisplayId();
|
}
|
}
|
|
DisplayInfo getDisplayInfo() {
|
final DisplayContent displayContent = getDisplayContent();
|
return displayContent != null ? displayContent.getDisplayInfo() : null;
|
}
|
|
@Override
|
public int getDisplayId() {
|
final DisplayContent displayContent = getDisplayContent();
|
if (displayContent == null) {
|
return Display.INVALID_DISPLAY;
|
}
|
return displayContent.getDisplayId();
|
}
|
|
Task getTask() {
|
return mAppToken != null ? mAppToken.getTask() : null;
|
}
|
|
TaskStack getStack() {
|
Task task = getTask();
|
if (task != null) {
|
if (task.mStack != null) {
|
return task.mStack;
|
}
|
}
|
// Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
|
// associate them with some stack to enable dimming.
|
final DisplayContent dc = getDisplayContent();
|
return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getHomeStack() : null;
|
}
|
|
/**
|
* Retrieves the visible bounds of the window.
|
* @param bounds The rect which gets the bounds.
|
*/
|
void getVisibleBounds(Rect bounds) {
|
final Task task = getTask();
|
boolean intersectWithStackBounds = task != null && task.cropWindowsToStackBounds();
|
bounds.setEmpty();
|
mTmpRect.setEmpty();
|
if (intersectWithStackBounds) {
|
final TaskStack stack = task.mStack;
|
if (stack != null) {
|
stack.getDimBounds(mTmpRect);
|
} else {
|
intersectWithStackBounds = false;
|
}
|
}
|
|
bounds.set(mWindowFrames.mVisibleFrame);
|
if (intersectWithStackBounds) {
|
bounds.intersect(mTmpRect);
|
}
|
|
if (bounds.isEmpty()) {
|
bounds.set(mWindowFrames.mFrame);
|
if (intersectWithStackBounds) {
|
bounds.intersect(mTmpRect);
|
}
|
return;
|
}
|
}
|
|
public long getInputDispatchingTimeoutNanos() {
|
return mAppToken != null
|
? mAppToken.mInputDispatchingTimeoutNanos
|
: WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
|
}
|
|
@Override
|
public boolean hasAppShownWindows() {
|
return mAppToken != null && (mAppToken.firstWindowDrawn || mAppToken.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 (inSizeCompatMode()) {
|
mGlobalScale = mToken.getSizeCompatScale();
|
mInvGlobalScale = 1 / mGlobalScale;
|
} else {
|
mGlobalScale = mInvGlobalScale = 1;
|
}
|
}
|
|
@Override
|
boolean hasContentToDisplay() {
|
if (!mAppFreezing && isDrawnLw() && (mViewVisibility == View.VISIBLE
|
|| (isAnimating() && !getDisplayContent().mAppTransition.isTransitionSet()))) {
|
return true;
|
}
|
|
return super.hasContentToDisplay();
|
}
|
|
@Override
|
boolean isVisible() {
|
return wouldBeVisibleIfPolicyIgnored() && 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.
|
&& (mInsetProvider == null || mInsetProvider.isClientVisible());
|
}
|
|
/**
|
* 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() {
|
return mHasSurface && !isParentWindowHidden()
|
&& !mAnimatingExit && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
|
}
|
|
@Override
|
public boolean isVisibleLw() {
|
return isVisible();
|
}
|
|
/**
|
* 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 (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.isSelfAnimating())
|
&& isVisible();
|
}
|
|
/**
|
* The same as isVisible(), but follows the current hidden state of the associated app token,
|
* not the pending requested hidden state.
|
*/
|
boolean isVisibleNow() {
|
/*
|
* If window is App Window, we get visible from ActivityRecord.
|
* Because mToken.isHidden() value refreshed slowly ActivityRecord visible.
|
* And it will cause that Activity invisible but mToken is not hidden.
|
*/
|
boolean visible;
|
if (mAppToken != null)
|
visible = mAppToken.mActivityRecord.visible;
|
else
|
visible = !mToken.isHidden();
|
return (visible || 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() {
|
return isVisibleNow() && !mRemoved
|
&& mInputChannel != null && mInputWindowHandle != null;
|
}
|
|
/**
|
* Same as isVisible(), but we also count it as visible between the
|
* call to IWindowSession.add() and the first relayout().
|
*/
|
boolean isVisibleOrAdding() {
|
final AppWindowToken atoken = mAppToken;
|
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
|
&& isVisibleByPolicy() && !isParentWindowHidden()
|
&& (atoken == null || !atoken.hiddenRequested)
|
&& !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 AppWindowToken atoken = mAppToken;
|
if (atoken != null) {
|
return ((!isParentWindowHidden() && !atoken.hiddenRequested)
|
|| isAnimating());
|
}
|
return !isParentWindowHidden() || isAnimating();
|
}
|
|
/**
|
* 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 mAppToken != null && !mAppDied
|
&& (!mAppToken.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.isHidden();
|
return mHasSurface && isVisibleByPolicy() && !mDestroying
|
&& (parentAndClientVisible || isAnimating());
|
}
|
|
// TODO: Another visibility method that was added late in the release to minimize risk.
|
@Override
|
public boolean canAffectSystemUiFlags() {
|
final boolean translucent = mAttrs.alpha == 0.0f;
|
if (translucent) {
|
return false;
|
}
|
if (mAppToken == 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 && !mAppToken.isHidden();
|
}
|
}
|
|
/**
|
* Like isOnScreen, but returns false if the surface hasn't yet
|
* been drawn.
|
*/
|
@Override
|
public boolean isDisplayedLw() {
|
final AppWindowToken atoken = mAppToken;
|
return isDrawnLw() && isVisibleByPolicy()
|
&& ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested))
|
|| isAnimating());
|
}
|
|
/**
|
* Return true if this window or its app token is currently animating.
|
*/
|
@Override
|
public boolean isAnimatingLw() {
|
return isAnimating();
|
}
|
|
@Override
|
public boolean isGoneForLayoutLw() {
|
final AppWindowToken atoken = mAppToken;
|
return mViewVisibility == View.GONE
|
|| !mRelayoutCalled
|
|| (atoken == null && mToken.isHidden())
|
|| (atoken != null && atoken.hiddenRequested)
|
|| 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.
|
*/
|
@Override
|
public boolean isDrawnLw() {
|
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.
|
return ((!mIsWallpaper && mAttrs.format == PixelFormat.OPAQUE)
|
|| (mIsWallpaper && mWallpaperVisible))
|
&& isDrawnLw() && !isAnimating();
|
}
|
|
@Override
|
void onMovedByResize() {
|
if (DEBUG_RESIZE) Slog.d(TAG, "onMovedByResize: Moving " + this);
|
mMovedByResize = true;
|
super.onMovedByResize();
|
}
|
|
boolean onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
|
boolean changed = false;
|
|
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
final WindowState c = mChildren.get(i);
|
changed |= c.onAppVisibilityChanged(visible, runningAppAnimation);
|
}
|
|
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() && mAppToken.isSelfAnimating()) {
|
mAnimatingExit = true;
|
mRemoveOnExit = true;
|
mWindowRemovalAllowed = true;
|
}
|
return changed;
|
}
|
|
final boolean isVisibleNow = isVisibleNow();
|
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 != null) {
|
accessibilityController.onWindowTransitionLocked(this, winTransit);
|
}
|
}
|
changed = true;
|
setDisplayLayoutNeeded();
|
}
|
|
return changed;
|
}
|
|
boolean onSetAppExiting() {
|
final DisplayContent displayContent = getDisplayContent();
|
boolean changed = false;
|
|
if (isVisibleNow()) {
|
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
|
if (mWmService.mAccessibilityController != null) {
|
mWmService.mAccessibilityController.onWindowTransitionLocked(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();
|
}
|
|
return changed;
|
}
|
|
@Override
|
void onResize() {
|
final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;
|
if (mHasSurface && !isGoneForLayoutLw() && !resizingWindows.contains(this)) {
|
if (DEBUG_RESIZE) Slog.d(TAG, "onResize: Resizing " + this);
|
resizingWindows.add(this);
|
}
|
if (isGoneForLayoutLw()) {
|
mResizedWhileGone = true;
|
}
|
|
super.onResize();
|
}
|
|
void onUnfreezeBounds() {
|
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
final WindowState c = mChildren.get(i);
|
c.onUnfreezeBounds();
|
}
|
|
if (!mHasSurface) {
|
return;
|
}
|
|
mLayoutNeeded = true;
|
setDisplayLayoutNeeded();
|
if (!mWmService.mResizingWindows.contains(this)) {
|
mWmService.mResizingWindows.add(this);
|
}
|
}
|
|
/**
|
* 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;
|
final Task task = getTask();
|
final boolean adjustedForMinimizedDockOrIme = task != null
|
&& (task.mStack.isAdjustedForMinimizedDockedStack()
|
|| task.mStack.isAdjustedForIme());
|
if (mToken.okToAnimate()
|
&& (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
|
&& !isDragResizing() && !adjustedForMinimizedDockOrIme
|
&& getWindowConfiguration().hasMovementAnimations()
|
&& !mWinAnimator.mLastHidden
|
&& !mSeamlesslyRotated) {
|
startMoveAnimation(left, top);
|
}
|
|
//TODO (multidisplay): Accessibility supported only for the default display.
|
if (mWmService.mAccessibilityController != null
|
&& getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
|
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
|
}
|
|
try {
|
mClient.moved(left, top);
|
} catch (RemoteException e) {
|
}
|
mMovedByResize = false;
|
}
|
|
/**
|
* Return whether this window has moved. (Only makes
|
* sense to call from performLayoutAndPlaceSurfacesLockedInner().)
|
*/
|
private boolean hasMoved() {
|
return mHasSurface && (mWindowFrames.hasContentChanged() || mMovedByResize)
|
&& !mAnimatingExit
|
&& (mWindowFrames.mFrame.top != mWindowFrames.mLastFrame.top
|
|| mWindowFrames.mFrame.left != mWindowFrames.mLastFrame.left)
|
&& (!mIsChildWindow || !getParentWindow().hasMoved());
|
}
|
|
boolean isObscuringDisplay() {
|
Task task = getTask();
|
if (task != null && task.mStack != null && !task.mStack.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;
|
}
|
|
private boolean matchesDisplayBounds() {
|
return getDisplayContent().getBounds().equals(getBounds());
|
}
|
|
/**
|
* @return {@code true} if last applied config was reported to the client already, {@code false}
|
* otherwise.
|
*/
|
boolean isLastConfigReportedToClient() {
|
return mLastConfigReportedToClient;
|
}
|
|
@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
|
// stack 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 forceWindowsScaleableInTransaction(boolean force) {
|
if (mWinAnimator != null && mWinAnimator.hasSurface()) {
|
mWinAnimator.mSurfaceController.forceScaleableInTransaction(force);
|
}
|
|
super.forceWindowsScaleableInTransaction(force);
|
}
|
|
@Override
|
void removeImmediately() {
|
super.removeImmediately();
|
|
if (mRemoved) {
|
// Nothing to do.
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
"WS.removeImmediately: " + this + " Already removed...");
|
return;
|
}
|
|
mRemoved = true;
|
|
mWillReplaceWindow = false;
|
if (mReplacementWindow != null) {
|
mReplacementWindow.mSkipEnterAnimationForSeamlessReplacement = false;
|
}
|
|
final DisplayContent dc = getDisplayContent();
|
if (isInputMethodTarget()) {
|
dc.computeImeTarget(true /* updateImeTarget */);
|
}
|
|
final int type = mAttrs.type;
|
if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
|
dc.mTapExcludedWindows.remove(this);
|
}
|
if (mTapExcludeRegionHolder != null) {
|
// If a tap exclude region container was initialized for this window, then it should've
|
// also been registered in display.
|
dc.mTapExcludeProvidingWindows.remove(this);
|
}
|
dc.getDisplayPolicy().removeWindowLw(this);
|
|
disposeInputChannel();
|
|
mWinAnimator.destroyDeferredSurfaceLocked();
|
mWinAnimator.destroySurfaceLocked();
|
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;
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG,
|
"removeIfPossible: " + this + " callers=" + Debug.getCallers(5));
|
|
final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
|
if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM,
|
"Starting window removed " + this);
|
|
if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused())
|
Slog.v(TAG_WM, "Remove " + this + " client="
|
+ Integer.toHexString(System.identityHashCode(mClient.asBinder()))
|
+ ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
|
+ Debug.getCallers(5));
|
|
final long origId = Binder.clearCallingIdentity();
|
|
try {
|
disposeInputChannel();
|
|
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
|
+ ": mSurfaceController=" + mWinAnimator.mSurfaceController
|
+ " mAnimatingExit=" + mAnimatingExit
|
+ " mRemoveOnExit=" + mRemoveOnExit
|
+ " mHasSurface=" + mHasSurface
|
+ " surfaceShowing=" + mWinAnimator.getShown()
|
+ " animating=" + isAnimating()
|
+ " app-animation="
|
+ (mAppToken != null ? mAppToken.isSelfAnimating() : "false")
|
+ " mWillReplaceWindow=" + mWillReplaceWindow
|
+ " inPendingTransaction="
|
+ (mAppToken != null ? mAppToken.inPendingTransaction : false)
|
+ " mDisplayFrozen=" + mWmService.mDisplayFrozen
|
+ " callers=" + Debug.getCallers(6));
|
|
// Visibility of the removed window. Will be used later to update orientation later on.
|
boolean wasVisible = false;
|
|
final int displayId = getDisplayId();
|
|
// 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.
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
"Preserving " + this + " until the new one is " + "added");
|
// 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) {
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
"Not removing " + this + " because app died while it's visible");
|
|
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 != null) {
|
mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
|
}
|
}
|
final boolean isAnimating = isAnimating()
|
&& (mAppToken == null || !mAppToken.isWaitingForTransitionStart());
|
final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
|
&& mAppToken.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() && mAnimatingExit
|
&& (!lastWindowIsStartingWindow || isAnimating)) {
|
// The exit animation is running or should run... wait for it!
|
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
|
"Not removing " + this + " due to exit animation ");
|
setupWindowForRemoveOnExit();
|
if (mAppToken != null) {
|
mAppToken.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.updateOrientationFromAppTokens()) {
|
displayContent.sendNewConfiguration();
|
}
|
}
|
mWmService.updateFocusedWindowLocked(isFocused()
|
? UPDATE_FOCUS_REMOVING_FOCUS
|
: UPDATE_FOCUS_NORMAL,
|
true /*updateInputWindows*/);
|
} finally {
|
Binder.restoreCallingIdentity(origId);
|
}
|
}
|
|
private void setupWindowForRemoveOnExit() {
|
mRemoveOnExit = true;
|
setDisplayLayoutNeeded();
|
// 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;
|
}
|
|
boolean canBeImeTarget() {
|
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;
|
}
|
|
final boolean windowsAreFocusable = mAppToken == null || mAppToken.windowsAreFocusable();
|
if (!windowsAreFocusable) {
|
// This window can't be an IME target if the app's windows should not be focusable.
|
return false;
|
}
|
|
final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
|
final int type = mAttrs.type;
|
|
// 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)
|
&& type != TYPE_APPLICATION_STARTING) {
|
return false;
|
}
|
|
if (DEBUG_INPUT_METHOD) {
|
Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
|
if (!isVisibleOrAdding()) {
|
Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
|
+ " relayoutCalled=" + mRelayoutCalled
|
+ " viewVis=" + mViewVisibility
|
+ " policyVis=" + isVisibleByPolicy()
|
+ " policyVisAfterAnim=" + mLegacyPolicyVisibilityAfterAnim
|
+ " parentHidden=" + isParentWindowHidden()
|
+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
|
if (mAppToken != null) {
|
Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);
|
}
|
}
|
}
|
return isVisibleOrAdding();
|
}
|
|
private final class DeadWindowEventReceiver extends InputEventReceiver {
|
DeadWindowEventReceiver(InputChannel inputChannel) {
|
super(inputChannel, mWmService.mH.getLooper());
|
}
|
@Override
|
public void onInputEvent(InputEvent event) {
|
finishInputEvent(event, true);
|
}
|
}
|
/**
|
* Dummy 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();
|
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
|
mInputChannel = inputChannels[0];
|
mClientChannel = inputChannels[1];
|
mInputWindowHandle.token = mClient.asBinder();
|
if (outInputChannel != null) {
|
mClientChannel.transferTo(outInputChannel);
|
mClientChannel.dispose();
|
mClientChannel = null;
|
} else {
|
// If the window died visible, we setup a dummy input channel, so that taps
|
// can still detected by input monitor channel, and we can relaunch the app.
|
// Create dummy event receiver that simply reports all events as handled.
|
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
|
}
|
mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
|
}
|
|
void disposeInputChannel() {
|
if (mDeadWindowEventReceiver != null) {
|
mDeadWindowEventReceiver.dispose();
|
mDeadWindowEventReceiver = null;
|
}
|
|
// unregister server channel first otherwise it complains about broken channel
|
if (mInputChannel != null) {
|
mWmService.mInputManager.unregisterInputChannel(mInputChannel);
|
|
mInputChannel.dispose();
|
mInputChannel = null;
|
}
|
if (mClientChannel != null) {
|
mClientChannel.dispose();
|
mClientChannel = null;
|
}
|
mInputWindowHandle.token = null;
|
}
|
|
/** Returns true if the replacement window was removed. */
|
boolean removeReplacedWindowIfNeeded(WindowState replacement) {
|
if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawnLw()) {
|
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() {
|
if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + 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();
|
}
|
}
|
|
void applyAdjustForImeIfNeeded() {
|
final Task task = getTask();
|
if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) {
|
task.mStack.applyAdjustForImeIfNeeded(task);
|
}
|
}
|
|
@Override
|
void switchUser() {
|
super.switchUser();
|
if (isHiddenFromUserLocked()) {
|
if (DEBUG_VISIBILITY) Slog.w(TAG_WM, "user changing, hiding " + this
|
+ ", attrs=" + mAttrs.type + ", belonging to " + mOwnerUid);
|
clearPolicyVisibilityFlag(VISIBLE_FOR_USER);
|
} else {
|
setPolicyVisibilityFlag(VISIBLE_FOR_USER);
|
}
|
}
|
|
int getSurfaceTouchableRegion(InputWindowHandle inputWindowHandle, int flags) {
|
final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
|
final Region region = inputWindowHandle.touchableRegion;
|
setTouchableRegionCropIfNeeded(inputWindowHandle);
|
|
final Rect appOverrideBounds = mAppToken != null
|
? mAppToken.getResolvedOverrideBounds() : null;
|
if (appOverrideBounds != null && !appOverrideBounds.isEmpty()) {
|
// There may have touchable letterboxes around the activity, so in order to let the
|
// letterboxes are able to receive touch event and slip to activity, the activity with
|
// compatibility bounds cannot occupy full screen touchable region.
|
if (modal) {
|
// A modal window uses the whole compatibility bounds.
|
flags |= FLAG_NOT_TOUCH_MODAL;
|
mTmpRect.set(0, 0, appOverrideBounds.width(), appOverrideBounds.height());
|
} else {
|
// Non-modal uses the application based frame.
|
mTmpRect.set(mWindowFrames.mCompatFrame);
|
}
|
// The offset of compatibility bounds is applied to surface of {@link #AppWindowToken}
|
// and frame, so it is unnecessary to translate twice in surface based coordinates.
|
final int surfaceOffsetX = mAppToken.inSizeCompatMode()
|
? mAppToken.getBounds().left : 0;
|
mTmpRect.offset(surfaceOffsetX - mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
|
region.set(mTmpRect);
|
return flags;
|
}
|
|
if (modal && mAppToken != null) {
|
// Limit the outer touch to the activity stack region.
|
flags |= FLAG_NOT_TOUCH_MODAL;
|
// 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.
|
mAppToken.getLetterboxInnerBounds(mTmpRect);
|
if (mTmpRect.isEmpty()) {
|
// 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 stack) will give us the good bounds, as they would be used to
|
// display the dim layer.
|
final Task task = getTask();
|
if (task != null) {
|
task.getDimBounds(mTmpRect);
|
} else {
|
getStack().getDimBounds(mTmpRect);
|
}
|
}
|
if (inFreeformWindowingMode()) {
|
// For freeform windows we 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);
|
mTmpRect.inset(-delta, -delta);
|
}
|
region.set(mTmpRect);
|
cropRegionToStackBoundsIfNeeded(region);
|
subtractTouchExcludeRegionIfNeeded(region);
|
} else if (modal && mTapExcludeRegionHolder != null) {
|
final Region touchExcludeRegion = Region.obtain();
|
amendTapExcludeRegion(touchExcludeRegion);
|
if (!touchExcludeRegion.isEmpty()) {
|
// Remove touch modal because there are some areas that cannot be touched.
|
flags |= FLAG_NOT_TOUCH_MODAL;
|
// 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);
|
// Subtract the area that cannot be touched.
|
region.op(touchExcludeRegion, Region.Op.DIFFERENCE);
|
inputWindowHandle.setTouchableRegionCrop(null);
|
}
|
touchExcludeRegion.recycle();
|
} else {
|
// Not modal or full screen modal
|
getTouchableRegion(region);
|
}
|
// Translate to surface based coordinates.
|
region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
|
|
return flags;
|
}
|
|
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("checkPolicyVisibilityChange");
|
if (isFocused()) {
|
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
|
"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.
|
boolean hasTurnScreenOnFlag = (mAttrs.flags & FLAG_TURN_SCREEN_ON) != 0;
|
|
// The screen will turn on if the following conditions are met
|
// 1. The window has the flag FLAG_TURN_SCREEN_ON
|
// 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
|
// canTurnScreenOn 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 = mAppToken == null || mAppToken.canTurnScreenOn();
|
|
if (allowTheaterMode && canTurnScreenOn && !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 (mAppToken != null) {
|
mAppToken.setCanTurnScreenOn(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 (isDrawnLw() && 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;
|
}
|
|
void getMergedConfiguration(MergedConfiguration outConfiguration) {
|
final Configuration globalConfig = getProcessGlobalConfiguration();
|
final Configuration overrideConfig = getMergedOverrideConfiguration();
|
outConfiguration.setConfiguration(globalConfig, overrideConfig);
|
}
|
|
void setLastReportedMergedConfiguration(MergedConfiguration config) {
|
mLastReportedConfiguration.setTo(config);
|
mLastConfigReportedToClient = true;
|
}
|
|
void getLastReportedMergedConfiguration(MergedConfiguration config) {
|
config.setTo(mLastReportedConfiguration);
|
}
|
|
private Configuration getLastReportedConfiguration() {
|
return mLastReportedConfiguration.getMergedConfiguration();
|
}
|
|
void adjustStartingWindowFlags() {
|
if (mAttrs.type == TYPE_BASE_APPLICATION && mAppToken != null
|
&& mAppToken.startingWindow != 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 = mAppToken.startingWindow.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.mAppToken != null && win.mAppToken.findMainWindow() == win) {
|
mWmService.mTaskSnapshotController.onAppDied(win.mAppToken);
|
}
|
win.removeIfPossible(shouldKeepVisibleDeadAppWindow());
|
if (win.mAttrs.type == TYPE_DOCK_DIVIDER) {
|
// The owner of the docked divider died :( We reset the docked stack,
|
// just in case they have the divider at an unstable position. Better
|
// also reset drag resizing state, because the owner can't do it
|
// anymore.
|
final TaskStack stack =
|
dc.getSplitScreenPrimaryStackIgnoringVisibility();
|
if (stack != null) {
|
stack.resetDockedStackToMiddle();
|
}
|
resetSplitScreenResizing = true;
|
}
|
} 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() || mAppToken == null || mAppToken.isClientHidden()) {
|
// 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();
|
}
|
|
@Override
|
public boolean canReceiveKeys() {
|
return isVisibleOrAdding()
|
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
|
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
|
&& (mAppToken == null || mAppToken.windowsAreFocusable())
|
&& !cantReceiveTouchInput();
|
}
|
|
@Override
|
public boolean canShowWhenLocked() {
|
final boolean showBecauseOfActivity =
|
mAppToken != null && mAppToken.mActivityRecord.canShowWhenLocked();
|
final boolean showBecauseOfWindow = (getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0;
|
return showBecauseOfActivity || showBecauseOfWindow;
|
}
|
|
/** @return false if this window desires touch events. */
|
boolean cantReceiveTouchInput() {
|
return mAppToken != null && mAppToken.getTask() != null
|
&& (mAppToken.getTask().mStack.shouldIgnoreInput() || mAppToken.hiddenRequested);
|
}
|
|
@Override
|
public boolean hasDrawnLw() {
|
return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
|
}
|
|
@Override
|
public boolean showLw(boolean doAnimation) {
|
return showLw(doAnimation, true);
|
}
|
|
boolean showLw(boolean doAnimation, boolean requestAnim) {
|
if (isLegacyPolicyVisibility() && mLegacyPolicyVisibilityAfterAnim) {
|
// Already showing.
|
return false;
|
}
|
if (isHiddenFromUserLocked()) {
|
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());
|
if (!mToken.okToAnimate()) {
|
doAnimation = false;
|
} else if (isLegacyPolicyVisibility() && !isAnimating()) {
|
// 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;
|
}
|
|
@Override
|
public boolean hideLw(boolean doAnimation) {
|
return hideLw(doAnimation, true);
|
}
|
|
boolean hideLw(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()) {
|
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) {
|
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
|
"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 (mOwnerCanAddInternalSystemWindow
|
|| (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
|
return;
|
}
|
if (mForceHideNonSystemOverlayWindow == forceHide) {
|
return;
|
}
|
mForceHideNonSystemOverlayWindow = forceHide;
|
if (forceHide) {
|
hideLw(true /* doAnimation */, true /* requestAnim */);
|
} else {
|
showLw(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) {
|
hideLw(true, true);
|
} else {
|
showLw(true, true);
|
}
|
}
|
|
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.
|
showLw(true, true);
|
} else {
|
hideLw(true, true);
|
}
|
}
|
}
|
|
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);
|
if (mode != MODE_ALLOWED && mode != MODE_DEFAULT) {
|
setAppOpVisibilityLw(false);
|
}
|
}
|
|
void resetAppOpsState() {
|
if (mAppOp != OP_NONE && mAppOpVisibility) {
|
mWmService.mAppOps.finishOp(mAppOp, getOwningUid(), getOwningPackage());
|
}
|
}
|
|
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);
|
setAppOpVisibilityLw(false);
|
}
|
} else {
|
final int mode = mWmService.mAppOps.startOpNoThrow(mAppOp, uid, packageName, true);
|
if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
|
setAppOpVisibilityLw(true);
|
}
|
}
|
}
|
|
public void hidePermanentlyLw() {
|
if (!mPermanentlyHidden) {
|
mPermanentlyHidden = true;
|
hideLw(true, true);
|
}
|
}
|
|
public void pokeDrawLockLw(long timeout) {
|
if (isVisibleOrAdding()) {
|
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);
|
}
|
}
|
|
@Override
|
public boolean isAlive() {
|
return mClient.asBinder().isBinderAlive();
|
}
|
|
boolean isClosing() {
|
return mAnimatingExit || (mAppToken != null && mAppToken.isClosingOrEnteringPip());
|
}
|
|
void addWinAnimatorToList(ArrayList<WindowStateAnimator> 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 clientHidden = mAppToken.isClientHidden();
|
if (mAttrs.type == TYPE_APPLICATION_STARTING && clientHidden) {
|
// Don't hide the starting window.
|
return;
|
}
|
|
if (clientHidden) {
|
// Once we are notifying the client that it's visibility has changed, we need to prevent
|
// it from destroying child surfaces until the animation has finished. We do this by
|
// detaching any surface control the client added from the client.
|
for (int i = mChildren.size() - 1; i >= 0; --i) {
|
final WindowState c = mChildren.get(i);
|
c.mWinAnimator.detachChildren();
|
}
|
|
mWinAnimator.detachChildren();
|
}
|
|
try {
|
if (DEBUG_VISIBILITY) Slog.v(TAG,
|
"Setting visibility of " + this + ": " + (!clientHidden));
|
mClient.dispatchAppVisibility(!clientHidden);
|
} 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) {
|
if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "set mOrientationChanging of " + this);
|
setOrientationChanging(true);
|
mWmService.mRoot.mOrientationChangeComplete = false;
|
}
|
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<WindowState> 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 (appStopped || mWindowRemovalAllowed) {
|
mWinAnimator.destroyPreservedSurfaceLocked();
|
}
|
|
if (mDestroying) {
|
if (DEBUG_ADD_REMOVE) Slog.e(TAG_WM, "win=" + this
|
+ " destroySurfaces: appStopped=" + appStopped
|
+ " win.mWindowRemovalAllowed=" + mWindowRemovalAllowed
|
+ " win.mRemoveOnExit=" + mRemoveOnExit);
|
if (!cleanupOnResume || mRemoveOnExit) {
|
destroySurfaceUnchecked();
|
}
|
if (mRemoveOnExit) {
|
removeImmediately();
|
}
|
if (cleanupOnResume) {
|
requestUpdateWallpaperIfNeeded();
|
}
|
mDestroying = false;
|
destroyedSomething = true;
|
|
// Since mDestroying will affect AppWindowToken#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(mAppToken)) {
|
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();
|
|
// 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;
|
}
|
|
@Override
|
public boolean isDefaultDisplay() {
|
final DisplayContent displayContent = getDisplayContent();
|
if (displayContent == null) {
|
// Only a window that was on a non-default display can be detached from it.
|
return false;
|
}
|
return displayContent.isDefaultDisplay;
|
}
|
|
void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
|
mShowToOwnerOnly = showToOwnerOnly;
|
}
|
|
private boolean isHiddenFromUserLocked() {
|
// Child windows are evaluated based on their parent window.
|
final WindowState win = getTopParentWindow();
|
if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
|
&& win.mAppToken != null && win.mAppToken.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.getFrameLw().left <= win.getDisplayFrameLw().left
|
&& win.getFrameLw().top <= win.getDisplayFrameLw().top
|
&& win.getFrameLw().right >= win.getStableFrameLw().right
|
&& win.getFrameLw().bottom >= win.getStableFrameLw().bottom) {
|
// Is a fullscreen window, like the clock alarm. Show to everyone.
|
return false;
|
}
|
}
|
|
return win.mShowToOwnerOnly
|
&& !mWmService.isCurrentProfileLocked(UserHandle.getUserId(win.mOwnerUid));
|
}
|
|
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);
|
outRegion.translate(frame.left, frame.top);
|
break;
|
}
|
}
|
cropRegionToStackBoundsIfNeeded(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 boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
|
final DisplayContent dc = getDisplayContent();
|
|
if (modal && dc != null) {
|
outRegion.set(dc.getBounds());
|
cropRegionToStackBoundsIfNeeded(outRegion);
|
subtractTouchExcludeRegionIfNeeded(outRegion);
|
} else {
|
getTouchableRegion(outRegion);
|
}
|
}
|
|
private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) {
|
final Task task = getTask();
|
if (task == null || !task.cropWindowsToStackBounds()) {
|
return;
|
}
|
|
final TaskStack stack = task.mStack;
|
if (stack == null) {
|
return;
|
}
|
|
handle.setTouchableRegionCrop(stack.getSurfaceControl());
|
}
|
|
private void cropRegionToStackBoundsIfNeeded(Region region) {
|
final Task task = getTask();
|
if (task == null || !task.cropWindowsToStackBounds()) {
|
return;
|
}
|
|
final TaskStack stack = task.mStack;
|
if (stack == null) {
|
return;
|
}
|
|
stack.getDimBounds(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 (mTapExcludeRegionHolder == null) {
|
return;
|
}
|
final Region touchExcludeRegion = Region.obtain();
|
amendTapExcludeRegion(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, boolean inTouchMode) {
|
try {
|
mClient.windowFocusChanged(focused, inTouchMode);
|
} catch (RemoteException e) {
|
}
|
if (mFocusCallbacks != null) {
|
final int N = mFocusCallbacks.beginBroadcast();
|
for (int i=0; i<N; i++) {
|
IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
|
try {
|
if (focused) {
|
obs.focusGained(mWindowId.asBinder());
|
} else {
|
obs.focusLost(mWindowId.asBinder());
|
}
|
} catch (RemoteException e) {
|
}
|
}
|
mFocusCallbacks.finishBroadcast();
|
}
|
}
|
|
@Override
|
public Configuration getConfiguration() {
|
if (mAppToken != null && mAppToken.mFrozenMergedConfig.size() > 0) {
|
return mAppToken.mFrozenMergedConfig.peek();
|
}
|
|
// If the process has not registered to any display to listen to the configuration change,
|
// we can simply return the mFullConfiguration as default.
|
if (!registeredForDisplayConfigChanges()) {
|
return super.getConfiguration();
|
}
|
|
// We use the process config this window is associated with as the based global config since
|
// the process can override its config, but isn't part of the window hierarchy.
|
mTempConfiguration.setTo(getProcessGlobalConfiguration());
|
mTempConfiguration.updateFrom(getMergedOverrideConfiguration());
|
return mTempConfiguration;
|
}
|
|
/** @return {@code true} if the process registered to a display as a config listener. */
|
private boolean registeredForDisplayConfigChanges() {
|
final WindowState parentWindow = getParentWindow();
|
final Session session = parentWindow != null ? parentWindow.mSession : mSession;
|
// System process or invalid process cannot register to display config change.
|
if (session.mPid == MY_PID || session.mPid < 0) return false;
|
WindowProcessController app =
|
mWmService.mAtmService.getProcessController(session.mPid, session.mUid);
|
if (app == null || !app.registeredForDisplayConfigChanges()) return false;
|
return true;
|
}
|
|
void reportResized() {
|
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wm.reportResized_" + getWindowTag());
|
try {
|
if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this
|
+ ": " + mWindowFrames.mCompatFrame);
|
final MergedConfiguration mergedConfiguration =
|
new MergedConfiguration(mWmService.mRoot.getConfiguration(),
|
getMergedOverrideConfiguration());
|
|
setLastReportedMergedConfiguration(mergedConfiguration);
|
|
if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
|
Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
|
|
final Rect frame = mWindowFrames.mCompatFrame;
|
final Rect overscanInsets = mWindowFrames.mLastOverscanInsets;
|
final Rect contentInsets = mWindowFrames.mLastContentInsets;
|
final Rect visibleInsets = mWindowFrames.mLastVisibleInsets;
|
final Rect stableInsets = mWindowFrames.mLastStableInsets;
|
final Rect outsets = mWindowFrames.mLastOutsets;
|
final boolean reportDraw = mWinAnimator.mDrawState == DRAW_PENDING;
|
final boolean reportOrientation = mReportOrientationChanged;
|
final int displayId = getDisplayId();
|
final DisplayCutout displayCutout = getWmDisplayCutout().getDisplayCutout();
|
if (mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
|
&& mClient instanceof IWindow.Stub) {
|
// To prevent deadlock simulate one-way call if win.mClient is a local object.
|
mWmService.mH.post(new Runnable() {
|
@Override
|
public void run() {
|
try {
|
dispatchResized(frame, overscanInsets, contentInsets, visibleInsets,
|
stableInsets, outsets, reportDraw, mergedConfiguration,
|
reportOrientation, displayId, displayCutout);
|
} catch (RemoteException e) {
|
// Not a remote call, RemoteException won't be raised.
|
}
|
}
|
});
|
} else {
|
dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets,
|
outsets, reportDraw, mergedConfiguration, reportOrientation, displayId,
|
displayCutout);
|
}
|
|
//TODO (multidisplay): Accessibility supported only for the default display.
|
if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
|
|| getDisplayContent().getParentWindow() != null)) {
|
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
|
}
|
|
mWindowFrames.resetInsetsChanged();
|
mWinAnimator.mSurfaceResized = false;
|
mReportOrientationChanged = false;
|
} catch (RemoteException e) {
|
setOrientationChanging(false);
|
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
|
- mWmService.mDisplayFreezeTime);
|
// We are assuming the hosting process is dead or in a zombie state.
|
Slog.w(TAG, "Failed to report 'resized' to the client of " + this
|
+ ", removing this window.");
|
mWmService.mPendingRemove.add(this);
|
mWmService.mWindowPlacerLocked.requestTraversal();
|
}
|
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
}
|
|
/**
|
* Called when the insets state changed.
|
*/
|
void notifyInsetsChanged() {
|
try {
|
mClient.insetsChanged(
|
getDisplayContent().getInsetsStateController().getInsetsForDispatch(this));
|
} catch (RemoteException e) {
|
Slog.w(TAG, "Failed to deliver inset state change", e);
|
}
|
}
|
|
void notifyInsetsControlChanged() {
|
final InsetsStateController stateController =
|
getDisplayContent().getInsetsStateController();
|
try {
|
mClient.insetsControlChanged(stateController.getInsetsForDispatch(this),
|
stateController.getControlsForDispatch(this));
|
} catch (RemoteException e) {
|
Slog.w(TAG, "Failed to deliver inset state change", e);
|
}
|
}
|
|
Rect getBackdropFrame(Rect frame) {
|
// When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
|
// start even if we haven't received the relayout window, so that the client requests
|
// the relayout sooner. When dragging stops, backDropFrame needs to stay fullscreen
|
// until the window to small size, otherwise the multithread renderer will shift last
|
// one or more frame to wrong offset. So here we send fullscreen backdrop if either
|
// isDragResizing() or isDragResizeChanged() is true.
|
boolean resizing = isDragResizing() || isDragResizeChanged();
|
if (getWindowConfiguration().useWindowFrameForBackdrop() || !resizing) {
|
// Surface position is now inherited from parent, and BackdropFrameRenderer uses
|
// backdrop frame to position content. Thus we just keep the size of backdrop frame, and
|
// remove the offset to avoid double offset from display origin.
|
mTmpRect.set(frame);
|
mTmpRect.offsetTo(0, 0);
|
return mTmpRect;
|
}
|
final DisplayInfo displayInfo = getDisplayInfo();
|
mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
|
return mTmpRect;
|
}
|
|
private int getStackId() {
|
final TaskStack stack = getStack();
|
if (stack == null) {
|
return INVALID_STACK_ID;
|
}
|
return stack.mStackId;
|
}
|
|
private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
|
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
|
MergedConfiguration mergedConfiguration, boolean reportOrientation, int displayId,
|
DisplayCutout displayCutout)
|
throws RemoteException {
|
final boolean forceRelayout = isDragResizeChanged() || reportOrientation;
|
|
mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
|
reportDraw, mergedConfiguration, getBackdropFrame(frame), forceRelayout,
|
getDisplayContent().getDisplayPolicy().areSystemBarsForcedShownLw(this), displayId,
|
new DisplayCutout.ParcelableWrapper(displayCutout));
|
mDragResizingChangeReported = true;
|
}
|
|
public void registerFocusObserver(IWindowFocusObserver observer) {
|
synchronized (mWmService.mGlobalLock) {
|
if (mFocusCallbacks == null) {
|
mFocusCallbacks = new RemoteCallbackList<IWindowFocusObserver>();
|
}
|
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 mAppToken == null || (mAppToken.matchParentBounds() && !inMultiWindowMode());
|
}
|
|
/** @return true when the window is in fullscreen mode, but has non-fullscreen bounds set, or
|
* is transitioning into/out-of fullscreen. */
|
boolean isLetterboxedAppWindow() {
|
return !inMultiWindowMode() && !matchesDisplayBounds()
|
|| isLetterboxedForDisplayCutoutLw();
|
}
|
|
@Override
|
public boolean isLetterboxedForDisplayCutoutLw() {
|
if (mAppToken == null) {
|
// Only windows with an AppWindowToken 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(mAppToken.getBounds());
|
mTmpRect.intersectUnchecked(mWindowFrames.mFrame);
|
return mAppToken.getBounds().equals(mTmpRect);
|
}
|
|
@Override
|
public boolean isLetterboxedOverlappingWith(Rect rect) {
|
return mAppToken != null && mAppToken.isLetterboxOverlappingWith(rect);
|
}
|
|
boolean isDragResizeChanged() {
|
return mDragResizing != computeDragResizing();
|
}
|
|
@Override
|
void setWaitingForDrawnIfResizingChanged() {
|
if (isDragResizeChanged()) {
|
mWmService.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()) {
|
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;
|
}
|
|
// If the bounds are currently frozen, it means that the layout size that the app sees
|
// and the bounds we clip this window to might be different. In order to avoid holes, we
|
// simulate that we are still resizing so the app fills the hole with the resizing
|
// background.
|
return (getDisplayContent().mDividerControllerLocked.isResizing()
|
|| mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
|
!task.inFreeformWindowingMode() && !isGoneForLayoutLw();
|
|
}
|
|
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 writeToProto(ProtoOutputStream proto, long fieldId,
|
@WindowTraceLogLevel int logLevel) {
|
boolean isVisible = isVisible();
|
if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
|
return;
|
}
|
|
final long token = proto.start(fieldId);
|
super.writeToProto(proto, WINDOW_CONTAINER, logLevel);
|
writeIdentifierToProto(proto, IDENTIFIER);
|
proto.write(DISPLAY_ID, getDisplayId());
|
proto.write(STACK_ID, getStackId());
|
mAttrs.writeToProto(proto, ATTRIBUTES);
|
mGivenContentInsets.writeToProto(proto, GIVEN_CONTENT_INSETS);
|
mWindowFrames.writeToProto(proto, WINDOW_FRAMES);
|
mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS);
|
mSurfacePosition.writeToProto(proto, SURFACE_POSITION);
|
mWinAnimator.writeToProto(proto, ANIMATOR);
|
proto.write(ANIMATING_EXIT, mAnimatingExit);
|
for (int i = 0; i < mChildren.size(); i++) {
|
mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, logLevel);
|
}
|
proto.write(REQUESTED_WIDTH, mRequestedWidth);
|
proto.write(REQUESTED_HEIGHT, mRequestedHeight);
|
proto.write(VIEW_VISIBILITY, mViewVisibility);
|
proto.write(SYSTEM_UI_VISIBILITY, mSystemUiVisibility);
|
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.end(token);
|
}
|
|
@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, UserHandle.getUserId(mOwnerUid));
|
final CharSequence title = getWindowTag();
|
if (title != null) {
|
proto.write(TITLE, title.toString());
|
}
|
proto.end(token);
|
}
|
|
@Override
|
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
|
final TaskStack stack = getStack();
|
pw.print(prefix + "mDisplayId=" + getDisplayId());
|
if (stack != null) {
|
pw.print(" stackId=" + stack.mStackId);
|
}
|
pw.println(" mSession=" + mSession
|
+ " mClient=" + mClient.asBinder());
|
pw.println(prefix + "mOwnerUid=" + mOwnerUid
|
+ " mShowToOwnerOnly=" + mShowToOwnerOnly
|
+ " 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
|
+ " mWallpaperVisible=" + mWallpaperVisible);
|
}
|
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 (mAppToken != null) {
|
pw.println(prefix + "mAppToken=" + mAppToken);
|
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);
|
pw.println(prefix + "mSeq=" + mSeq
|
+ " mSystemUiVisibility=0x" + Integer.toHexString(mSystemUiVisibility));
|
}
|
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 (inSizeCompatMode()) {
|
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 (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());
|
}
|
|
@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" + UserHandle.getUserId(mOwnerUid)
|
+ " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
|
}
|
return mStringNameCache;
|
}
|
|
void transformClipRectFromScreenToSurfaceSpace(Rect clipRect) {
|
if (mHScale == 1 && mVScale == 1) {
|
return;
|
}
|
if (mHScale >= 0) {
|
clipRect.left = (int) (clipRect.left / mHScale);
|
clipRect.right = (int) Math.ceil(clipRect.right / mHScale);
|
}
|
if (mVScale >= 0) {
|
clipRect.top = (int) (clipRect.top / mVScale);
|
clipRect.bottom = (int) Math.ceil(clipRect.bottom / mVScale);
|
}
|
}
|
|
private void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
|
final int pw = containingFrame.width();
|
final int ph = containingFrame.height();
|
final Task task = getTask();
|
final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
|
final boolean noLimits = (mAttrs.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)
|
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
|
float x, y;
|
int w,h;
|
|
final boolean inSizeCompatMode = inSizeCompatMode();
|
if ((mAttrs.flags & FLAG_SCALED) != 0) {
|
if (mAttrs.width < 0) {
|
w = pw;
|
} else if (inSizeCompatMode) {
|
w = (int)(mAttrs.width * mGlobalScale + .5f);
|
} else {
|
w = mAttrs.width;
|
}
|
if (mAttrs.height < 0) {
|
h = ph;
|
} else if (inSizeCompatMode) {
|
h = (int)(mAttrs.height * mGlobalScale + .5f);
|
} else {
|
h = mAttrs.height;
|
}
|
} else {
|
if (mAttrs.width == MATCH_PARENT) {
|
w = pw;
|
} else if (inSizeCompatMode) {
|
w = (int)(mRequestedWidth * mGlobalScale + .5f);
|
} else {
|
w = mRequestedWidth;
|
}
|
if (mAttrs.height == MATCH_PARENT) {
|
h = ph;
|
} else if (inSizeCompatMode) {
|
h = (int)(mRequestedHeight * mGlobalScale + .5f);
|
} else {
|
h = mRequestedHeight;
|
}
|
}
|
|
if (inSizeCompatMode) {
|
x = mAttrs.x * mGlobalScale;
|
y = mAttrs.y * mGlobalScale;
|
} else {
|
x = mAttrs.x;
|
y = mAttrs.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);
|
}
|
|
// Set mFrame
|
Gravity.apply(mAttrs.gravity, w, h, containingFrame,
|
(int) (x + mAttrs.horizontalMargin * pw),
|
(int) (y + mAttrs.verticalMargin * ph), mWindowFrames.mFrame);
|
|
// Now make sure the window fits in the overall display frame.
|
if (fitToDisplay) {
|
Gravity.applyDisplay(mAttrs.gravity, displayFrame, mWindowFrames.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.
|
mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);
|
if (inSizeCompatMode) {
|
// See comparable block in computeFrameLw.
|
mWindowFrames.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.isGoneForLayoutLw();
|
}
|
|
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 && (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
|
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 (inSizeCompatMode()) {
|
winX *= mGlobalScale;
|
}
|
return winX;
|
}
|
|
float translateToWindowY(float y) {
|
float winY = y - mWindowFrames.mFrame.top;
|
if (inSizeCompatMode()) {
|
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;
|
}
|
|
@Override
|
public int getRotationAnimationHint() {
|
if (mAppToken != null) {
|
return mAppToken.mRotationAnimationHint;
|
} else {
|
return -1;
|
}
|
}
|
|
@Override
|
public boolean isInputMethodWindow() {
|
return mIsImWindow;
|
}
|
|
// This must be called while inside a transaction.
|
boolean performShowLocked() {
|
if (isHiddenFromUserLocked()) {
|
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)
|
&& mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) {
|
mAppToken.onFirstWindowDrawn(this, mWinAnimator);
|
}
|
|
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();
|
}
|
}
|
}
|
}
|
|
if (mAttrs.type == TYPE_INPUT_METHOD) {
|
getDisplayContent().mDividerControllerLocked.resetImeHideRequested();
|
}
|
|
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.hiddenRequested="
|
+ (mAppToken != null && mAppToken.hiddenRequested)
|
+ " tok.hidden=" + (mAppToken != null && mAppToken.isHidden())
|
+ " animating=" + isAnimating()
|
+ " tok animating="
|
+ (mAppToken != null && mAppToken.isSelfAnimating())
|
+ " Callers=" + Debug.getCallers(4));
|
}
|
}
|
|
WindowInfo getWindowInfo() {
|
WindowInfo windowInfo = WindowInfo.obtain();
|
windowInfo.type = mAttrs.type;
|
windowInfo.layer = mLayer;
|
windowInfo.token = mClient.asBinder();
|
if (mAppToken != null) {
|
windowInfo.activityToken = mAppToken.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.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<WindowState> 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<WindowState> 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<WindowState> 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<WindowState> callback,
|
boolean traverseTopToBottom) {
|
// If this window is the current IME target, so we need to process the IME windows
|
// directly above it. The exception is if we are in split screen
|
// in which case we process the IME at the DisplayContent level to
|
// ensure it is above the docked divider.
|
if (isInputMethodTarget() && !inSplitScreenWindowingMode()) {
|
if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> 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<WindowState> 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;
|
}
|
|
void onExitAnimationDone() {
|
if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
|
+ ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
|
+ " selfAnimating=" + isSelfAnimating());
|
|
if (!mChildren.isEmpty()) {
|
// Copying to a different list as multiple children can be removed.
|
final ArrayList<WindowState> 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 (mAppToken == null) {
|
try {
|
mClient.dispatchWindowShown();
|
} catch (RemoteException e) {
|
}
|
}
|
}
|
|
if (isSelfAnimating()) {
|
return;
|
}
|
|
//TODO (multidisplay): Accessibility is supported only for the default display.
|
if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
|
|| getDisplayContent().getParentWindow() != null)) {
|
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
|
}
|
|
if (!isSelfOrAncestorWindowAnimatingExit()) {
|
return;
|
}
|
|
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG,
|
"Exit animation finished in " + this + ": remove=" + 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 (mAppToken != null) {
|
mAppToken.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 hideWallpaperWindow(boolean wasDeferred, String reason) {
|
for (int j = mChildren.size() - 1; j >= 0; --j) {
|
final WindowState c = mChildren.get(j);
|
c.hideWallpaperWindow(wasDeferred, reason);
|
}
|
if (!mWinAnimator.mLastHidden || wasDeferred) {
|
mWinAnimator.hide(reason);
|
getDisplayContent().mWallpaperController.mDeferredHideWallpaper = null;
|
dispatchWallpaperVisibility(false);
|
final DisplayContent displayContent = getDisplayContent();
|
if (displayContent != null) {
|
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
|
if (DEBUG_LAYOUT_REPEATS) {
|
mWmService.mWindowPlacerLocked.debugLayoutRepeats("hideWallpaperWindow " + this,
|
displayContent.pendingLayoutChanges);
|
}
|
}
|
}
|
}
|
|
/**
|
* Check wallpaper window for visibility change and notify window if so.
|
* @param visible Current visibility.
|
*/
|
void dispatchWallpaperVisibility(final boolean visible) {
|
final boolean hideAllowed =
|
getDisplayContent().mWallpaperController.mDeferredHideWallpaper == null;
|
|
// Only send notification if the visibility actually changed and we are not trying to hide
|
// the wallpaper when we are deferring hiding of the wallpaper.
|
if (mWallpaperVisible != visible && (hideAllowed || visible)) {
|
mWallpaperVisible = visible;
|
try {
|
if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
|
"Updating vis of wallpaper " + this
|
+ ": " + visible + " from:\n" + Debug.getCallers(4, " "));
|
mClient.dispatchAppVisibility(visible);
|
} catch (RemoteException e) {
|
}
|
}
|
}
|
|
boolean hasVisibleNotDrawnWallpaper() {
|
if (mWallpaperVisible && !isDrawnLw()) {
|
return true;
|
}
|
for (int j = mChildren.size() - 1; j >= 0; --j) {
|
final WindowState c = mChildren.get(j);
|
if (c.hasVisibleNotDrawnWallpaper()) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
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=" + isDrawnLw()
|
+ ", animating=" + isAnimating());
|
if (!isDrawnLw()) {
|
Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
|
+ " pv=" + isVisibleByPolicy()
|
+ " mDrawState=" + mWinAnimator.mDrawState
|
+ " ph=" + isParentWindowHidden()
|
+ " th=" + (mAppToken != null ? mAppToken.hiddenRequested : false)
|
+ " a=" + isAnimating());
|
}
|
}
|
|
results.numInteresting++;
|
if (isDrawnLw()) {
|
results.numDrawn++;
|
if (!isAnimating()) {
|
results.numVisible++;
|
}
|
results.nowGone = false;
|
} else if (isAnimating()) {
|
results.nowGone = false;
|
}
|
}
|
|
private boolean skipDecorCrop() {
|
// The decor frame is used to specify the region not covered by the system
|
// decorations (nav bar, status bar). In case this is empty, for example with
|
// FLAG_TRANSLUCENT_NAVIGATION, we don't need to do any cropping.
|
if (mWindowFrames.mDecorFrame.isEmpty()) {
|
return true;
|
}
|
|
// But if we have a frame, and are an application window, then we must be cropped.
|
if (mAppToken != null) {
|
return false;
|
}
|
|
// For non application windows, we may be allowed to extend over the decor bars
|
// depending on our type and permissions assosciated with our token.
|
return mToken.canLayerAboveSystemBars();
|
}
|
|
/**
|
* Calculate the window crop according to system decor policy. In general this is
|
* the system decor rect (see #calculateSystemDecorRect), but we also have some
|
* special cases. This rectangle is in screen space.
|
*/
|
void calculatePolicyCrop(Rect policyCrop) {
|
final DisplayContent displayContent = getDisplayContent();
|
|
if (!displayContent.isDefaultDisplay && !displayContent.supportsSystemDecorations()) {
|
// On a different display there is no system decor. Crop the window
|
// by the screen boundaries.
|
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
|
policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(),
|
mWindowFrames.mCompatFrame.height());
|
policyCrop.intersect(-mWindowFrames.mCompatFrame.left, -mWindowFrames.mCompatFrame.top,
|
displayInfo.logicalWidth - mWindowFrames.mCompatFrame.left,
|
displayInfo.logicalHeight - mWindowFrames.mCompatFrame.top);
|
} else if (skipDecorCrop()) {
|
// Windows without policy decor aren't cropped.
|
policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(),
|
mWindowFrames.mCompatFrame.height());
|
} else {
|
// Crop to the system decor specified by policy.
|
calculateSystemDecorRect(policyCrop);
|
}
|
}
|
|
/**
|
* The system decor rect is the region of the window which is not covered
|
* by system decorations.
|
*/
|
private void calculateSystemDecorRect(Rect systemDecorRect) {
|
final Rect decorRect = mWindowFrames.mDecorFrame;
|
final int width = mWindowFrames.mFrame.width();
|
final int height = mWindowFrames.mFrame.height();
|
|
final int left = mWindowFrames.mFrame.left;
|
final int top = mWindowFrames.mFrame.top;
|
|
// Initialize the decor rect to the entire frame.
|
if (isDockedResizing()) {
|
// If we are resizing with the divider, the task bounds might be smaller than the
|
// stack bounds. The system decor is used to clip to the task bounds, which we don't
|
// want in this case in order to avoid holes.
|
//
|
// We take care to not shrink the width, for surfaces which are larger than
|
// the display region. Of course this area will not eventually be visible
|
// but if we truncate the width now, we will calculate incorrectly
|
// when adjusting to the stack bounds.
|
final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
|
systemDecorRect.set(0, 0,
|
Math.max(width, displayInfo.logicalWidth),
|
Math.max(height, displayInfo.logicalHeight));
|
} else {
|
systemDecorRect.set(0, 0, width, height);
|
}
|
|
// If a freeform window is animating from a position where it would be cutoff, it would be
|
// cutoff during the animation. We don't want that, so for the duration of the animation
|
// we ignore the decor cropping and depend on layering to position windows correctly.
|
|
// We also ignore cropping when the window is currently being drag resized in split screen
|
// to prevent issues with the crop for screenshot.
|
final boolean cropToDecor =
|
!(inFreeformWindowingMode() && isAnimatingLw()) && !isDockedResizing();
|
if (cropToDecor) {
|
// Intersect with the decor rect, offsetted by window position.
|
systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
|
decorRect.right - left, decorRect.bottom - top);
|
}
|
|
// If size compatibility is being applied to the window, the
|
// surface is scaled relative to the screen. Also apply this
|
// scaling to the crop rect. We aren't using the standard rect
|
// scale function because we want to round things to make the crop
|
// always round to a larger rect to ensure we don't crop too
|
// much and hide part of the window that should be seen.
|
if (mInvGlobalScale != 1.0f && inSizeCompatMode()) {
|
final float scale = mInvGlobalScale;
|
systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f);
|
systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f);
|
systemDecorRect.right = (int) ((systemDecorRect.right + 1) * scale - 0.5f);
|
systemDecorRect.bottom = (int) ((systemDecorRect.bottom + 1) * scale - 0.5f);
|
}
|
|
}
|
|
/**
|
* 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, int attrChanges) {
|
final boolean wasVisible = isVisibleLw();
|
|
result |= (!wasVisible || !isDrawnLw()) ? 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 (isSelfAnimating()) {
|
cancelAnimation();
|
destroySurfaceUnchecked();
|
}
|
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 ((attrChanges & FORMAT_CHANGED) != 0) {
|
// If the format can't be changed in place, preserve the old surface until the app draws
|
// on the new one. This prevents blinking when we change elevation of freeform and
|
// pinned windows.
|
if (!mWinAnimator.tryChangeFormatInPlaceLocked()) {
|
mWinAnimator.preserveSurfaceLocked();
|
result |= RELAYOUT_RES_SURFACE_CHANGED
|
| RELAYOUT_RES_FIRST_TIME;
|
}
|
}
|
|
// When we change the Surface size, in scenarios which may require changing
|
// the surface position in sync with the resize, we use a preserved surface
|
// so we can freeze it while waiting for the client to report draw on the newly
|
// sized surface. At the moment this logic is only in place for switching
|
// in and out of the big surface for split screen resize.
|
if (isDragResizeChanged()) {
|
setDragResizing();
|
// We can only change top level windows to the full-screen surface when
|
// resizing (as we only have one full-screen surface). So there is no need
|
// to preserve and destroy windows which are attached to another, they
|
// will keep their surface and its size may change over time.
|
if (mHasSurface && !isChildWindow()) {
|
mWinAnimator.preserveSurfaceLocked();
|
result |= RELAYOUT_RES_SURFACE_CHANGED |
|
RELAYOUT_RES_FIRST_TIME;
|
}
|
}
|
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;
|
}
|
|
/**
|
* Updates the last inset values to the current ones.
|
*/
|
void updateLastInsetValues() {
|
mWindowFrames.updateLastInsetValues();
|
}
|
|
void startAnimation(Animation anim) {
|
|
// If we are an inset provider, all our animations are driven by the inset client.
|
if (mInsetProvider != null && mInsetProvider.isControllable()) {
|
return;
|
}
|
|
final DisplayInfo displayInfo = getDisplayContent().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 (mInsetProvider != null && mInsetProvider.isControllable()) {
|
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);
|
}
|
|
@Override
|
protected void onAnimationFinished() {
|
super.onAnimationFinished();
|
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] = mWinAnimator.mDsDx;
|
float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
|
float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
|
float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
|
int x = mSurfacePosition.x;
|
int y = mSurfacePosition.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 - displayParent.mAttrs.surfaceInsets.left
|
+ (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
|
y += displayParent.mWindowFrames.mFrame.top - displayParent.mAttrs.surfaceInsets.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 AppWindowToken needs to access without having
|
// access to its windows children. Need to investigate re-writing
|
// {@link AppWindowToken#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<WindowState> 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_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;
|
}
|
return true;
|
}
|
|
@Override
|
SurfaceSession getSession() {
|
if (mSession.mSurfaceSession != null) {
|
return mSession.mSurfaceSession;
|
} else {
|
return getParent().getSession();
|
}
|
}
|
|
@Override
|
boolean needsZBoost() {
|
final WindowState inputMethodTarget = getDisplayContent().mInputMethodTarget;
|
if (mIsImWindow && inputMethodTarget != null) {
|
final AppWindowToken appToken = inputMethodTarget.mAppToken;
|
if (appToken != null) {
|
return appToken.needsZBoost();
|
}
|
}
|
return mWillReplaceWindow;
|
}
|
|
private void applyDims(Dimmer dimmer) {
|
if (!mAnimatingExit && mAppDied) {
|
mIsDimming = true;
|
dimmer.dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
|
} else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 && isVisibleNow() && !mHidden) {
|
// Only show a dim behind when the following is satisfied:
|
// 1. The window has the flag FLAG_DIM_BEHIND
|
// 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;
|
dimmer.dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
|
}
|
}
|
|
@Override
|
void prepareSurfaces() {
|
final Dimmer dimmer = getDimmer();
|
mIsDimming = false;
|
if (dimmer != null) {
|
applyDims(dimmer);
|
}
|
updateSurfacePosition();
|
|
mWinAnimator.prepareSurfaceLocked(true);
|
super.prepareSurfaces();
|
}
|
|
@Override
|
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
|
super.onAnimationLeashCreated(t, leash);
|
|
// Leash is now responsible for position, so set our position to 0.
|
t.setPosition(mSurfaceControl, 0, 0);
|
mLastSurfacePosition.set(0, 0);
|
}
|
|
@Override
|
public void onAnimationLeashLost(Transaction t) {
|
super.onAnimationLeashLost(t);
|
updateSurfacePosition(t);
|
}
|
|
@Override
|
void updateSurfacePosition() {
|
updateSurfacePosition(getPendingTransaction());
|
}
|
|
@VisibleForTesting
|
void updateSurfacePosition(Transaction t) {
|
if (mSurfaceControl == null) {
|
return;
|
}
|
|
transformFrameToSurfacePosition(mWindowFrames.mFrame.left, mWindowFrames.mFrame.top,
|
mSurfacePosition);
|
|
// 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)) {
|
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
|
mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
|
if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
|
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
|
t.deferTransactionUntil(mSurfaceControl,
|
mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
|
getFrameNumber());
|
}
|
}
|
}
|
|
private 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 = parentWindowContainer.getDisplayedBounds();
|
outPoint.offset(-parentBounds.left, -parentBounds.top);
|
}
|
|
TaskStack stack = getStack();
|
|
// If we have stack 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 (stack != null) {
|
final int outset = stack.getStackOutset();
|
outPoint.offset(outset, outset);
|
}
|
|
// Expand for surface insets. See WindowState.expandForSurfaceInsets.
|
transformSurfaceInsetsPosition(mTmpPoint, mAttrs.surfaceInsets);
|
outPoint.offset(-mTmpPoint.x, -mTmpPoint.y);
|
}
|
|
/**
|
* 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 (!inSizeCompatMode()) {
|
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 only use the relative layering mode in split screen, as part of elevating the IME
|
// and windows above it's target above the docked divider.
|
if (!inSplitScreenWindowingMode()) {
|
return false;
|
}
|
|
if (isChildWindow()) {
|
// If we are a child of the input method target we need this promotion.
|
if (getParentWindow().isInputMethodTarget()) {
|
return true;
|
}
|
} else if (mAppToken != 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 = getDisplayContent().mInputMethodTarget;
|
boolean inTokenWithAndAboveImeTarget = imeTarget != null && imeTarget != this
|
&& imeTarget.mToken == mToken && imeTarget.compareTo(this) <= 0;
|
return inTokenWithAndAboveImeTarget;
|
}
|
return false;
|
}
|
|
@Override
|
void assignLayer(Transaction t, int layer) {
|
// See comment in assignRelativeLayerForImeTargetChild
|
if (needsRelativeLayeringToIme()) {
|
getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
|
return;
|
}
|
super.assignLayer(t, layer);
|
}
|
|
@Override
|
public boolean isDimming() {
|
return mIsDimming;
|
}
|
|
// 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) {
|
w.assignLayer(t, -2);
|
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
|
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(int regionId, Region region) {
|
final DisplayContent currentDisplay = getDisplayContent();
|
if (currentDisplay == null) {
|
throw new IllegalStateException("Trying to update window not attached to any display.");
|
}
|
|
if (mTapExcludeRegionHolder == null) {
|
mTapExcludeRegionHolder = new TapExcludeRegionHolder();
|
|
// Make sure that this window is registered as one that provides a tap exclude region
|
// for its containing display.
|
currentDisplay.mTapExcludeProvidingWindows.add(this);
|
}
|
|
mTapExcludeRegionHolder.updateRegion(regionId, region);
|
// Trigger touch exclude region update on current display.
|
currentDisplay.updateTouchExcludeRegion();
|
// Trigger touchable region update for this window.
|
currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */);
|
}
|
|
/**
|
* Union the region with current tap exclude region that this window provides.
|
*
|
* @param region The region to be amended. It is on the screen coordinates.
|
*/
|
void amendTapExcludeRegion(Region region) {
|
final Region tempRegion = Region.obtain();
|
mTmpRect.set(mWindowFrames.mFrame);
|
mTmpRect.offsetTo(0, 0);
|
mTapExcludeRegionHolder.amendRegion(tempRegion, mTmpRect);
|
// The region held by the holder is on the window coordinates. We need to translate it to
|
// the screen coordinates.
|
tempRegion.translate(mWindowFrames.mFrame.left, mWindowFrames.mFrame.top);
|
region.op(tempRegion, Region.Op.UNION);
|
tempRegion.recycle();
|
}
|
|
@Override
|
public boolean isInputMethodTarget() {
|
return getDisplayContent().mInputMethodTarget == this;
|
}
|
|
long getFrameNumber() {
|
return mFrameNumber;
|
}
|
|
void setFrameNumber(long frameNumber) {
|
mFrameNumber = frameNumber;
|
}
|
|
public void getMaxVisibleBounds(Rect out) {
|
if (out.isEmpty()) {
|
out.set(mWindowFrames.mVisibleFrame);
|
return;
|
}
|
|
if (mWindowFrames.mVisibleFrame.left < out.left) {
|
out.left = mWindowFrames.mVisibleFrame.left;
|
}
|
if (mWindowFrames.mVisibleFrame.top < out.top) {
|
out.top = mWindowFrames.mVisibleFrame.top;
|
}
|
if (mWindowFrames.mVisibleFrame.right > out.right) {
|
out.right = mWindowFrames.mVisibleFrame.right;
|
}
|
if (mWindowFrames.mVisibleFrame.bottom > out.bottom) {
|
out.bottom = mWindowFrames.mVisibleFrame.bottom;
|
}
|
}
|
|
/**
|
* Copy the inset values over so they can be sent back to the client when a relayout occurs.
|
*/
|
void getInsetsForRelayout(Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
|
Rect outStableInsets, Rect outOutsets) {
|
outOverscanInsets.set(mWindowFrames.mOverscanInsets);
|
outContentInsets.set(mWindowFrames.mContentInsets);
|
outVisibleInsets.set(mWindowFrames.mVisibleInsets);
|
outStableInsets.set(mWindowFrames.mStableInsets);
|
outOutsets.set(mWindowFrames.mOutsets);
|
|
mLastRelayoutContentInsets.set(mWindowFrames.mContentInsets);
|
}
|
|
void getContentInsets(Rect outContentInsets) {
|
outContentInsets.set(mWindowFrames.mContentInsets);
|
}
|
|
Rect getContentInsets() {
|
return mWindowFrames.mContentInsets;
|
}
|
|
void getStableInsets(Rect outStableInsets) {
|
outStableInsets.set(mWindowFrames.mStableInsets);
|
}
|
|
Rect getStableInsets() {
|
return mWindowFrames.mStableInsets;
|
}
|
|
void resetLastContentInsets() {
|
mWindowFrames.resetLastContentInsets();
|
}
|
|
Rect getVisibleInsets() {
|
return mWindowFrames.mVisibleInsets;
|
}
|
|
@Override
|
public WindowFrames getWindowFrames() {
|
return mWindowFrames;
|
}
|
|
void resetContentChanged() {
|
mWindowFrames.setContentChanged(false);
|
}
|
|
void setInsetProvider(InsetsSourceProvider insetProvider) {
|
mInsetProvider = insetProvider;
|
}
|
|
InsetsSourceProvider getInsetProvider() {
|
return mInsetProvider;
|
}
|
|
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 = (float) currentPlayTime / getDuration();
|
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 writeToProtoInner(ProtoOutputStream proto) {
|
final long token = proto.start(MOVE);
|
mFrom.writeToProto(proto, FROM);
|
mTo.writeToProto(proto, TO);
|
proto.write(DURATION_MS, mDuration);
|
proto.end(token);
|
}
|
}
|
}
|