Thumbnail enter animations for multiwindow.
The multi-window differs from the full screen entrance animation by
using translation to make the window travel from the thumbnail to the
final position. It also takes into account the surface insets when
determining scaling animation. It doesn't use clipping at the moment,
but that would be a near future improvement.
Change-Id: I7f310850713448b820b9e94ac2f8fbf74563068c
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 7bd0635..a2307f9 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -651,7 +652,7 @@
* when a thumbnail is specified with the activity options.
*/
Animation createThumbnailAspectScaleAnimationLocked(int appWidth, int appHeight,
- int deviceWidth, int transit) {
+ int deviceWidth) {
Animation a;
final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -659,7 +660,6 @@
final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
float scaleW = deviceWidth / thumbWidth;
- float unscaledWidth = deviceWidth;
float unscaledHeight = thumbHeight * scaleW;
float unscaledStartY = mNextAppTransitionStartY - (unscaledHeight - thumbHeight) / 2f;
if (mNextAppTransitionScaleUp) {
@@ -716,7 +716,7 @@
*/
Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
int appWidth, int appHeight, int orientation, int transit, Rect containingFrame,
- Rect contentInsets) {
+ Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow) {
Animation a;
final int thumbWidthI = mNextAppTransitionStartWidth;
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -729,40 +729,45 @@
switch (thumbTransitState) {
case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
- // App window scaling up to become full screen
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- // In portrait, we scale the width and clip to the top/left square
- scale = thumbWidth / appWidth;
- scaledTopDecor = (int) (scale * contentInsets.top);
- int unscaledThumbHeight = (int) (thumbHeight / scale);
- mTmpFromClipRect.set(containingFrame);
- mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight);
- mTmpToClipRect.set(containingFrame);
+ if (resizedWindow) {
+ a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
+ containingFrame, surfaceInsets);
} else {
- // In landscape, we scale the height and clip to the top/left square
- scale = thumbHeight / (appHeight - contentInsets.top);
- scaledTopDecor = (int) (scale * contentInsets.top);
- int unscaledThumbWidth = (int) (thumbWidth / scale);
- mTmpFromClipRect.set(containingFrame);
- mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth);
- mTmpToClipRect.set(containingFrame);
+ // App window scaling up to become full screen
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ // In portrait, we scale the width and clip to the top/left square
+ scale = thumbWidth / appWidth;
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbHeight = (int) (thumbHeight / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpFromClipRect.bottom = (mTmpFromClipRect.top + unscaledThumbHeight);
+ mTmpToClipRect.set(containingFrame);
+ } else {
+ // In landscape, we scale the height and clip to the top/left square
+ scale = thumbHeight / (appHeight - contentInsets.top);
+ scaledTopDecor = (int) (scale * contentInsets.top);
+ int unscaledThumbWidth = (int) (thumbWidth / scale);
+ mTmpFromClipRect.set(containingFrame);
+ mTmpFromClipRect.right = (mTmpFromClipRect.left + unscaledThumbWidth);
+ mTmpToClipRect.set(containingFrame);
+ }
+ // exclude top screen decor (status bar) region from the source clip.
+ mTmpFromClipRect.top = contentInsets.top;
+
+ mNextAppTransitionInsets.set(contentInsets);
+
+ Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
+ computePivot(mNextAppTransitionStartX, scale),
+ computePivot(mNextAppTransitionStartY, scale));
+ Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+ Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
+
+ AnimationSet set = new AnimationSet(true);
+ set.addAnimation(clipAnim);
+ set.addAnimation(scaleAnim);
+ set.addAnimation(translateAnim);
+ a = set;
}
- // exclude top screen decor (status bar) region from the source clip.
- mTmpFromClipRect.top = contentInsets.top;
-
- mNextAppTransitionInsets.set(contentInsets);
-
- Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
- computePivot(mNextAppTransitionStartX, scale),
- computePivot(mNextAppTransitionStartY, scale));
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
-
- AnimationSet set = new AnimationSet(true);
- set.addAnimation(clipAnim);
- set.addAnimation(scaleAnim);
- set.addAnimation(translateAnim);
- a = set;
break;
}
case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
@@ -836,6 +841,31 @@
mTouchResponseInterpolator);
}
+ private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked(
+ Rect containingFrame, @Nullable Rect surfaceInsets) {
+ float width = containingFrame.width();
+ float height = containingFrame.height();
+ float scaleWidth = mNextAppTransitionStartWidth / width;
+ float scaleHeight = mNextAppTransitionStartHeight / height;
+ AnimationSet set = new AnimationSet(true);
+ int surfaceInsetsHorizontal = surfaceInsets == null
+ ? 0 : surfaceInsets.left + surfaceInsets.right;
+ int surfaceInsetsVertical = surfaceInsets == null
+ ? 0 : surfaceInsets.top + surfaceInsets.bottom;
+ // We want the scaling to happen from the center of the surface. In order to achieve that,
+ // we need to account for surface insets that will be used to enlarge the surface.
+ ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1,
+ (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2);
+ int fromX = mNextAppTransitionStartX + mNextAppTransitionStartWidth / 2
+ - (containingFrame.left + containingFrame.width() / 2);
+ int fromY = mNextAppTransitionStartY + mNextAppTransitionStartHeight / 2
+ - (containingFrame.top + containingFrame.height() / 2);
+ TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0);
+ set.addAnimation(scale);
+ set.addAnimation(translation);
+ return set;
+ }
+
/**
* This animation runs for the thumbnail that gets cross faded with the enter/exit activity
* when a thumbnail is specified with the activity options.
@@ -881,7 +911,7 @@
* leaving, and the activity that is entering.
*/
Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth,
- int appHeight, int transit) {
+ int appHeight, int transit) {
Animation a;
final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -954,7 +984,8 @@
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets,
- Rect appFrame, boolean isVoiceInteraction) {
+ @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction,
+ boolean resizedWindow) {
Animation a;
if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
|| transit == TRANSIT_TASK_OPEN
@@ -1023,8 +1054,8 @@
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
a = createAspectScaledThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), appWidth, appHeight, orientation,
- transit, containingFrame, contentInsets);
+ getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit,
+ containingFrame, contentInsets, surfaceInsets, resizedWindow);
if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
String animName = mNextAppTransitionScaleUp ?
"ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9af5e14..5ec2137 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3448,8 +3448,8 @@
}
}
- private boolean applyAnimationLocked(AppWindowToken atoken,
- WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) {
+ private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
+ int transit, boolean enter, boolean isVoiceInteraction) {
// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
@@ -3466,24 +3466,36 @@
Rect containingFrame = new Rect(0, 0, width, height);
Rect contentInsets = new Rect();
Rect appFrame = new Rect(0, 0, width, height);
- if (win != null && win.isFullscreen(width, height)) {
- // For fullscreen windows use the window frames and insets to set the thumbnail
- // clip. For none-fullscreen windows we use the app display region so the clip
- // isn't affected by the window insets.
+ Rect surfaceInsets = null;
+ final boolean fullscreen = win != null && win.isFullscreen(width, height);
+ // Dialog activities have windows with containing frame being very large, but not
+ // exactly fullscreen and much smaller mFrame. We use this distinction to identify
+ // dialog activities.
+ final boolean dialogWindow = win != null && !win.mContainingFrame.equals(win.mFrame);
+ if (win != null) {
containingFrame.set(win.mContainingFrame);
- contentInsets.set(win.mContentInsets);
- appFrame.set(win.mFrame);
+ surfaceInsets = win.getAttrs().surfaceInsets;
+ if (fullscreen) {
+ // For fullscreen windows use the window frames and insets to set the thumbnail
+ // clip. For none-fullscreen windows we use the app display region so the clip
+ // isn't affected by the window insets.
+ contentInsets.set(win.mContentInsets);
+ appFrame.set(win.mFrame);
+ }
}
+ final int containingWidth = containingFrame.width();
+ final int containingHeight = containingFrame.height();
if (atoken.mLaunchTaskBehind) {
// Differentiate the two animations. This one which is briefly on the screen
// gets the !enter animation, and the other activity which remains on the
// screen gets the enter animation. Both appear in the mOpeningApps set.
enter = false;
}
- Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height,
- mCurConfiguration.orientation, containingFrame, contentInsets, appFrame,
- isVoiceInteraction);
+ final boolean resizedWindow = !fullscreen && !dialogWindow;
+ Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth,
+ containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets,
+ surfaceInsets, appFrame, isVoiceInteraction, resizedWindow);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3493,7 +3505,7 @@
}
Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
}
- atoken.mAppAnimator.setAnimation(a, width, height,
+ atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
mAppTransition.canSkipFirstFrame());
}
} else {
@@ -9533,7 +9545,7 @@
// open/close animation (only on the way down)
anim = mAppTransition.createThumbnailAspectScaleAnimationLocked(
displayInfo.appWidth, displayInfo.appHeight,
- displayInfo.logicalWidth, transit);
+ displayInfo.logicalWidth);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(topOpeningLayer,
topClosingLayer);
openingAppAnimator.deferThumbnailDestruction =