From 55a309f8e2a972a2f0ef0cd86736d3c2f47a75f6 Mon Sep 17 00:00:00 2001 From: Filip Gruszczynski Date: Fri, 4 Sep 2015 17:15:01 -0700 Subject: [PATCH] Maximize animation when activity is relaunched. This is the first step towards maximize operation animations. It builds upon preserving old window and starts clip+translate animation of the new window from the frame of the old window. It mostly uses the existing infrastructure, expanding the idea of opening apps list to include also relaunching apps and treating maximize operation as an app transition. Change-Id: I7be402bd329c2fe5bf7d53a2a910532286a8b194 --- .../server/am/ActivityStackSupervisor.java | 7 ++- .../com/android/server/wm/AppTransition.java | 23 ++++++++ .../com/android/server/wm/AppWindowToken.java | 16 ++++- .../server/wm/WindowManagerService.java | 58 ++++++++++++++++--- .../com/android/server/wm/WindowState.java | 10 ++++ .../server/wm/WindowStateAnimator.java | 1 + .../server/wm/WindowSurfacePlacer.java | 18 ++---- 7 files changed, 110 insertions(+), 23 deletions(-) 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();