diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b7c55d43ffbfb..dd9b6f13869d5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3092,8 +3092,11 @@ public final class ActivityStackSupervisor implements DisplayListener { // to size change. The activity will first remove the old window and then add a // new one. This call will tell window manager about this, so it can preserve // the old window until the new one is drawn. This prevents having a gap between - // the removal and addition, in which no window is visible. - mWindowManager.setReplacingWindow(r.appToken); + // the removal and addition, in which no window is visible. If we also changed + // the stack to the fullscreen stack, i.e. maximized the window, we will animate + // the transition. + mWindowManager.setReplacingWindow(r.appToken, + stackId == FULLSCREEN_WORKSPACE_STACK_ID /* animate */); } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index de7c07ebaee9d..5dc6887522f36 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -122,6 +122,8 @@ public class AppTransition implements Dump { public static final int TRANSIT_TASK_OPEN_BEHIND = 16; /** A window in a task is being animated in-place. */ public static final int TRANSIT_TASK_IN_PLACE = 17; + /** An activity is being relaunched (e.g. due to configuration change). */ + public static final int TRANSIT_ACTIVITY_RELAUNCH = 18; /** Fraction of animation at which the recents thumbnail stays completely transparent */ private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f; @@ -1004,6 +1006,22 @@ public class AppTransition implements Dump { return prepareThumbnailAnimation(a, appWidth, appHeight, transit); } + private Animation createRelaunchAnimation(int appWidth, int appHeight) { + getDefaultNextAppTransitionStartRect(mTmpFromClipRect); + final int left = mTmpFromClipRect.left; + final int top = mTmpFromClipRect.top; + mTmpFromClipRect.offset(-left, -top); + mTmpToClipRect.set(0, 0, appWidth, appHeight); + AnimationSet set = new AnimationSet(true); + ClipRectAnimation clip = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); + TranslateAnimation translate = new TranslateAnimation(left, 0, top, 0); + clip.setInterpolator(mDecelerateInterpolator); + set.addAnimation(clip); + set.addAnimation(translate); + set.setDuration(DEFAULT_APP_TRANSITION_DURATION); + return set; + } + /** * @return true if and only if the first frame of the transition can be skipped, i.e. the first * frame of the transition doesn't change the visuals on screen, so we can start @@ -1040,6 +1058,8 @@ public class AppTransition implements Dump { "applyAnimation voice:" + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); + } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) { + a = createRelaunchAnimation(appWidth, appHeight); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { a = loadAnimationRes(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); @@ -1326,6 +1346,9 @@ public class AppTransition implements Dump { case TRANSIT_TASK_OPEN_BEHIND: { return "TRANSIT_TASK_OPEN_BEHIND"; } + case TRANSIT_ACTIVITY_RELAUNCH: { + return "TRANSIT_ACTIVITY_RELAUNCH"; + } default: { return ""; } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 6ee2c3952f2d3..4f62909c061a9 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -116,6 +116,10 @@ class AppWindowToken extends WindowToken { // 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 mReplacingWindow; + // 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; AppWindowToken(WindowManagerService _service, IApplicationToken _token, boolean _voiceInteraction) { @@ -230,16 +234,24 @@ class AppWindowToken extends WindowToken { } WindowState findMainWindow() { + WindowState candidate = null; int j = windows.size(); while (j > 0) { j--; WindowState win = windows.get(j); if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { - return win; + // In cases where there are multiple windows, we prefer the non-exiting window. This + // happens for example when when replacing windows during an activity relaunch. When + // constructing the animation, we want the new window, not the exiting one. + if (win.mExiting) { + candidate = win; + } else { + return win; + } } } - return null; + return candidate; } boolean isVisible() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ebabc52015837..f3e7d1d58313e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -125,7 +125,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.Watchdog; -import com.android.server.am.BatteryStatsService; import com.android.server.input.InputManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.power.ShutdownThread; @@ -290,6 +289,10 @@ public class WindowManagerService extends IWindowManager.Stub private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + // Used to indicate that if there is already a transition set, it should be preserved when + // trying to apply a new one. + private static final boolean ALWAYS_KEEP_CURRENT = true; + final private KeyguardDisableHandler mKeyguardDisableHandler; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -1740,6 +1743,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); + AppWindowToken atoken = null; if (token == null) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG, "Attempted to add application window with unknown token " @@ -1774,7 +1778,7 @@ public class WindowManagerService extends IWindowManager.Stub token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { - AppWindowToken atoken = token.appWindowToken; + atoken = token.appWindowToken; if (atoken == null) { Slog.w(TAG, "Attempted to add window with non-application token " + token + ". Aborting."); @@ -1920,6 +1924,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; + prepareWindowReplacementTransition(atoken); if (displayContent.isDefaultDisplay) { mPolicy.getInsetHintLw(win.mAttrs, mRotation, outContentInsets, outStableInsets, @@ -1977,6 +1982,33 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private void prepareWindowReplacementTransition(AppWindowToken atoken) { + if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) { + 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) { + replacedWindow = candidate; + } + } + if (replacedWindow == null) { + // We expect to already receive a request to remove the old window. If it did not + // happen, let's just simply add a window. + return; + } + Rect frame = replacedWindow.mFrame; + // We treat this as if this activity was opening, so we can trigger the app transition + // animation and piggy-back on existing transition animation infrastructure. + mOpeningApps.add(atoken); + prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT); + mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top, + frame.width(), frame.height()); + executeAppTransition(); + } + /** * Returns whether screen capture is disabled for all windows of a specific user. */ @@ -2072,6 +2104,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is " + "added"); win.mExiting = true; + appToken.mReplacingRemoveRequested = true; return; } // If we are not currently running the exit animation, we @@ -2730,6 +2763,7 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win); if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { getDefaultDisplayContentLocked().pendingLayoutChanges |= @@ -3426,6 +3460,12 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * @param transit What kind of transition is happening. Use one of the constants + * AppTransition.TRANSIT_*. + * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT + * be set. + */ @Override public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -3822,10 +3862,13 @@ public class WindowManagerService extends IWindowManager.Stub } wtoken.willBeHidden = false; - // Allow for state changes and animation to be applied if token is transitioning - // visibility state or the token was marked as hidden and is exiting before we had a chance - // to play the transition animation. - if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting)) { + // Allow for state changes and animation to be applied if: + // * token is transitioning visibility state + // * or the token was marked as hidden and is exiting before we had a chance to play the + // transition animation + // * or this is an opening app and windows are being replaced. + if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || + (visible && wtoken.mReplacingWindow)) { boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -9723,7 +9766,7 @@ public class WindowManagerService extends IWindowManager.Stub return mWindowMap; } - public void setReplacingWindow(IBinder token) { + public void setReplacingWindow(IBinder token, boolean animate) { synchronized (mWindowMap) { AppWindowToken appWindowToken = findAppWindowToken(token); if (appWindowToken == null) { @@ -9731,6 +9774,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } appWindowToken.mReplacingWindow = true; + appWindowToken.mAnimateReplacingWindow = animate; } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 49d260e46f192..d756d39121705 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -545,6 +545,14 @@ 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.mReplacingWindow + && (mExiting || !mAppToken.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 = mAppToken != null ? getTask() : null; @@ -1274,6 +1282,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { AppWindowToken token = mAppToken; if (token != null && token.mReplacingWindow) { token.mReplacingWindow = false; + token.mAnimateReplacingWindow = false; + token.mReplacingRemoveRequested = false; for (int i = token.allAppWindows.size() - 1; i >= 0; i--) { WindowState win = token.allAppWindows.get(i); if (win.mExiting) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index efad8bf6bef9d..066dc9554ebcd 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1501,6 +1501,7 @@ class WindowStateAnimator { w.mLastHScale = w.mHScale; w.mLastVScale = w.mVScale; if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "control=" + mSurfaceControl + "alpha=" + mShownAlpha + " layer=" + mAnimLayer + " matrix=[" + mDsDx + "*" + w.mHScale + "," + mDtDx + "*" + w.mVScale diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index ce07a9d859be2..8582ca28bd4ac 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -47,7 +47,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; @@ -305,9 +304,9 @@ class WindowSurfacePlacer { if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( "On entry to LockedInner", displayContent.pendingLayoutChanges); - if ((displayContent.pendingLayoutChanges & - FINISH_LAYOUT_REDO_WALLPAPER) != 0 && - mWallpaperControllerLocked.adjustWallpaperWindows()) { + if ((displayContent.pendingLayoutChanges + & FINISH_LAYOUT_REDO_WALLPAPER) != 0 + && mWallpaperControllerLocked.adjustWallpaperWindows()) { mService.assignLayersLocked(windows); displayContent.layoutNeeded = true; } @@ -430,8 +429,7 @@ class WindowSurfacePlacer { // Moved from updateWindowsAndWallpaperLocked(). if (w.mHasSurface) { // Take care of the window being ready to display. - final boolean committed = - winAnimator.commitFinishDrawingLocked(); + final boolean committed = winAnimator.commitFinishDrawingLocked(); if (isDefaultDisplay && committed) { if (w.mAttrs.type == TYPE_DREAM) { // HACK: When a dream is shown, it may at that @@ -447,9 +445,8 @@ class WindowSurfacePlacer { } } if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { - if (DEBUG_WALLPAPER_LIGHT) - Slog.v(TAG, - "First draw done in potential wallpaper target " + w); + if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, + "First draw done in potential wallpaper target " + w); mWallpaperMayChange = true; displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; @@ -544,7 +541,6 @@ class WindowSurfacePlacer { // Give the display manager a chance to adjust properties // like display rotation if it needs to. mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); - } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { @@ -1461,8 +1457,6 @@ class WindowSurfacePlacer { // open/close animation (only on the way down) anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect, thumbnailHeader, taskId); - Log.d(TAG, "assigning thumbnail force above layer: " - + openingLayer + " " + closingLayer); openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer); openingAppAnimator.deferThumbnailDestruction = !mService.mAppTransition.isNextThumbnailTransitionScaleUp();