From a1eb439eee65138280c560f96e2a6896f9c9112c Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Thu, 10 Dec 2015 12:43:51 -0800 Subject: [PATCH] Move window replacement tracking to window state. In preparation for supporting replacement of child windows we make replacement per window rather than per app. Bug: 26070641 Change-Id: Ifa332086599c125611e430219c9497bae7e2ce31 --- core/java/android/view/WindowManager.java | 10 +++ core/java/android/widget/PopupWindow.java | 4 ++ .../com/android/server/wm/AppWindowToken.java | 72 +++++++++++++++---- .../server/wm/WallpaperController.java | 2 +- .../com/android/server/wm/WindowAnimator.java | 4 -- .../server/wm/WindowManagerService.java | 58 ++++++++------- .../com/android/server/wm/WindowState.java | 48 +++++++++---- .../server/wm/WindowStateAnimator.java | 2 +- .../server/wm/WindowSurfacePlacer.java | 2 +- 9 files changed, 139 insertions(+), 63 deletions(-) diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d6bc27c4996a1..2e884ccc7b253 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1171,6 +1171,16 @@ public interface WindowManager extends ViewManager { */ public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000; + /** + * Flag to indicate that this window is not expected to be replaced across + * configuration change triggered activity relaunches. In general the WindowManager + * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces + * until the replacement is ready to show in order to prevent visual glitch. However + * some windows, such as PopupWindows expect to be cleared across configuration change, + * and thus should hint to the WindowManager that it should not wait for a replacement. + * @hide + */ + public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000; /** * Control flags that are private to the platform. diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 7b9de7967db32..f4c343a9509aa 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -16,6 +16,8 @@ package android.widget; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; + import com.android.internal.R; import android.annotation.NonNull; @@ -1311,6 +1313,8 @@ public class PopupWindow { p.width = mLastWidth = mWidth; } + p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; + // Used for debugging. p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index fd5c7047c4fda..b49641fb6ab68 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -122,21 +123,6 @@ class AppWindowToken extends WindowToken { // True if the windows associated with this token should be cropped to their stack bounds. boolean mCropWindowsToStack; - // This application will have its window 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; - // If true, the replaced window was already requested to be removed. - boolean mReplacingRemoveRequested; - // Whether the replacement of the window should trigger app transition animation. - boolean mAnimateReplacingWindow; - // 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. - WindowState mReplacingWindow; - // Whether the new window has replaced the old one, so the old one can be removed without - // blinking. - boolean mHasReplacedWindow; - AppWindowToken(WindowManagerService _service, IApplicationToken _token, boolean _voiceInteraction) { super(_service, _token.asBinder(), @@ -392,6 +378,62 @@ class AppWindowToken extends WindowToken { } } + void setReplacingWindows(boolean animate) { + if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken + + " with replacing windows."); + + for (int i = allAppWindows.size() - 1; i >= 0; i--) { + final WindowState w = allAppWindows.get(i); + w.setReplacing(animate); + } + if (animate) { + // Set-up dummy animation so we can start treating windows associated with this + // token like they are in transition before the new app window is ready for us to + // run the real transition animation. + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, + "setReplacingWindow() Setting dummy animation on: " + this); + mAppAnimator.setDummyAnimation(); + } + } + + void addWindow(WindowState w) { + for (int i = allAppWindows.size() - 1; i >= 0; i--) { + WindowState candidate = allAppWindows.get(i); + if (candidate.mWillReplaceWindow && candidate.mReplacingWindow == null && + candidate.getWindowTag().equals(w.getWindowTag().toString())) { + candidate.mReplacingWindow = w; + } + } + allAppWindows.add(w); + } + + boolean waitingForReplacement() { + for (int i = allAppWindows.size() -1; i >= 0; i--) { + WindowState candidate = allAppWindows.get(i); + if (candidate.mWillReplaceWindow) { + return true; + } + } + return false; + } + + void clearTimedoutReplaceesLocked() { + for (int i = allAppWindows.size() - 1; i >= 0; + // removeWindowLocked at bottom of loop may remove multiple entries from + // allAppWindows if the window to be removed has child windows. It also may + // not remove any windows from allAppWindows at all if win is exiting and + // currently animating away. This ensures that winNdx is monotonically decreasing + // and never beyond allAppWindows bounds. + i = Math.min(i - 1, allAppWindows.size() - 1)) { + WindowState candidate = allAppWindows.get(i); + if (candidate.mWillReplaceWindow == false) { + continue; + } + candidate.mWillReplaceWindow = false; + service.removeWindowLocked(candidate); + } + } + @Override void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3db9ae0c4c826..2e424d036da78 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -506,7 +506,7 @@ class WallpaperController { inFreeformSpace = stack != null && stack.mStackId == FREEFORM_WORKSPACE_STACK_ID; } - replacing = replacing || (w.mAppToken != null && w.mAppToken.mWillReplaceWindow); + replacing = replacing || w.mWillReplaceWindow; // If the app is executing an animation because the keyguard is going away, // keep the wallpaper during the animation so it doesn't flicker out. diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index a6523a471ff2b..6a5183fa9c77e 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -918,10 +918,6 @@ public class WindowAnimator { } void requestRemovalOfReplacedWindows(WindowState win) { - final AppWindowToken token = win.mAppToken; - if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == win) { - token.mHasReplacedWindow = true; - } mRemoveReplacedWindows = true; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0c606fefacb74..f915fe98ce9ca 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -267,6 +267,9 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; + /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */ + static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000; + /** Amount of time to allow a last ANR message to exist before freeing the memory. */ static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours /** @@ -1349,10 +1352,7 @@ public class WindowManagerService extends IWindowManager.Stub final AppWindowToken appToken = win.mAppToken; if (appToken != null) { if (addToToken) { - appToken.allAppWindows.add(win); - } - if (appToken.mWillReplaceWindow) { - appToken.mReplacingWindow = win; + appToken.addWindow(win); } } } @@ -2083,14 +2083,15 @@ public class WindowManagerService extends IWindowManager.Stub } private void prepareWindowReplacementTransition(AppWindowToken atoken) { - if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) { + if (atoken == null) { return; } atoken.allDrawn = false; WindowState replacedWindow = null; for (int i = atoken.windows.size() - 1; i >= 0 && replacedWindow == null; i--) { WindowState candidate = atoken.windows.get(i); - if (candidate.mExiting) { + if (candidate.mExiting && candidate.mWillReplaceWindow + && candidate.mAnimateReplacingWindow) { replacedWindow = candidate; } } @@ -2190,7 +2191,7 @@ public class WindowManagerService extends IWindowManager.Stub + " app-animation=" + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null) + " mWillReplaceWindow=" - + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false) + + win.mWillReplaceWindow + " inPendingTransaction=" + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false) + " mDisplayFrozen=" + mDisplayFrozen); @@ -2202,13 +2203,13 @@ public class WindowManagerService extends IWindowManager.Stub // animation wouldn't be seen. if (win.mHasSurface && okToDisplay()) { final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mWillReplaceWindow) { + if (win.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 " + win + " until the new one is " + "added"); win.mExiting = true; - appToken.mReplacingRemoveRequested = true; + win.mReplacingRemoveRequested = true; Binder.restoreCallingIdentity(origId); return; } @@ -4042,7 +4043,7 @@ public class WindowManagerService extends IWindowManager.Stub // transition animation // * or this is an opening app and windows are being replaced. if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || - (visible && wtoken.mWillReplaceWindow)) { + (visible && wtoken.waitingForReplacement())) { boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG_WM, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -7483,6 +7484,8 @@ public class WindowManagerService extends IWindowManager.Stub public static final int TWO_FINGER_SCROLL_START = 45; public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46; + public static final int WINDOW_REPLACEMENT_TIMEOUT = 47; + /** * Used to denote that an integer field in a message will not be used. */ @@ -8065,6 +8068,13 @@ public class WindowManagerService extends IWindowManager.Stub toast.show(); } break; + case WINDOW_REPLACEMENT_TIMEOUT: { + final AppWindowToken token = (AppWindowToken) msg.obj; + synchronized (mWindowMap) { + token.clearTimedoutReplaceesLocked(); + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG_WM, "handleMessage: exit"); @@ -8587,7 +8597,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); - if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) { + if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) { continue; } i = reAddAppWindowsLocked(displayContent, i, wtoken); @@ -8696,8 +8706,8 @@ public class WindowManagerService extends IWindowManager.Stub private void forceHigherLayerIfNeeded(WindowState w, WindowStateAnimator winAnimator, AppWindowToken wtoken) { boolean force = false; - if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w - && wtoken.mAnimateReplacingWindow) { + + if (w.mWillReplaceWindow) { // We know that we will be animating a relaunching window in the near future, // which will receive a z-order increase. We want the replaced window to // immediately receive the same treatment, e.g. to be above the dock divider. @@ -10213,26 +10223,20 @@ public class WindowManagerService extends IWindowManager.Stub * @param token Application token for which the activity will be relaunched. */ public void setReplacingWindow(IBinder token, boolean animate) { + AppWindowToken appWindowToken = null; synchronized (mWindowMap) { - AppWindowToken appWindowToken = findAppWindowToken(token); + appWindowToken = findAppWindowToken(token); if (appWindowToken == null || !appWindowToken.isVisible()) { Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token); return; } - if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken - + " as replacing window."); - appWindowToken.mWillReplaceWindow = true; - appWindowToken.mHasReplacedWindow = false; - appWindowToken.mAnimateReplacingWindow = animate; + appWindowToken.setReplacingWindows(animate); + } - if (animate) { - // Set-up dummy animation so we can start treating windows associated with this - // token like they are in transition before the new app window is ready for us to - // run the real transition animation. - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, - "setReplacingWindow() Setting dummy animation on: " + appWindowToken); - appWindowToken.mAppAnimator.setDummyAnimation(); - } + if (appWindowToken != null) { + mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT); + mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken), + WINDOW_REPLACEMENT_TIMEOUT_DURATION); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 5a589e3a736b0..e4a6806621ffd 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -75,6 +75,7 @@ import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; 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_KEYGUARD; +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.TYPE_APPLICATION_STARTING; @@ -403,6 +404,18 @@ final class WindowState implements WindowManagerPolicy.WindowState { // used to start an entering animation earlier. public boolean mSurfaceSaved = false; + // This window will be replaced due to relaunch. This allows window manager + // to differentiate between simple removal of a window and replacement. In the latter case it + // will preserve the old window until the new one is drawn. + boolean mWillReplaceWindow = false; + // If true, the replaced window was already requested to be removed. + boolean mReplacingRemoveRequested = false; + // Whether the replacement of the window should trigger app transition animation. + 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. + WindowState mReplacingWindow = null; + /** * Wake lock for drawing. * Even though it's slightly more expensive to do so, we will use a separate wake lock @@ -580,8 +593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { @Override public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf, Rect osf) { - if (mAppToken != null && mAppToken.mWillReplaceWindow - && (mExiting || !mAppToken.mReplacingRemoveRequested)) { + if (mWillReplaceWindow && (mExiting || !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 @@ -1343,17 +1355,17 @@ final class WindowState implements WindowManagerPolicy.WindowState { } void maybeRemoveReplacedWindow() { - AppWindowToken token = mAppToken; - if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this - && token.mHasReplacedWindow) { - if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this); - token.mWillReplaceWindow = false; - token.mAnimateReplacingWindow = false; - token.mReplacingRemoveRequested = false; - token.mReplacingWindow = null; - token.mHasReplacedWindow = false; - for (int i = token.allAppWindows.size() - 1; i >= 0; i--) { - final WindowState win = token.allAppWindows.get(i); + if (mAppToken == null) { + return; + } + for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) { + final WindowState win = mAppToken.allAppWindows.get(i); + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win); + if (win.mWillReplaceWindow && win.mReplacingWindow == this) { + win.mWillReplaceWindow = false; + win.mAnimateReplacingWindow = false; + win.mReplacingRemoveRequested = false; + win.mReplacingWindow = null; if (win.mExiting) { mService.removeWindowInnerLocked(win); } @@ -2161,7 +2173,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { + " " + getWindowTag(); } - private CharSequence getWindowTag() { + CharSequence getWindowTag() { CharSequence tag = mAttrs.getTitle(); if (tag == null || tag.length() <= 0) { tag = mAttrs.packageName; @@ -2259,4 +2271,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean isChildWindow() { return mAttachedWindow != null; } + + void setReplacing(boolean animate) { + if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) == 0) { + mWillReplaceWindow = true; + mReplacingWindow = null; + mAnimateReplacingWindow = animate; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 539810de67ece..b3eb1737bbb19 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1190,7 +1190,7 @@ class WindowStateAnimator { // different stack. If we suddenly crop it to the new stack bounds, it might get cut off. // We don't want it to happen, so we let it ignore the stack bounds until it gets removed. // The window that will replace it will abide them. - if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace() + if (isAnimating() && (w.mWillReplaceWindow || w.inDockedWorkspace() || w.inFreeformWorkspace())) { return; } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 50bdf257d7ee6..82a6b561c6e69 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -1192,7 +1192,7 @@ class WindowSurfacePlacer { // if app window is removed, or window relayout to invisible. 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. - if (win.mAppToken == null || !win.mAppToken.mWillReplaceWindow) { + if (!win.mWillReplaceWindow) { win.mExiting = false; } if (win.mWinAnimator.mAnimLayer > layer) {