From 23fa16b759f023ea18ab9f84e89df50d4b449dfd Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Wed, 13 Jan 2016 13:19:58 -0800 Subject: [PATCH] Replace SurfaceViews across resize trigerred relaunches. In resize modes where we are preserving the main application window, we need to tell the WindowManager to prepare to replace the child surfaces, or they will dissapear across relaunches. Bug: 26070641 Change-Id: I864168688dc320e9280e651f9c5df614f52bc96c --- core/java/android/app/ActivityThread.java | 15 +++++++++ core/java/android/view/IWindowSession.aidl | 8 +++++ .../com/android/server/wm/AppWindowToken.java | 11 +++++++ .../java/com/android/server/wm/Session.java | 5 +++ .../server/wm/WindowManagerService.java | 33 +++++++++++++++++-- .../bridge/android/BridgeWindowSession.java | 5 +++ 6 files changed, 75 insertions(+), 2 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4e55c89fa7392..d962b7c5ae32a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4325,6 +4325,21 @@ public final class ActivityThread { r.activity.mChangingConfigurations = true; + // If we are preserving the main window across relaunches we would also like to preserve + // the children. However the client side view system does not support preserving + // the child views so we notify the window manager to expect these windows to + // be replaced and defer requests to destroy or hide them. This way we can achieve + // visual continuity. It's important that we do this here prior to pause and destroy + // as that is when we may hide or remove the child views. + try { + if (r.mPreserveWindow) { + WindowManagerGlobal.getWindowSession().prepareToReplaceChildren(r.token); + } + } catch (RemoteException e) { + // If the system process has died, it's game over for everyone. + } + + // Need to ensure state is saved. if (!r.paused) { performPauseActivity(r.token, false, r.isPreHoneycomb()); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index b3cd8c11f0f94..96e73eb1daba2 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -120,6 +120,14 @@ interface IWindowSession { void repositionChild(IWindow childWindow, int left, int top, int right, int bottom, long deferTransactionUntilFrame, out Rect outFrame); + /* + * Notify the window manager that an application is relaunching and + * child windows should be prepared for replacement. + * + * @param appToken The application + */ + void prepareToReplaceChildren(IBinder appToken); + /** * If a call to relayout() asked to have the surface destroy deferred, * it must call this once it is okay to destroy that surface. diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 751f8715a1e13..e2aaf0a6a8046 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -404,6 +404,17 @@ class AppWindowToken extends WindowToken { } } + void setReplacingChildren() { + if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken + + " with replacing child windows."); + for (int i = allAppWindows.size() - 1; i >= 0; i--) { + final WindowState w = allAppWindows.get(i); + if (w.isChildWindow()) { + w.setReplacing(false /* animate */); + } + } + } + void resetReplacingWindows() { if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Resetting app token " + appWindowToken + " of replacing window marks."); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 1b6957d9e5541..b5cf40ca0e2c1 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -200,6 +200,11 @@ final class Session extends IWindowSession.Stub deferTransactionUntilFrame, outFrame); } + @Override + public void prepareToReplaceChildren(IBinder appToken) { + mService.setReplacingChildren(appToken); + } + public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7d142eca0bb4b..ba8cb905a327c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2690,7 +2690,14 @@ public class WindowManagerService extends IWindowManager.Stub final boolean notExitingOrAnimating = !win.mExiting && !win.isAnimatingWithSavedSurface(); result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0; - if (notExitingOrAnimating) { + // We don't want to animate visibility of windows which are pending + // replacement. In the case of activity relaunch child windows + // could request visibility changes as they are detached from the main + // application window during the tear down process. If we satisfied + // these visibility changes though, we would cause a visual glitch + // hiding the window before it's replacement was available. + // So we just do nothing on our side. + if (notExitingOrAnimating && win.mWillReplaceWindow == false) { focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay, focusMayChange); @@ -2884,9 +2891,10 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); - if (win == null) { + if (win == null || win.mWillReplaceWindow) { return; } + win.mWinAnimator.destroyDeferredSurfaceLocked(); } } finally { @@ -10189,6 +10197,27 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Hint to a token that its children will be replaced across activity relaunch. + * The children would otherwise be removed shortly following this as the + * activity is torn down. + * @param token Application token for which the activity will be relaunched. + */ + public void setReplacingChildren(IBinder token) { + AppWindowToken appWindowToken = null; + synchronized (mWindowMap) { + appWindowToken = findAppWindowToken(token); + if (appWindowToken == null || !appWindowToken.isVisible()) { + Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + + token); + return; + } + + appWindowToken.setReplacingChildren(); + scheduleClearReplacingWindowIfNeeded(token, true /* replacing */); + } + } + /** * If we're replacing the window, schedule a timer to clear the replaced window * after a timeout, in case the replacing window is not coming. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 5c73fb6a6fbe0..d862242905927 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -230,4 +230,9 @@ public final class BridgeWindowSession implements IWindowSession { public void pokeDrawLock(IBinder window) { // pass for now. } + + @Override + public void prepareToReplaceChildren(IBinder appToken) { + // pass for now. + } }