From 5fcda4f8602031564ea2cdb920edf70696aa5337 Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Wed, 23 Dec 2020 11:10:20 +0800 Subject: [PATCH] Make AppTransition animations sharable (1/n) Move some animation functions in AppTransition to new TransitionAnimation class for wm-shell using. Bug: 175053726 Test: build Change-Id: I7eef67359fd4532f7e4d97c787d5288362a32f13 --- .../internal/policy}/AttributeCache.java | 54 +- .../internal/policy}/ClipRectLRAnimation.java | 4 +- .../internal/policy}/ClipRectTBAnimation.java | 5 +- .../policy/LogDecelerateInterpolator.java | 5 +- .../internal/policy/TransitionAnimation.java | 960 ++++++++++++++++++ .../server/am/ActivityManagerService.java | 2 +- .../server/pm/PackageManagerService.java | 2 +- .../server/policy/PhoneWindowManager.java | 30 +- .../com/android/server/wm/ActivityRecord.java | 2 +- .../server/wm/ActivityTaskManagerService.java | 2 +- .../com/android/server/wm/AppTransition.java | 743 ++------------ .../java/com/android/server/SystemServer.java | 1 + .../android/server/wm/WindowTestsBase.java | 2 +- 13 files changed, 1070 insertions(+), 742 deletions(-) rename {services/core/java/com/android/server => core/java/com/android/internal/policy}/AttributeCache.java (84%) rename {services/core/java/com/android/server/wm/animation => core/java/com/android/internal/policy}/ClipRectLRAnimation.java (95%) rename {services/core/java/com/android/server/wm/animation => core/java/com/android/internal/policy}/ClipRectTBAnimation.java (96%) rename {services/core/java/com/android/server => core/java/com/android/internal}/policy/LogDecelerateInterpolator.java (93%) create mode 100644 core/java/com/android/internal/policy/TransitionAnimation.java diff --git a/services/core/java/com/android/server/AttributeCache.java b/core/java/com/android/internal/policy/AttributeCache.java similarity index 84% rename from services/core/java/com/android/server/AttributeCache.java rename to core/java/com/android/internal/policy/AttributeCache.java index 58ec836547a7d..1bdad25d25bb3 100644 --- a/services/core/java/com/android/server/AttributeCache.java +++ b/core/java/com/android/internal/policy/AttributeCache.java @@ -1,21 +1,20 @@ /* -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -package com.android.server; +package com.android.internal.policy; import android.content.Context; import android.content.pm.ActivityInfo; @@ -34,6 +33,7 @@ import com.android.internal.annotations.GuardedBy; * TODO: This should be better integrated into the system so it doesn't need * special calls from the activity manager to clear it. */ +/** @hide */ public final class AttributeCache { private static final int CACHE_SIZE = 4; private static AttributeCache sInstance = null; @@ -54,11 +54,11 @@ public final class AttributeCache { context = c; } } - + public final static class Entry { public final Context context; public final TypedArray array; - + public Entry(Context c, TypedArray ta) { context = c; array = ta; @@ -70,17 +70,17 @@ public final class AttributeCache { } } } - + public static void init(Context context) { if (sInstance == null) { sInstance = new AttributeCache(context); } } - + public static AttributeCache instance() { return sInstance; } - + public AttributeCache(Context context) { mContext = context; } @@ -115,7 +115,11 @@ public final class AttributeCache { } } } - + + public Entry get(String packageName, int resId, int[] styleable) { + return get(packageName, resId, styleable, UserHandle.USER_CURRENT); + } + public Entry get(String packageName, int resId, int[] styleable, int userId) { synchronized (this) { Package pkg = mPackages.get(packageName); @@ -143,12 +147,12 @@ public final class AttributeCache { pkg = new Package(context); mPackages.put(packageName, pkg); } - + if (map == null) { map = new ArrayMap<>(); pkg.mMap.put(resId, map); } - + try { ent = new Entry(pkg.context, pkg.context.obtainStyledAttributes(resId, styleable)); @@ -156,7 +160,7 @@ public final class AttributeCache { } catch (Resources.NotFoundException e) { return null; } - + return ent; } } diff --git a/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java b/core/java/com/android/internal/policy/ClipRectLRAnimation.java similarity index 95% rename from services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java rename to core/java/com/android/internal/policy/ClipRectLRAnimation.java index 0db4c70e27616..4dc3cd31ac204 100644 --- a/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java +++ b/core/java/com/android/internal/policy/ClipRectLRAnimation.java @@ -11,10 +11,10 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.server.wm.animation; +package com.android.internal.policy; import android.graphics.Rect; import android.view.animation.ClipRectAnimation; diff --git a/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java b/core/java/com/android/internal/policy/ClipRectTBAnimation.java similarity index 96% rename from services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java rename to core/java/com/android/internal/policy/ClipRectTBAnimation.java index 1f5b1a3f9660d..24913cf7fca23 100644 --- a/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java +++ b/core/java/com/android/internal/policy/ClipRectTBAnimation.java @@ -11,16 +11,15 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.server.wm.animation; +package com.android.internal.policy; import android.graphics.Rect; import android.view.animation.ClipRectAnimation; import android.view.animation.Interpolator; import android.view.animation.Transformation; -import android.view.animation.TranslateAnimation; /** * Special case of ClipRectAnimation that animates only the top/bottom diff --git a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java b/core/java/com/android/internal/policy/LogDecelerateInterpolator.java similarity index 93% rename from services/core/java/com/android/server/policy/LogDecelerateInterpolator.java rename to core/java/com/android/internal/policy/LogDecelerateInterpolator.java index ed5dc6fa7a510..dee77aae01fe0 100644 --- a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java +++ b/core/java/com/android/internal/policy/LogDecelerateInterpolator.java @@ -11,13 +11,14 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.server.policy; +package com.android.internal.policy; import android.view.animation.Interpolator; +/** @hide */ public class LogDecelerateInterpolator implements Interpolator { private int mBase; diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java new file mode 100644 index 0000000000000..56b25b2060ea0 --- /dev/null +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy; + +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE; +import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER; +import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN; +import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.ResourceId; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.hardware.HardwareBuffer; +import android.os.SystemProperties; +import android.util.Slog; +import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.TransitionOldType; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnimationUtils; +import android.view.animation.ClipRectAnimation; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; + +import com.android.internal.R; + +import java.util.List; + +/** @hide */ +public class TransitionAnimation { + // These are the possible states for the enter/exit activities during a thumbnail transition + public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; + public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; + public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; + public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; + + /** + * Maximum duration for the clip reveal animation. This is used when there is a lot of movement + * involved, to make it more understandable. + */ + private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420; + private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8; + private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336; + + public static final int DEFAULT_APP_TRANSITION_DURATION = 336; + + /** Fraction of animation at which the recents thumbnail becomes completely transparent */ + private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f; + + private final Context mContext; + private final String mTag; + + private final LogDecelerateInterpolator mInterpolator = new LogDecelerateInterpolator(100, 0); + /** Interpolator to be used for animations that respond directly to a touch */ + private final Interpolator mTouchResponseInterpolator = + new PathInterpolator(0.3f, 0f, 0.1f, 1f); + private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f); + private final Interpolator mDecelerateInterpolator; + private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mThumbnailFadeOutInterpolator; + private final Rect mTmpFromClipRect = new Rect(); + private final Rect mTmpToClipRect = new Rect(); + private final Rect mTmpRect = new Rect(); + + private final int mClipRevealTranslationY; + private final int mConfigShortAnimTime; + private final int mDefaultWindowAnimationStyleResId; + + private final boolean mDebug; + private final boolean mGridLayoutRecentsEnabled; + private final boolean mLowRamRecentsEnabled; + + public TransitionAnimation(Context context, boolean debug, String tag) { + mContext = context; + mDebug = debug; + mTag = tag; + + mDecelerateInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.decelerate_cubic); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); + mThumbnailFadeOutInterpolator = input -> { + // Linear response for first fraction, then complete after that. + if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) { + float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION; + return mLinearOutSlowInInterpolator.getInterpolation(t); + } + return 1f; + }; + + mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP + * mContext.getResources().getDisplayMetrics().density); + mConfigShortAnimTime = context.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + + mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); + mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic(); + + final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes( + com.android.internal.R.styleable.Window); + mDefaultWindowAnimationStyleResId = windowStyle.getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + windowStyle.recycle(); + } + + public Animation loadKeyguardExitAnimation(int transit, int transitionFlags) { + if ((transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) { + return null; + } + final boolean toShade = + (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0; + final boolean subtle = + (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0; + return createHiddenByKeyguardExit(mContext, mInterpolator, + transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, toShade, subtle); + } + + @Nullable + public Animation loadKeyguardUnoccludeAnimation(LayoutParams lp) { + return loadAnimationRes(lp, com.android.internal.R.anim.wallpaper_open_exit); + } + + @Nullable + public Animation loadVoiceActivityOpenAnimation(LayoutParams lp, boolean enter) { + return loadAnimationRes(lp, enter + ? com.android.internal.R.anim.voice_activity_open_enter + : com.android.internal.R.anim.voice_activity_open_exit); + } + + @Nullable + public Animation loadVoiceActivityExitAnimation(LayoutParams lp, boolean enter) { + return loadAnimationRes(lp, enter + ? com.android.internal.R.anim.voice_activity_close_enter + : com.android.internal.R.anim.voice_activity_close_exit); + } + + @Nullable + public Animation loadAppTransitionAnimation(String packageName, int resId) { + return loadAnimationRes(packageName, resId); + } + + @Nullable + public Animation loadCrossProfileAppEnterAnimation() { + return loadAnimationRes("android", + com.android.internal.R.anim.task_open_enter_cross_profile_apps); + } + + @Nullable + public Animation loadCrossProfileAppThumbnailEnterAnimation() { + return loadAnimationRes( + "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter); + } + + @Nullable + private Animation loadAnimationRes(LayoutParams lp, int resId) { + Context context = mContext; + if (ResourceId.isValid(resId)) { + AttributeCache.Entry ent = getCachedAnimations(lp); + if (ent != null) { + context = ent.context; + } + return loadAnimationSafely(context, resId, mTag); + } + return null; + } + + @Nullable + private Animation loadAnimationRes(String packageName, int resId) { + if (ResourceId.isValid(resId)) { + AttributeCache.Entry ent = getCachedAnimations(packageName, resId); + if (ent != null) { + return loadAnimationSafely(ent.context, resId, mTag); + } + } + return null; + } + + @Nullable + public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { + int resId = Resources.ID_NULL; + Context context = mContext; + if (animAttr >= 0) { + AttributeCache.Entry ent = getCachedAnimations(lp); + if (ent != null) { + context = ent.context; + resId = ent.array.getResourceId(animAttr, 0); + } + } + resId = updateToTranslucentAnimIfNeeded(resId, transit); + if (ResourceId.isValid(resId)) { + return loadAnimationSafely(context, resId, mTag); + } + return null; + } + + @Nullable + private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { + if (mDebug) { + Slog.v(mTag, "Loading animations: layout params pkg=" + + (lp != null ? lp.packageName : null) + + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); + } + if (lp != null && lp.windowAnimations != 0) { + // If this is a system resource, don't try to load it from the + // application resources. It is nice to avoid loading application + // resources if we can. + String packageName = lp.packageName != null ? lp.packageName : "android"; + int resId = getAnimationStyleResId(lp); + if ((resId & 0xFF000000) == 0x01000000) { + packageName = "android"; + } + if (mDebug) { + Slog.v(mTag, "Loading animations: picked package=" + packageName); + } + return AttributeCache.instance().get(packageName, resId, + com.android.internal.R.styleable.WindowAnimation); + } + return null; + } + + @Nullable + private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { + if (mDebug) { + Slog.v(mTag, "Loading animations: package=" + + packageName + " resId=0x" + Integer.toHexString(resId)); + } + if (packageName != null) { + if ((resId & 0xFF000000) == 0x01000000) { + packageName = "android"; + } + if (mDebug) { + Slog.v(mTag, "Loading animations: picked package=" + + packageName); + } + return AttributeCache.instance().get(packageName, resId, + com.android.internal.R.styleable.WindowAnimation); + } + return null; + } + + /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */ + public int getAnimationStyleResId(@NonNull LayoutParams lp) { + int resId = lp.windowAnimations; + if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) { + // Note that we don't want application to customize starting window animation. + // Since this window is specific for displaying while app starting, + // application should not change its animation directly. + // In this case, it will use system resource to get default animation. + resId = mDefaultWindowAnimationStyleResId; + } + return resId; + } + + public Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets, + Rect startRect) { + setupDefaultNextAppTransitionStartRect(startRect, mTmpFromClipRect); + final int left = mTmpFromClipRect.left; + final int top = mTmpFromClipRect.top; + mTmpFromClipRect.offset(-left, -top); + // TODO: Isn't that strange that we ignore exact position of the containingFrame? + mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height()); + AnimationSet set = new AnimationSet(true); + float fromWidth = mTmpFromClipRect.width(); + float toWidth = mTmpToClipRect.width(); + float fromHeight = mTmpFromClipRect.height(); + // While the window might span the whole display, the actual content will be cropped to the + // system decoration frame, for example when the window is docked. We need to take into + // account the visible height when constructing the animation. + float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom; + int translateAdjustment = 0; + if (fromWidth <= toWidth && fromHeight <= toHeight) { + // The final window is larger in both dimensions than current window (e.g. we are + // maximizing), so we can simply unclip the new window and there will be no disappearing + // frame. + set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)); + } else { + // The disappearing window has one larger dimension. We need to apply scaling, so the + // first frame of the entry animation matches the old window. + set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1)); + // We might not be going exactly full screen, but instead be aligned under the status + // bar using cropping. We still need to account for the cropped part, which will also + // be scaled. + translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight); + } + + // We animate the translation from the old position of the removed window, to the new + // position of the added window. The latter might not be full screen, for example docked for + // docked windows. + TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left, + 0, top - containingFrame.top - translateAdjustment, 0); + set.addAnimation(translate); + set.setDuration(DEFAULT_APP_TRANSITION_DURATION); + set.setZAdjustment(Animation.ZORDER_TOP); + return set; + } + + private void setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect) { + if (startRect == null) { + Slog.e(mTag, "Starting rect for app requested, but none available", new Throwable()); + rect.setEmpty(); + } else { + rect.set(startRect); + } + } + + public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame, + Rect displayFrame, Rect startRect) { + final Animation anim; + if (enter) { + final int appWidth = appFrame.width(); + final int appHeight = appFrame.height(); + + // mTmpRect will contain an area around the launcher icon that was pressed. We will + // clip reveal from that area in the final area of the app. + setupDefaultNextAppTransitionStartRect(startRect, mTmpRect); + + float t = 0f; + if (appHeight > 0) { + t = (float) mTmpRect.top / displayFrame.height(); + } + int translationY = mClipRevealTranslationY + (int) (displayFrame.height() / 7f * t); + int translationX = 0; + int translationYCorrection = translationY; + int centerX = mTmpRect.centerX(); + int centerY = mTmpRect.centerY(); + int halfWidth = mTmpRect.width() / 2; + int halfHeight = mTmpRect.height() / 2; + int clipStartX = centerX - halfWidth - appFrame.left; + int clipStartY = centerY - halfHeight - appFrame.top; + boolean cutOff = false; + + // If the starting rectangle is fully or partially outside of the target rectangle, we + // need to start the clipping at the edge and then achieve the rest with translation + // and extending the clip rect from that edge. + if (appFrame.top > centerY - halfHeight) { + translationY = (centerY - halfHeight) - appFrame.top; + translationYCorrection = 0; + clipStartY = 0; + cutOff = true; + } + if (appFrame.left > centerX - halfWidth) { + translationX = (centerX - halfWidth) - appFrame.left; + clipStartX = 0; + cutOff = true; + } + if (appFrame.right < centerX + halfWidth) { + translationX = (centerX + halfWidth) - appFrame.right; + clipStartX = appWidth - mTmpRect.width(); + cutOff = true; + } + final long duration = calculateClipRevealTransitionDuration(cutOff, translationX, + translationY, displayFrame); + + // Clip third of the from size of launch icon, expand to full width/height + Animation clipAnimLR = new ClipRectLRAnimation( + clipStartX, clipStartX + mTmpRect.width(), 0, appWidth); + clipAnimLR.setInterpolator(mClipHorizontalInterpolator); + clipAnimLR.setDuration((long) (duration / 2.5f)); + + TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0); + translate.setInterpolator(cutOff ? mTouchResponseInterpolator + : mLinearOutSlowInInterpolator); + translate.setDuration(duration); + + Animation clipAnimTB = new ClipRectTBAnimation( + clipStartY, clipStartY + mTmpRect.height(), + 0, appHeight, + translationYCorrection, 0, + mLinearOutSlowInInterpolator); + clipAnimTB.setInterpolator(mTouchResponseInterpolator); + clipAnimTB.setDuration(duration); + + // Quick fade-in from icon to app window + final long alphaDuration = duration / 4; + AlphaAnimation alpha = new AlphaAnimation(0.5f, 1); + alpha.setDuration(alphaDuration); + alpha.setInterpolator(mLinearOutSlowInInterpolator); + + AnimationSet set = new AnimationSet(false); + set.addAnimation(clipAnimLR); + set.addAnimation(clipAnimTB); + set.addAnimation(translate); + set.addAnimation(alpha); + set.setZAdjustment(Animation.ZORDER_TOP); + set.initialize(appWidth, appHeight, appWidth, appHeight); + anim = set; + } else { + final long duration; + switch (transit) { + case TRANSIT_OLD_ACTIVITY_OPEN: + case TRANSIT_OLD_ACTIVITY_CLOSE: + duration = mConfigShortAnimTime; + break; + default: + duration = DEFAULT_APP_TRANSITION_DURATION; + break; + } + if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN + || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) { + // If we are on top of the wallpaper, we need an animation that + // correctly handles the wallpaper staying static behind all of + // the animated elements. To do this, will just have the existing + // element fade out. + anim = new AlphaAnimation(1, 0); + anim.setDetachWallpaper(true); + } else { + // For normal animations, the exiting element just holds in place. + anim = new AlphaAnimation(1, 1); + } + anim.setInterpolator(mDecelerateInterpolator); + anim.setDuration(duration); + anim.setFillAfter(true); + } + return anim; + } + + public Animation createScaleUpAnimationLocked(int transit, boolean enter, + Rect containingFrame, Rect startRect) { + Animation a; + setupDefaultNextAppTransitionStartRect(startRect, mTmpRect); + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); + if (enter) { + // Entering app zooms out from the center of the initial rect. + float scaleW = mTmpRect.width() / (float) appWidth; + float scaleH = mTmpRect.height() / (float) appHeight; + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.top, scaleH)); + scale.setInterpolator(mDecelerateInterpolator); + + Animation alpha = new AlphaAnimation(0, 1); + alpha.setInterpolator(mThumbnailFadeOutInterpolator); + + AnimationSet set = new AnimationSet(false); + set.addAnimation(scale); + set.addAnimation(alpha); + set.setDetachWallpaper(true); + a = set; + } else if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN + || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) { + // If we are on top of the wallpaper, we need an animation that + // correctly handles the wallpaper staying static behind all of + // the animated elements. To do this, will just have the existing + // element fade out. + a = new AlphaAnimation(1, 0); + a.setDetachWallpaper(true); + } else { + // For normal animations, the exiting element just holds in place. + a = new AlphaAnimation(1, 1); + } + + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + final long duration; + switch (transit) { + case TRANSIT_OLD_ACTIVITY_OPEN: + case TRANSIT_OLD_ACTIVITY_CLOSE: + duration = mConfigShortAnimTime; + break; + default: + duration = DEFAULT_APP_TRANSITION_DURATION; + break; + } + a.setDuration(duration); + a.setFillAfter(true); + a.setInterpolator(mDecelerateInterpolator); + a.initialize(appWidth, appHeight, appWidth, appHeight); + return a; + } + + /** + * This animation is created when we are doing a thumbnail transition, for the activity that is + * leaving, and the activity that is entering. + */ + public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, + Rect containingFrame, int transit, HardwareBuffer thumbnailHeader, + Rect startRect) { + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); + Animation a; + setupDefaultNextAppTransitionStartRect(startRect, mTmpRect); + final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth; + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight; + final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + + switch (thumbTransitState) { + case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { + // Entering app scales up with the thumbnail + float scaleW = thumbWidth / appWidth; + float scaleH = thumbHeight / appHeight; + a = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.top, scaleH)); + break; + } + case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { + // Exiting app while the thumbnail is scaling up should fade or stay in place + if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { + // Fade out while bringing up selected activity. This keeps the + // current activity from showing through a launching wallpaper + // activity. + a = new AlphaAnimation(1, 0); + } else { + // noop animation + a = new AlphaAnimation(1, 1); + } + break; + } + case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: { + // Entering the other app, it should just be visible while we scale the thumbnail + // down above it + a = new AlphaAnimation(1, 1); + break; + } + case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { + // Exiting the current app, the app should scale down with the thumbnail + float scaleW = thumbWidth / appWidth; + float scaleH = thumbHeight / appHeight; + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.top, scaleH)); + + Animation alpha = new AlphaAnimation(1, 0); + + AnimationSet set = new AnimationSet(true); + set.addAnimation(scale); + set.addAnimation(alpha); + set.setZAdjustment(Animation.ZORDER_TOP); + a = set; + break; + } + default: + throw new RuntimeException("Invalid thumbnail transition state"); + } + + return prepareThumbnailAnimation(a, appWidth, appHeight, transit); + } + + /** + * This alternate animation is created when we are doing a thumbnail transition, for the + * activity that is leaving, and the activity that is entering. + */ + public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, + int orientation, int transit, Rect containingFrame, Rect contentInsets, + @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform, + Rect startRect, Rect defaultStartRect) { + Animation a; + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); + setupDefaultNextAppTransitionStartRect(defaultStartRect, mTmpRect); + final int thumbWidthI = mTmpRect.width(); + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = mTmpRect.height(); + final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left; + final int thumbStartY = mTmpRect.top - containingFrame.top; + + switch (thumbTransitState) { + case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: + case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { + final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP; + if (freeform && scaleUp) { + a = createAspectScaledThumbnailEnterFreeformAnimationLocked( + containingFrame, surfaceInsets, startRect, defaultStartRect); + } else if (freeform) { + a = createAspectScaledThumbnailExitFreeformAnimationLocked( + containingFrame, surfaceInsets, startRect, defaultStartRect); + } else { + AnimationSet set = new AnimationSet(true); + + // In portrait, we scale to fit the width + mTmpFromClipRect.set(containingFrame); + mTmpToClipRect.set(containingFrame); + + // Containing frame is in screen space, but we need the clip rect in the + // app space. + mTmpFromClipRect.offsetTo(0, 0); + mTmpToClipRect.offsetTo(0, 0); + + // Exclude insets region from the source clip. + mTmpFromClipRect.inset(contentInsets); + + if (shouldScaleDownThumbnailTransition(orientation)) { + // We scale the width and clip to the top/left square + float scale = + thumbWidth / (appWidth - contentInsets.left - contentInsets.right); + if (!mGridLayoutRecentsEnabled) { + int unscaledThumbHeight = (int) (thumbHeight / scale); + mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; + } + + Animation scaleAnim = new ScaleAnimation( + scaleUp ? scale : 1, scaleUp ? 1 : scale, + scaleUp ? scale : 1, scaleUp ? 1 : scale, + containingFrame.width() / 2f, + containingFrame.height() / 2f + contentInsets.top); + final float targetX = (mTmpRect.left - containingFrame.left); + final float x = containingFrame.width() / 2f + - containingFrame.width() / 2f * scale; + final float targetY = (mTmpRect.top - containingFrame.top); + float y = containingFrame.height() / 2f + - containingFrame.height() / 2f * scale; + + // During transition may require clipping offset from any top stable insets + // such as the statusbar height when statusbar is hidden + if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) { + mTmpFromClipRect.top += stableInsets.top; + y += stableInsets.top; + } + final float startX = targetX - x; + final float startY = targetY - y; + Animation clipAnim = scaleUp + ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect) + : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect); + Animation translateAnim = scaleUp + ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0) + : createCurvedMotion(0, startX, 0, startY - contentInsets.top); + + set.addAnimation(clipAnim); + set.addAnimation(scaleAnim); + set.addAnimation(translateAnim); + + } else { + // In landscape, we don't scale at all and only crop + mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI; + mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI; + + Animation clipAnim = scaleUp + ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect) + : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect); + Animation translateAnim = scaleUp + ? createCurvedMotion(thumbStartX, 0, + thumbStartY - contentInsets.top, 0) + : createCurvedMotion(0, thumbStartX, 0, + thumbStartY - contentInsets.top); + + set.addAnimation(clipAnim); + set.addAnimation(translateAnim); + } + a = set; + a.setZAdjustment(Animation.ZORDER_TOP); + } + break; + } + case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { + // Previous app window during the scale up + if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { + // Fade out the source activity if we are animating to a wallpaper + // activity. + a = new AlphaAnimation(1, 0); + } else { + a = new AlphaAnimation(1, 1); + } + break; + } + case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: { + // Target app window during the scale down + if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { + // Fade in the destination activity if we are animating from a wallpaper + // activity. + a = new AlphaAnimation(0, 1); + } else { + a = new AlphaAnimation(1, 1); + } + break; + } + default: + throw new RuntimeException("Invalid thumbnail transition state"); + } + + return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, + THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator); + } + + /** + * Prepares the specified animation with a standard duration, interpolator, etc. + */ + private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, + int transit) { + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + final int duration; + switch (transit) { + case TRANSIT_OLD_ACTIVITY_OPEN: + case TRANSIT_OLD_ACTIVITY_CLOSE: + duration = mConfigShortAnimTime; + break; + default: + duration = DEFAULT_APP_TRANSITION_DURATION; + break; + } + return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration, + mDecelerateInterpolator); + } + + + private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame, + @Nullable Rect surfaceInsets, @Nullable Rect startRect, + @Nullable Rect defaultStartRect) { + getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect); + return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets, + true); + } + + private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame, + @Nullable Rect surfaceInsets, @Nullable Rect startRect, + @Nullable Rect defaultStartRect) { + getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect); + return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets, + false); + } + + private void getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect) { + if (startRect == null && defaultStartRect == null) { + Slog.e(mTag, "Starting rect for container not available", new Throwable()); + rect.setEmpty(); + } else { + rect.set(startRect != null ? startRect : defaultStartRect); + } + } + + private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame, + Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) { + final float sourceWidth = sourceFrame.width(); + final float sourceHeight = sourceFrame.height(); + final float destWidth = destFrame.width(); + final float destHeight = destFrame.height(); + final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth; + final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight; + AnimationSet set = new AnimationSet(true); + final int surfaceInsetsH = surfaceInsets == null + ? 0 : surfaceInsets.left + surfaceInsets.right; + final int surfaceInsetsV = 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. + final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2; + final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2; + final ScaleAnimation scale = enter + ? new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter) + : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter); + final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2; + final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2; + final int destHCenter = destFrame.left + destFrame.width() / 2; + final int destVCenter = destFrame.top + destFrame.height() / 2; + final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter; + final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter; + final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0) + : new TranslateAnimation(0, fromX, 0, fromY); + set.addAnimation(scale); + set.addAnimation(translation); + return set; + } + + /** + * @return whether the transition should show the thumbnail being scaled down. + */ + private boolean shouldScaleDownThumbnailTransition(int orientation) { + return mGridLayoutRecentsEnabled + || orientation == Configuration.ORIENTATION_PORTRAIT; + } + + private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) { + if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN + && anim == R.anim.activity_open_enter) { + return R.anim.activity_translucent_open_enter; + } + if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE + && anim == R.anim.activity_close_exit) { + return R.anim.activity_translucent_close_exit; + } + return anim; + } + + /** + * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that + * the start rect is outside of the target rect, and there is a lot of movement going on. + * + * @param cutOff whether the start rect was not fully contained by the end rect + * @param translationX the total translation the surface moves in x direction + * @param translationY the total translation the surfaces moves in y direction + * @param displayFrame our display frame + * + * @return the duration of the clip reveal animation, in milliseconds + */ + private static long calculateClipRevealTransitionDuration(boolean cutOff, float translationX, + float translationY, Rect displayFrame) { + if (!cutOff) { + return DEFAULT_APP_TRANSITION_DURATION; + } + final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(), + Math.abs(translationY) / displayFrame.height()); + return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction + * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION)); + } + + /** + * Prepares the specified animation with a standard duration, interpolator, etc. + */ + private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, + int appHeight, long duration, Interpolator interpolator) { + if (duration > 0) { + a.setDuration(duration); + } + a.setFillAfter(true); + if (interpolator != null) { + a.setInterpolator(interpolator); + } + a.initialize(appWidth, appHeight, appWidth, appHeight); + return a; + } + + private static Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) { + return new TranslateAnimation(fromX, toX, fromY, toY); + } + + /** + * Compute the pivot point for an animation that is scaling from a small + * rect on screen to a larger rect. The pivot point varies depending on + * the distance between the inner and outer edges on both sides. This + * function computes the pivot point for one dimension. + * @param startPos Offset from left/top edge of outer rectangle to + * left/top edge of inner rectangle. + * @param finalScale The scaling factor between the size of the outer + * and inner rectangles. + */ + public static float computePivot(int startPos, float finalScale) { + + /* + Theorem of intercepting lines: + + + + +-----------------------------------------------+ + | | | | + | | | | + | | | | + | | | | + x | y | | | + | | | | + | | | | + | | | | + | | | | + | + | +--------------------+ | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | | | | + | | +--------------------+ | + | | | + | | | + | | | + | | | + | | | + | | | + | | | + | +-----------------------------------------------+ + | + | + | + | + | + | + | + | + | + + ++ + p ++ + + scale = (x - y) / x + <=> x = -y / (scale - 1) + */ + final float denom = finalScale - 1; + if (Math.abs(denom) < .0001f) { + return startPos; + } + return -startPos / denom; + } + + @Nullable + public static Animation loadAnimationSafely(Context context, int resId, String tag) { + try { + return AnimationUtils.loadAnimation(context, resId); + } catch (Resources.NotFoundException e) { + Slog.w(tag, "Unable to load animation resource", e); + return null; + } + } + + public static Animation createHiddenByKeyguardExit(Context context, + LogDecelerateInterpolator interpolator, boolean onWallpaper, + boolean goingToNotificationShade, boolean subtleAnimation) { + if (goingToNotificationShade) { + return AnimationUtils.loadAnimation(context, R.anim.lock_screen_behind_enter_fade_in); + } + + final int resource; + if (subtleAnimation) { + resource = R.anim.lock_screen_behind_enter_subtle; + } else if (onWallpaper) { + resource = R.anim.lock_screen_behind_enter_wallpaper; + } else { + resource = R.anim.lock_screen_behind_enter; + } + + AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(context, resource); + + // TODO: Use XML interpolators when we have log interpolators available in XML. + final List animations = set.getAnimations(); + for (int i = animations.size() - 1; i >= 0; --i) { + animations.get(i).setInterpolator(interpolator); + } + + return set; + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 05f2fa096e4d0..c1500479e3f80 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -319,6 +319,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -329,7 +330,6 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.HeptFunction; import com.android.internal.util.function.QuadFunction; import com.android.server.AlarmManagerInternal; -import com.android.server.AttributeCache; import com.android.server.DeviceIdleInternal; import com.android.server.DisplayThread; import com.android.server.IntentResolver; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 50aacd677fdc6..12542f6991e31 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -347,6 +347,7 @@ import com.android.internal.content.PackageHelper; import com.android.internal.content.om.OverlayConfig; import com.android.internal.logging.MetricsLogger; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.AttributeCache; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; @@ -355,7 +356,6 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.permission.persistence.RuntimePermissionsPersistence; -import com.android.server.AttributeCache; import com.android.server.DeviceIdleInternal; import com.android.server.EventLogTags; import com.android.server.FgThread; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c073b430c8dff..b0b1ea2664c0e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -188,7 +188,6 @@ import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.autofill.AutofillManagerInternal; @@ -201,7 +200,9 @@ import com.android.internal.os.RoSystemProperties; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyInterceptionInfo; +import com.android.internal.policy.LogDecelerateInterpolator; import com.android.internal.policy.PhoneWindow; +import com.android.internal.policy.TransitionAnimation; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; import com.android.server.ExtconStateObserver; @@ -228,7 +229,6 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.HashSet; -import java.util.List; /** * WindowManagerPolicy implementation for the Android phone UI. This @@ -582,7 +582,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private final SparseArray mFallbackActions = new SparseArray(); - private final LogDecelerateInterpolator mLogDecelerateInterpolator + private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0); private final MutableBoolean mTmpBoolean = new MutableBoolean(false); @@ -2484,28 +2484,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public Animation createHiddenByKeyguardExit(boolean onWallpaper, boolean goingToNotificationShade, boolean subtleAnimation) { - if (goingToNotificationShade) { - return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_behind_enter_fade_in); - } - - final int resource; - if (subtleAnimation) { - resource = R.anim.lock_screen_behind_enter_subtle; - } else if (onWallpaper) { - resource = R.anim.lock_screen_behind_enter_wallpaper; - } else { - resource = R.anim.lock_screen_behind_enter; - } - - AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(mContext, resource); - - // TODO: Use XML interpolators when we have log interpolators available in XML. - final List animations = set.getAnimations(); - for (int i = animations.size() - 1; i >= 0; --i) { - animations.get(i).setInterpolator(mLogDecelerateInterpolator); - } - - return set; + return TransitionAnimation.createHiddenByKeyguardExit(mContext, + mLogDecelerateInterpolator, onWallpaper, goingToNotificationShade, subtleAnimation); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 509cbdef41ff4..639a0f8299af5 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -303,13 +303,13 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.ReferrerIntent; +import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledFunction; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.AttributeCache; import com.android.server.LocalServices; import com.android.server.am.AppTimeTracker; import com.android.server.am.PendingIntentRecord; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f0db3f9855dfb..f38b102afa6a9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -231,13 +231,13 @@ import com.android.internal.app.ProcessMap; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.TransferPipe; +import com.android.internal.policy.AttributeCache; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.AttributeCache; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f8b498797ff30..90070c8f50686 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -78,6 +78,10 @@ import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpe import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation; +import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN; +import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP; +import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN; +import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE; @@ -96,9 +100,6 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; -import android.content.res.ResourceId; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -136,15 +137,12 @@ import android.view.animation.PathInterpolator; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.DumpUtils.Dump; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; -import com.android.server.AttributeCache; -import com.android.server.wm.animation.ClipRectLRAnimation; -import com.android.server.wm.animation.ClipRectTBAnimation; import java.io.PrintWriter; import java.util.ArrayList; @@ -184,6 +182,8 @@ public class AppTransition implements Dump { private final WindowManagerService mService; private final DisplayContent mDisplayContent; + private final TransitionAnimation mTransitionAnimation; + private @TransitionFlags int mNextAppTransitionFlags = 0; private final ArrayList mNextAppTransitionRequests = new ArrayList<>(); private @TransitionOldType int mLastUsedAppTransition = TRANSIT_OLD_UNSET; @@ -212,12 +212,6 @@ public class AppTransition implements Dump { private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; private boolean mNextAppTransitionOverrideRequested; - // These are the possible states for the enter/exit activities during a thumbnail transition - private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; - private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1; - private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2; - private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3; - private String mNextAppTransitionPackage; // Used for thumbnail transitions. True if we're scaling up, false if scaling down private boolean mNextAppTransitionScaleUp; @@ -283,6 +277,7 @@ public class AppTransition implements Dump { mService = service; mHandler = new Handler(service.mH.getLooper()); mDisplayContent = displayContent; + mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.linear_out_slow_in); mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, @@ -555,242 +550,18 @@ public class AppTransition implements Dump { /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */ @VisibleForTesting int getAnimationStyleResId(@NonNull LayoutParams lp) { - int resId = lp.windowAnimations; - if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) { - // Note that we don't want application to customize starting window animation. - // Since this window is specific for displaying while app starting, - // application should not change its animation directly. - // In this case, it will use system resource to get default animation. - resId = mDefaultWindowAnimationStyleResId; - } - return resId; - } - - private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" - + (lp != null ? lp.packageName : null) - + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null)); - if (lp != null && lp.windowAnimations != 0) { - // If this is a system resource, don't try to load it from the - // application resources. It is nice to avoid loading application - // resources if we can. - String packageName = lp.packageName != null ? lp.packageName : "android"; - int resId = getAnimationStyleResId(lp); - if ((resId&0xFF000000) == 0x01000000) { - packageName = "android"; - } - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" - + packageName); - return AttributeCache.instance().get(packageName, resId, - com.android.internal.R.styleable.WindowAnimation, mCurrentUserId); - } - return null; - } - - private AttributeCache.Entry getCachedAnimations(String packageName, int resId) { - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package=" - + packageName + " resId=0x" + Integer.toHexString(resId)); - if (packageName != null) { - if ((resId&0xFF000000) == 0x01000000) { - packageName = "android"; - } - if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package=" - + packageName); - return AttributeCache.instance().get(packageName, resId, - com.android.internal.R.styleable.WindowAnimation, mCurrentUserId); - } - return null; - } - - Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { - int resId = Resources.ID_NULL; - Context context = mContext; - if (animAttr >= 0) { - AttributeCache.Entry ent = getCachedAnimations(lp); - if (ent != null) { - context = ent.context; - resId = ent.array.getResourceId(animAttr, 0); - } - } - resId = updateToTranslucentAnimIfNeeded(resId, transit); - if (ResourceId.isValid(resId)) { - return loadAnimationSafely(context, resId); - } - return null; - } - - private Animation loadAnimationRes(LayoutParams lp, int resId) { - Context context = mContext; - if (ResourceId.isValid(resId)) { - AttributeCache.Entry ent = getCachedAnimations(lp); - if (ent != null) { - context = ent.context; - } - return loadAnimationSafely(context, resId); - } - return null; - } - - private Animation loadAnimationRes(String packageName, int resId) { - if (ResourceId.isValid(resId)) { - AttributeCache.Entry ent = getCachedAnimations(packageName, resId); - if (ent != null) { - return loadAnimationSafely(ent.context, resId); - } - } - return null; + return mTransitionAnimation.getAnimationStyleResId(lp); } @VisibleForTesting + @Nullable Animation loadAnimationSafely(Context context, int resId) { - try { - return AnimationUtils.loadAnimation(context, resId); - } catch (NotFoundException e) { - Slog.w(TAG, "Unable to load animation resource", e); - return null; - } + return TransitionAnimation.loadAnimationSafely(context, resId, TAG); } - private int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) { - if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN - && anim == R.anim.activity_open_enter) { - return R.anim.activity_translucent_open_enter; - } - if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE - && anim == R.anim.activity_close_exit) { - return R.anim.activity_translucent_close_exit; - } - return anim; - } - - /** - * Compute the pivot point for an animation that is scaling from a small - * rect on screen to a larger rect. The pivot point varies depending on - * the distance between the inner and outer edges on both sides. This - * function computes the pivot point for one dimension. - * @param startPos Offset from left/top edge of outer rectangle to - * left/top edge of inner rectangle. - * @param finalScale The scaling factor between the size of the outer - * and inner rectangles. - */ - private static float computePivot(int startPos, float finalScale) { - - /* - Theorem of intercepting lines: - - + + +-----------------------------------------------+ - | | | | - | | | | - | | | | - | | | | - x | y | | | - | | | | - | | | | - | | | | - | | | | - | + | +--------------------+ | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | | | | - | | +--------------------+ | - | | | - | | | - | | | - | | | - | | | - | | | - | | | - | +-----------------------------------------------+ - | - | - | - | - | - | - | - | - | - + ++ - p ++ - - scale = (x - y) / x - <=> x = -y / (scale - 1) - */ - final float denom = finalScale-1; - if (Math.abs(denom) < .0001f) { - return startPos; - } - return -startPos / denom; - } - - private Animation createScaleUpAnimationLocked(int transit, boolean enter, - Rect containingFrame) { - Animation a; - getDefaultNextAppTransitionStartRect(mTmpRect); - final int appWidth = containingFrame.width(); - final int appHeight = containingFrame.height(); - if (enter) { - // Entering app zooms out from the center of the initial rect. - float scaleW = mTmpRect.width() / (float) appWidth; - float scaleH = mTmpRect.height() / (float) appHeight; - Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpRect.left, scaleW), - computePivot(mTmpRect.top, scaleH)); - scale.setInterpolator(mDecelerateInterpolator); - - Animation alpha = new AlphaAnimation(0, 1); - alpha.setInterpolator(mThumbnailFadeOutInterpolator); - - AnimationSet set = new AnimationSet(false); - set.addAnimation(scale); - set.addAnimation(alpha); - set.setDetachWallpaper(true); - a = set; - } else if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN - || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) { - // If we are on top of the wallpaper, we need an animation that - // correctly handles the wallpaper staying static behind all of - // the animated elements. To do this, will just have the existing - // element fade out. - a = new AlphaAnimation(1, 0); - a.setDetachWallpaper(true); - } else { - // For normal animations, the exiting element just holds in place. - a = new AlphaAnimation(1, 1); - } - - // Pick the desired duration. If this is an inter-activity transition, - // it is the standard duration for that. Otherwise we use the longer - // task transition duration. - final long duration; - switch (transit) { - case TRANSIT_OLD_ACTIVITY_OPEN: - case TRANSIT_OLD_ACTIVITY_CLOSE: - duration = mConfigShortAnimTime; - break; - default: - duration = DEFAULT_APP_TRANSITION_DURATION; - break; - } - a.setDuration(duration); - a.setFillAfter(true); - a.setInterpolator(mDecelerateInterpolator); - a.initialize(appWidth, appHeight, appWidth, appHeight); - return a; + @Nullable + Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { + return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit); } private void getDefaultNextAppTransitionStartRect(Rect rect) { @@ -824,27 +595,6 @@ public class AppTransition implements Dump { buffer, new Rect(left, top, left + width, top + height)); } - /** - * @return the duration of the last clip reveal animation - */ - long getLastClipRevealTransitionDuration() { - return mLastClipRevealTransitionDuration; - } - - /** - * @return the maximum distance the app surface is traveling of the last clip reveal animation - */ - int getLastClipRevealMaxTranslation() { - return mLastClipRevealMaxTranslation; - } - - /** - * @return true if in the last app transition had a clip reveal animation, false otherwise - */ - boolean hadClipRevealAnimation() { - return mLastHadClipReveal; - } - /** * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that * the start rect is outside of the target rect, and there is a lot of movement going on. @@ -867,137 +617,21 @@ public class AppTransition implements Dump { (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION)); } - private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame, - Rect displayFrame) { - final Animation anim; - if (enter) { - final int appWidth = appFrame.width(); - final int appHeight = appFrame.height(); - - // mTmpRect will contain an area around the launcher icon that was pressed. We will - // clip reveal from that area in the final area of the app. - getDefaultNextAppTransitionStartRect(mTmpRect); - - float t = 0f; - if (appHeight > 0) { - t = (float) mTmpRect.top / displayFrame.height(); - } - int translationY = mClipRevealTranslationY + (int)(displayFrame.height() / 7f * t); - int translationX = 0; - int translationYCorrection = translationY; - int centerX = mTmpRect.centerX(); - int centerY = mTmpRect.centerY(); - int halfWidth = mTmpRect.width() / 2; - int halfHeight = mTmpRect.height() / 2; - int clipStartX = centerX - halfWidth - appFrame.left; - int clipStartY = centerY - halfHeight - appFrame.top; - boolean cutOff = false; - - // If the starting rectangle is fully or partially outside of the target rectangle, we - // need to start the clipping at the edge and then achieve the rest with translation - // and extending the clip rect from that edge. - if (appFrame.top > centerY - halfHeight) { - translationY = (centerY - halfHeight) - appFrame.top; - translationYCorrection = 0; - clipStartY = 0; - cutOff = true; - } - if (appFrame.left > centerX - halfWidth) { - translationX = (centerX - halfWidth) - appFrame.left; - clipStartX = 0; - cutOff = true; - } - if (appFrame.right < centerX + halfWidth) { - translationX = (centerX + halfWidth) - appFrame.right; - clipStartX = appWidth - mTmpRect.width(); - cutOff = true; - } - final long duration = calculateClipRevealTransitionDuration(cutOff, translationX, - translationY, displayFrame); - - // Clip third of the from size of launch icon, expand to full width/height - Animation clipAnimLR = new ClipRectLRAnimation( - clipStartX, clipStartX + mTmpRect.width(), 0, appWidth); - clipAnimLR.setInterpolator(mClipHorizontalInterpolator); - clipAnimLR.setDuration((long) (duration / 2.5f)); - - TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0); - translate.setInterpolator(cutOff ? TOUCH_RESPONSE_INTERPOLATOR - : mLinearOutSlowInInterpolator); - translate.setDuration(duration); - - Animation clipAnimTB = new ClipRectTBAnimation( - clipStartY, clipStartY + mTmpRect.height(), - 0, appHeight, - translationYCorrection, 0, - mLinearOutSlowInInterpolator); - clipAnimTB.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); - clipAnimTB.setDuration(duration); - - // Quick fade-in from icon to app window - final long alphaDuration = duration / 4; - AlphaAnimation alpha = new AlphaAnimation(0.5f, 1); - alpha.setDuration(alphaDuration); - alpha.setInterpolator(mLinearOutSlowInInterpolator); - - AnimationSet set = new AnimationSet(false); - set.addAnimation(clipAnimLR); - set.addAnimation(clipAnimTB); - set.addAnimation(translate); - set.addAnimation(alpha); - set.setZAdjustment(Animation.ZORDER_TOP); - set.initialize(appWidth, appHeight, appWidth, appHeight); - anim = set; - mLastHadClipReveal = true; - mLastClipRevealTransitionDuration = duration; - - // If the start rect was full inside the target rect (cutOff == false), we don't need - // to store the translation, because it's only used if cutOff == true. - mLastClipRevealMaxTranslation = cutOff - ? Math.max(Math.abs(translationY), Math.abs(translationX)) : 0; - } else { - final long duration; - switch (transit) { - case TRANSIT_OLD_ACTIVITY_OPEN: - case TRANSIT_OLD_ACTIVITY_CLOSE: - duration = mConfigShortAnimTime; - break; - default: - duration = DEFAULT_APP_TRANSITION_DURATION; - break; - } - if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN - || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) { - // If we are on top of the wallpaper, we need an animation that - // correctly handles the wallpaper staying static behind all of - // the animated elements. To do this, will just have the existing - // element fade out. - anim = new AlphaAnimation(1, 0); - anim.setDetachWallpaper(true); - } else { - // For normal animations, the exiting element just holds in place. - anim = new AlphaAnimation(1, 1); - } - anim.setInterpolator(mDecelerateInterpolator); - anim.setDuration(duration); - anim.setFillAfter(true); - } - return anim; - } - /** * Prepares the specified animation with a standard duration, interpolator, etc. */ - Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight, - long duration, Interpolator interpolator) { - if (duration > 0) { - a.setDuration(duration); + Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth, + int appHeight, long duration, Interpolator interpolator) { + if (a != null) { + if (duration > 0) { + a.setDuration(duration); + } + a.setFillAfter(true); + if (interpolator != null) { + a.setInterpolator(interpolator); + } + a.initialize(appWidth, appHeight, appWidth, appHeight); } - a.setFillAfter(true); - if (interpolator != null) { - a.setInterpolator(interpolator); - } - a.initialize(appWidth, appHeight, appWidth, appHeight); return a; } @@ -1069,8 +703,8 @@ public class AppTransition implements Dump { } Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) { - final Animation animation = loadAnimationRes( - "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter); + final Animation animation = + mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation(); return prepareThumbnailAnimationWithDuration(animation, appRect.width(), appRect.height(), 0, null); } @@ -1203,145 +837,6 @@ public class AppTransition implements Dump { return TOUCH_RESPONSE_INTERPOLATOR; } - /** - * This alternate animation is created when we are doing a thumbnail transition, for the - * activity that is leaving, and the activity that is entering. - */ - Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, - int uiMode, int orientation, int transit, Rect containingFrame, Rect contentInsets, - @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform, - WindowContainer container) { - Animation a; - final int appWidth = containingFrame.width(); - final int appHeight = containingFrame.height(); - getDefaultNextAppTransitionStartRect(mTmpRect); - final int thumbWidthI = mTmpRect.width(); - final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = mTmpRect.height(); - final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; - final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left; - final int thumbStartY = mTmpRect.top - containingFrame.top; - - switch (thumbTransitState) { - case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: - case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { - final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP; - if (freeform && scaleUp) { - a = createAspectScaledThumbnailEnterFreeformAnimationLocked( - containingFrame, surfaceInsets, container); - } else if (freeform) { - a = createAspectScaledThumbnailExitFreeformAnimationLocked( - containingFrame, surfaceInsets, container); - } else { - AnimationSet set = new AnimationSet(true); - - // In portrait, we scale to fit the width - mTmpFromClipRect.set(containingFrame); - mTmpToClipRect.set(containingFrame); - - // Containing frame is in screen space, but we need the clip rect in the - // app space. - mTmpFromClipRect.offsetTo(0, 0); - mTmpToClipRect.offsetTo(0, 0); - - // Exclude insets region from the source clip. - mTmpFromClipRect.inset(contentInsets); - mNextAppTransitionInsets.set(contentInsets); - - if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { - // We scale the width and clip to the top/left square - float scale = thumbWidth / - (appWidth - contentInsets.left - contentInsets.right); - if (!mGridLayoutRecentsEnabled) { - int unscaledThumbHeight = (int) (thumbHeight / scale); - mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; - } - - mNextAppTransitionInsets.set(contentInsets); - - Animation scaleAnim = new ScaleAnimation( - scaleUp ? scale : 1, scaleUp ? 1 : scale, - scaleUp ? scale : 1, scaleUp ? 1 : scale, - containingFrame.width() / 2f, - containingFrame.height() / 2f + contentInsets.top); - final float targetX = (mTmpRect.left - containingFrame.left); - final float x = containingFrame.width() / 2f - - containingFrame.width() / 2f * scale; - final float targetY = (mTmpRect.top - containingFrame.top); - float y = containingFrame.height() / 2f - - containingFrame.height() / 2f * scale; - - // During transition may require clipping offset from any top stable insets - // such as the statusbar height when statusbar is hidden - if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) { - mTmpFromClipRect.top += stableInsets.top; - y += stableInsets.top; - } - final float startX = targetX - x; - final float startY = targetY - y; - Animation clipAnim = scaleUp - ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect) - : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect); - Animation translateAnim = scaleUp - ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0) - : createCurvedMotion(0, startX, 0, startY - contentInsets.top); - - set.addAnimation(clipAnim); - set.addAnimation(scaleAnim); - set.addAnimation(translateAnim); - - } else { - // In landscape, we don't scale at all and only crop - mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI; - mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI; - - Animation clipAnim = scaleUp - ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect) - : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect); - Animation translateAnim = scaleUp - ? createCurvedMotion(thumbStartX, 0, - thumbStartY - contentInsets.top, 0) - : createCurvedMotion(0, thumbStartX, 0, - thumbStartY - contentInsets.top); - - set.addAnimation(clipAnim); - set.addAnimation(translateAnim); - } - a = set; - a.setZAdjustment(Animation.ZORDER_TOP); - } - break; - } - case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { - // Previous app window during the scale up - if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { - // Fade out the source activity if we are animating to a wallpaper - // activity. - a = new AlphaAnimation(1, 0); - } else { - a = new AlphaAnimation(1, 1); - } - break; - } - case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: { - // Target app window during the scale down - if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { - // Fade in the destination activity if we are animating from a wallpaper - // activity. - a = new AlphaAnimation(0, 1); - } else { - a = new AlphaAnimation(1, 1); - } - break; - } - default: - throw new RuntimeException("Invalid thumbnail transition state"); - } - - return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, - getAspectScaleDuration(), getAspectScaleInterpolator()); - } - private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame, @Nullable Rect surfaceInsets, WindowContainer container) { getNextAppTransitionStartRect(container, mTmpRect); @@ -1408,8 +903,8 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; float scaleH = appHeight / thumbHeight; Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mTmpRect.left, 1 / scaleW), - computePivot(mTmpRect.top, 1 / scaleH)); + TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW), + TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH)); scale.setInterpolator(mDecelerateInterpolator); Animation alpha = new AlphaAnimation(1, 0); @@ -1425,124 +920,13 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; float scaleH = appHeight / thumbHeight; a = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpRect.left, 1 / scaleW), - computePivot(mTmpRect.top, 1 / scaleH)); + TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW), + TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH)); } return prepareThumbnailAnimation(a, appWidth, appHeight, transit); } - /** - * This animation is created when we are doing a thumbnail transition, for the activity that is - * leaving, and the activity that is entering. - */ - Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame, - int transit, WindowContainer container) { - final int appWidth = containingFrame.width(); - final int appHeight = containingFrame.height(); - final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container); - Animation a; - getDefaultNextAppTransitionStartRect(mTmpRect); - final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth; - final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight; - final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; - - switch (thumbTransitState) { - case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { - // Entering app scales up with the thumbnail - float scaleW = thumbWidth / appWidth; - float scaleH = thumbHeight / appHeight; - a = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpRect.left, scaleW), - computePivot(mTmpRect.top, scaleH)); - break; - } - case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { - // Exiting app while the thumbnail is scaling up should fade or stay in place - if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) { - // Fade out while bringing up selected activity. This keeps the - // current activity from showing through a launching wallpaper - // activity. - a = new AlphaAnimation(1, 0); - } else { - // noop animation - a = new AlphaAnimation(1, 1); - } - break; - } - case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: { - // Entering the other app, it should just be visible while we scale the thumbnail - // down above it - a = new AlphaAnimation(1, 1); - break; - } - case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { - // Exiting the current app, the app should scale down with the thumbnail - float scaleW = thumbWidth / appWidth; - float scaleH = thumbHeight / appHeight; - Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mTmpRect.left, scaleW), - computePivot(mTmpRect.top, scaleH)); - - Animation alpha = new AlphaAnimation(1, 0); - - AnimationSet set = new AnimationSet(true); - set.addAnimation(scale); - set.addAnimation(alpha); - set.setZAdjustment(Animation.ZORDER_TOP); - a = set; - break; - } - default: - throw new RuntimeException("Invalid thumbnail transition state"); - } - - return prepareThumbnailAnimation(a, appWidth, appHeight, transit); - } - - private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) { - getDefaultNextAppTransitionStartRect(mTmpFromClipRect); - final int left = mTmpFromClipRect.left; - final int top = mTmpFromClipRect.top; - mTmpFromClipRect.offset(-left, -top); - // TODO: Isn't that strange that we ignore exact position of the containingFrame? - mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height()); - AnimationSet set = new AnimationSet(true); - float fromWidth = mTmpFromClipRect.width(); - float toWidth = mTmpToClipRect.width(); - float fromHeight = mTmpFromClipRect.height(); - // While the window might span the whole display, the actual content will be cropped to the - // system decoration frame, for example when the window is docked. We need to take into - // account the visible height when constructing the animation. - float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom; - int translateAdjustment = 0; - if (fromWidth <= toWidth && fromHeight <= toHeight) { - // The final window is larger in both dimensions than current window (e.g. we are - // maximizing), so we can simply unclip the new window and there will be no disappearing - // frame. - set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)); - } else { - // The disappearing window has one larger dimension. We need to apply scaling, so the - // first frame of the entry animation matches the old window. - set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1)); - // We might not be going exactly full screen, but instead be aligned under the status - // bar using cropping. We still need to account for the cropped part, which will also - // be scaled. - translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight); - } - - // We animate the translation from the old position of the removed window, to the new - // position of the added window. The latter might not be full screen, for example docked for - // docked windows. - TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left, - 0, top - containingFrame.top - translateAdjustment, 0); - set.addAnimation(translate); - set.setDuration(DEFAULT_APP_TRANSITION_DURATION); - set.setZAdjustment(Animation.ZORDER_TOP); - 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 @@ -1581,6 +965,7 @@ public class AppTransition implements Dump { * to the recents thumbnail and hence need to account for the surface being * bigger. */ + @Nullable Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode, int orientation, Rect frame, Rect displayFrame, Rect insets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction, @@ -1592,57 +977,61 @@ public class AppTransition implements Dump { Animation a; if (isKeyguardGoingAwayTransitOld(transit) && enter) { - a = loadKeyguardExitAnimation(transit); + a = mTransitionAnimation.loadKeyguardExitAnimation(transit, mNextAppTransitionFlags); } else if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) { a = null; } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE && !enter) { - a = loadAnimationRes(lp, com.android.internal.R.anim.wallpaper_open_exit); + a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(lp); } else if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) { a = null; } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_OPEN || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT)) { - a = loadAnimationRes(lp, enter - ? com.android.internal.R.anim.voice_activity_open_enter - : com.android.internal.R.anim.voice_activity_open_exit); + a = mTransitionAnimation.loadVoiceActivityOpenAnimation(lp, enter); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_CLOSE || transit == TRANSIT_OLD_TASK_CLOSE || transit == TRANSIT_OLD_TASK_TO_BACK)) { - a = loadAnimationRes(lp, enter - ? com.android.internal.R.anim.voice_activity_close_enter - : com.android.internal.R.anim.voice_activity_close_exit); + a = mTransitionAnimation.loadVoiceActivityExitAnimation(lp, enter); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (transit == TRANSIT_OLD_ACTIVITY_RELAUNCH) { - a = createRelaunchAnimation(frame, insets); + a = mTransitionAnimation.createRelaunchAnimation(frame, insets, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { - a = loadAnimationRes(mNextAppTransitionPackage, enter ? - mNextAppTransitionEnter : mNextAppTransitionExit); + a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage, + enter ? mNextAppTransitionEnter : mNextAppTransitionExit); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s " + "isEntrance=%b Callers=%s", a, appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) { - a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace); + a = mTransitionAnimation.loadAppTransitionAnimation( + mNextAppTransitionPackage, mNextAppTransitionInPlace); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE " + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { - a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame); + a = mTransitionAnimation.createClipRevealAnimationLocked( + transit, enter, frame, displayFrame, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL " + "transit=%s Callers=%s", a, appTransitionOldToString(transit), Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { - a = createScaleUpAnimationLocked(transit, enter, frame); + a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s " + "isEntrance=%s Callers=%s", @@ -1651,8 +1040,11 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); - a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter), - frame, transit, container); + final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container); + a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked( + getThumbnailTransitionState(enter), frame, transit, thumbnailHeader, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b " + "Callers=%s", @@ -1663,9 +1055,13 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); - a = createAspectScaledThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), uiMode, orientation, transit, frame, - insets, surfaceInsets, stableInsets, freeform, container); + AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get( + container.hashCode()); + a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked( + getThumbnailTransitionState(enter), orientation, transit, frame, + insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null, + mDefaultNextAppTransitionAnimationSpec != null + ? mDefaultNextAppTransitionAnimationSpec.rect : null); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b " + "Callers=%s", @@ -1674,8 +1070,7 @@ public class AppTransition implements Dump { : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN", appTransitionOldToString(transit), enter, Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) { - a = loadAnimationRes("android", - com.android.internal.R.anim.task_open_enter_cross_profile_apps); + a = mTransitionAnimation.loadCrossProfileAppEnterAnimation(); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: " + "anim=%s transit=%s isEntrance=true Callers=%s", @@ -1758,18 +1153,6 @@ public class AppTransition implements Dump { return a; } - private Animation loadKeyguardExitAnimation(int transit) { - if ((mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) { - return null; - } - final boolean toShade = - (mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0; - final boolean subtle = - (mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0; - return mService.mPolicy.createHiddenByKeyguardExit( - transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, toShade, subtle); - } - int getAppRootTaskClipMode() { return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH) || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY) diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 12595af1db7ff..d23d57aa8e4c8 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -97,6 +97,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; +import com.android.internal.policy.AttributeCache; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index c85991d8f3b2a..8b604a32c512e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -88,8 +88,8 @@ import android.view.WindowManager.DisplayImePolicy; import android.window.ITaskOrganizer; import android.window.StartingWindowInfo; +import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; -import com.android.server.AttributeCache; import org.junit.Before; import org.junit.BeforeClass;