• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.policy;
18 
19 import static android.view.WindowManager.TRANSIT_CLOSE;
20 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
21 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
22 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
23 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
24 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
25 import static android.view.WindowManager.TRANSIT_OLD_NONE;
26 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
27 import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
28 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
29 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
30 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
31 import static android.view.WindowManager.TRANSIT_OPEN;
32 
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.app.ActivityManager;
36 import android.content.Context;
37 import android.content.res.Configuration;
38 import android.content.res.ResourceId;
39 import android.content.res.Resources;
40 import android.content.res.TypedArray;
41 import android.graphics.Bitmap;
42 import android.graphics.Canvas;
43 import android.graphics.Color;
44 import android.graphics.Picture;
45 import android.graphics.Rect;
46 import android.graphics.drawable.Drawable;
47 import android.hardware.HardwareBuffer;
48 import android.os.SystemProperties;
49 import android.util.Slog;
50 import android.view.WindowManager.LayoutParams;
51 import android.view.WindowManager.TransitionOldType;
52 import android.view.WindowManager.TransitionType;
53 import android.view.animation.AlphaAnimation;
54 import android.view.animation.Animation;
55 import android.view.animation.AnimationSet;
56 import android.view.animation.AnimationUtils;
57 import android.view.animation.ClipRectAnimation;
58 import android.view.animation.Interpolator;
59 import android.view.animation.PathInterpolator;
60 import android.view.animation.ScaleAnimation;
61 import android.view.animation.TranslateAnimation;
62 
63 import com.android.internal.R;
64 
65 import java.util.List;
66 
67 /** @hide */
68 public class TransitionAnimation {
69     public static final int WALLPAPER_TRANSITION_NONE = 0;
70     public static final int WALLPAPER_TRANSITION_OPEN = 1;
71     public static final int WALLPAPER_TRANSITION_CLOSE = 2;
72     public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
73     public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
74 
75     // These are the possible states for the enter/exit activities during a thumbnail transition
76     private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
77     private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
78     private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
79     private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
80 
81     /**
82      * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
83      * involved, to make it more understandable.
84      */
85     private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
86     private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8;
87     private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
88 
89     public static final int DEFAULT_APP_TRANSITION_DURATION = 336;
90 
91     /** Fraction of animation at which the recents thumbnail stays completely transparent */
92     private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
93     /** Fraction of animation at which the recents thumbnail becomes completely transparent */
94     private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
95 
96     /** Interpolator to be used for animations that respond directly to a touch */
97     static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
98             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
99 
100     private static final String DEFAULT_PACKAGE = "android";
101 
102     private final Context mContext;
103     private final String mTag;
104 
105     private final LogDecelerateInterpolator mInterpolator = new LogDecelerateInterpolator(100, 0);
106     /** Interpolator to be used for animations that respond directly to a touch */
107     private final Interpolator mTouchResponseInterpolator =
108             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
109     private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
110     private final Interpolator mDecelerateInterpolator;
111     private final Interpolator mFastOutLinearInInterpolator;
112     private final Interpolator mLinearOutSlowInInterpolator;
113     private final Interpolator mThumbnailFadeInInterpolator;
114     private final Interpolator mThumbnailFadeOutInterpolator;
115     private final Rect mTmpFromClipRect = new Rect();
116     private final Rect mTmpToClipRect = new Rect();
117     private final Rect mTmpRect = new Rect();
118 
119     private final int mClipRevealTranslationY;
120     private final int mConfigShortAnimTime;
121     private final int mDefaultWindowAnimationStyleResId;
122 
123     private final boolean mDebug;
124     private final boolean mGridLayoutRecentsEnabled;
125     private final boolean mLowRamRecentsEnabled;
126 
TransitionAnimation(Context context, boolean debug, String tag)127     public TransitionAnimation(Context context, boolean debug, String tag) {
128         mContext = context;
129         mDebug = debug;
130         mTag = tag;
131 
132         mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
133                 com.android.internal.R.interpolator.decelerate_cubic);
134         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
135                 com.android.internal.R.interpolator.fast_out_linear_in);
136         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
137                 com.android.internal.R.interpolator.linear_out_slow_in);
138         mThumbnailFadeInInterpolator = input -> {
139             // Linear response for first fraction, then complete after that.
140             if (input < RECENTS_THUMBNAIL_FADEIN_FRACTION) {
141                 return 0f;
142             }
143             float t = (input - RECENTS_THUMBNAIL_FADEIN_FRACTION)
144                     / (1f - RECENTS_THUMBNAIL_FADEIN_FRACTION);
145             return mFastOutLinearInInterpolator.getInterpolation(t);
146         };
147         mThumbnailFadeOutInterpolator = input -> {
148             // Linear response for first fraction, then complete after that.
149             if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
150                 float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
151                 return mLinearOutSlowInInterpolator.getInterpolation(t);
152             }
153             return 1f;
154         };
155 
156         mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP
157                 * mContext.getResources().getDisplayMetrics().density);
158         mConfigShortAnimTime = context.getResources().getInteger(
159                 com.android.internal.R.integer.config_shortAnimTime);
160 
161         mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
162         mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
163 
164         final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
165                 com.android.internal.R.styleable.Window);
166         mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
167                 com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
168         windowStyle.recycle();
169     }
170 
171     /** Loads keyguard animation by transition flags and check it is on wallpaper or not. */
loadKeyguardExitAnimation(int transitionFlags, boolean onWallpaper)172     public Animation loadKeyguardExitAnimation(int transitionFlags, boolean onWallpaper) {
173         if ((transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) {
174             return null;
175         }
176         final boolean toShade =
177                 (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0;
178         final boolean subtle =
179                 (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0;
180         return createHiddenByKeyguardExit(mContext, mInterpolator, onWallpaper, toShade, subtle);
181     }
182 
183     @Nullable
loadKeyguardUnoccludeAnimation()184     public Animation loadKeyguardUnoccludeAnimation() {
185         return loadDefaultAnimationRes(com.android.internal.R.anim.wallpaper_open_exit);
186     }
187 
188     @Nullable
loadVoiceActivityOpenAnimation(boolean enter)189     public Animation loadVoiceActivityOpenAnimation(boolean enter) {
190         return loadDefaultAnimationRes(enter
191                 ? com.android.internal.R.anim.voice_activity_open_enter
192                 : com.android.internal.R.anim.voice_activity_open_exit);
193     }
194 
195     @Nullable
loadVoiceActivityExitAnimation(boolean enter)196     public Animation loadVoiceActivityExitAnimation(boolean enter) {
197         return loadDefaultAnimationRes(enter
198                 ? com.android.internal.R.anim.voice_activity_close_enter
199                 : com.android.internal.R.anim.voice_activity_close_exit);
200     }
201 
202     @Nullable
loadAppTransitionAnimation(String packageName, int resId)203     public Animation loadAppTransitionAnimation(String packageName, int resId) {
204         return loadAnimationRes(packageName, resId);
205     }
206 
207     @Nullable
loadCrossProfileAppEnterAnimation()208     public Animation loadCrossProfileAppEnterAnimation() {
209         return loadAnimationRes(DEFAULT_PACKAGE,
210                 com.android.internal.R.anim.task_open_enter_cross_profile_apps);
211     }
212 
213     @Nullable
loadCrossProfileAppThumbnailEnterAnimation()214     public Animation loadCrossProfileAppThumbnailEnterAnimation() {
215         return loadAnimationRes(
216                 DEFAULT_PACKAGE, com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
217     }
218 
219     @Nullable
createCrossProfileAppsThumbnailAnimationLocked(Rect appRect)220     public Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
221         final Animation animation = loadCrossProfileAppThumbnailEnterAnimation();
222         return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
223                 appRect.height(), 0, null);
224     }
225 
226     /** Load animation by resource Id from specific package. */
227     @Nullable
loadAnimationRes(String packageName, int resId)228     public Animation loadAnimationRes(String packageName, int resId) {
229         if (ResourceId.isValid(resId)) {
230             AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
231             if (ent != null) {
232                 return loadAnimationSafely(ent.context, resId, mTag);
233             }
234         }
235         return null;
236     }
237 
238     /** Load animation by resource Id from android package. */
239     @Nullable
loadDefaultAnimationRes(int resId)240     public Animation loadDefaultAnimationRes(int resId) {
241         return loadAnimationRes(DEFAULT_PACKAGE, resId);
242     }
243 
244     /** Load animation by attribute Id from specific LayoutParams */
245     @Nullable
loadAnimationAttr(LayoutParams lp, int animAttr, int transit)246     public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
247         int resId = Resources.ID_NULL;
248         Context context = mContext;
249         if (animAttr >= 0) {
250             AttributeCache.Entry ent = getCachedAnimations(lp);
251             if (ent != null) {
252                 context = ent.context;
253                 resId = ent.array.getResourceId(animAttr, 0);
254             }
255         }
256         resId = updateToTranslucentAnimIfNeeded(resId, transit);
257         if (ResourceId.isValid(resId)) {
258             return loadAnimationSafely(context, resId, mTag);
259         }
260         return null;
261     }
262     /**
263      * Load animation by attribute Id from a specific AnimationStyle resource.
264      *
265      * @param translucent {@code true} if we're sure that the animation is applied on a translucent
266      *                    window container, {@code false} otherwise.
267      * @param transit {@link TransitionOldType} for the app transition of this animation, or
268      *                {@link TransitionOldType#TRANSIT_OLD_UNSET} if app transition type is unknown.
269      */
270     @Nullable
loadAnimationAttr(String packageName, int animStyleResId, int animAttr, boolean translucent, @TransitionOldType int transit)271     private Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
272             boolean translucent, @TransitionOldType int transit) {
273         if (animStyleResId == 0) {
274             return null;
275         }
276         int resId = Resources.ID_NULL;
277         Context context = mContext;
278         if (animAttr >= 0) {
279             packageName = packageName != null ? packageName : DEFAULT_PACKAGE;
280             AttributeCache.Entry ent = getCachedAnimations(packageName, animStyleResId);
281             if (ent != null) {
282                 context = ent.context;
283                 resId = ent.array.getResourceId(animAttr, 0);
284             }
285         }
286         if (translucent) {
287             resId = updateToTranslucentAnimIfNeeded(resId);
288         } else if (transit != TRANSIT_OLD_UNSET) {
289             resId = updateToTranslucentAnimIfNeeded(resId, transit);
290         }
291         if (ResourceId.isValid(resId)) {
292             return loadAnimationSafely(context, resId, mTag);
293         }
294         return null;
295     }
296 
297 
298     /** Load animation by attribute Id from a specific AnimationStyle resource. */
299     @Nullable
loadAnimationAttr(String packageName, int animStyleResId, int animAttr, boolean translucent)300     public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
301             boolean translucent) {
302         return loadAnimationAttr(packageName, animStyleResId, animAttr, translucent,
303                 TRANSIT_OLD_UNSET);
304     }
305 
306     /** Load animation by attribute Id from android package. */
307     @Nullable
loadDefaultAnimationAttr(int animAttr, boolean translucent)308     public Animation loadDefaultAnimationAttr(int animAttr, boolean translucent) {
309         return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr,
310                 translucent);
311     }
312 
313     /** Load animation by attribute Id from android package. */
314     @Nullable
loadDefaultAnimationAttr(int animAttr, @TransitionOldType int transit)315     public Animation loadDefaultAnimationAttr(int animAttr, @TransitionOldType int transit) {
316         return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr,
317                 false /* translucent */, transit);
318     }
319 
320     @Nullable
getCachedAnimations(LayoutParams lp)321     private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
322         if (mDebug) {
323             Slog.v(mTag, "Loading animations: layout params pkg="
324                     + (lp != null ? lp.packageName : null)
325                     + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
326         }
327         if (lp != null && lp.windowAnimations != 0) {
328             // If this is a system resource, don't try to load it from the
329             // application resources.  It is nice to avoid loading application
330             // resources if we can.
331             String packageName = lp.packageName != null ? lp.packageName : DEFAULT_PACKAGE;
332             int resId = getAnimationStyleResId(lp);
333             if ((resId & 0xFF000000) == 0x01000000) {
334                 packageName = DEFAULT_PACKAGE;
335             }
336             if (mDebug) {
337                 Slog.v(mTag, "Loading animations: picked package=" + packageName);
338             }
339             return AttributeCache.instance().get(packageName, resId,
340                     com.android.internal.R.styleable.WindowAnimation);
341         }
342         return null;
343     }
344 
345     @Nullable
getCachedAnimations(String packageName, int resId)346     private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
347         if (mDebug) {
348             Slog.v(mTag, "Loading animations: package="
349                     + packageName + " resId=0x" + Integer.toHexString(resId));
350         }
351         if (packageName != null) {
352             if ((resId & 0xFF000000) == 0x01000000) {
353                 packageName = DEFAULT_PACKAGE;
354             }
355             if (mDebug) {
356                 Slog.v(mTag, "Loading animations: picked package="
357                         + packageName);
358             }
359             return AttributeCache.instance().get(packageName, resId,
360                     com.android.internal.R.styleable.WindowAnimation);
361         }
362         return null;
363     }
364 
365     /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
getAnimationStyleResId(@onNull LayoutParams lp)366     public int getAnimationStyleResId(@NonNull LayoutParams lp) {
367         int resId = lp.windowAnimations;
368         if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) {
369             // Note that we don't want application to customize starting window animation.
370             // Since this window is specific for displaying while app starting,
371             // application should not change its animation directly.
372             // In this case, it will use system resource to get default animation.
373             resId = mDefaultWindowAnimationStyleResId;
374         }
375         return resId;
376     }
377 
createRelaunchAnimation(Rect containingFrame, Rect contentInsets, Rect startRect)378     public Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets,
379             Rect startRect) {
380         setupDefaultNextAppTransitionStartRect(startRect, mTmpFromClipRect);
381         final int left = mTmpFromClipRect.left;
382         final int top = mTmpFromClipRect.top;
383         mTmpFromClipRect.offset(-left, -top);
384         // TODO: Isn't that strange that we ignore exact position of the containingFrame?
385         mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
386         AnimationSet set = new AnimationSet(true);
387         float fromWidth = mTmpFromClipRect.width();
388         float toWidth = mTmpToClipRect.width();
389         float fromHeight = mTmpFromClipRect.height();
390         // While the window might span the whole display, the actual content will be cropped to the
391         // system decoration frame, for example when the window is docked. We need to take into
392         // account the visible height when constructing the animation.
393         float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
394         int translateAdjustment = 0;
395         if (fromWidth <= toWidth && fromHeight <= toHeight) {
396             // The final window is larger in both dimensions than current window (e.g. we are
397             // maximizing), so we can simply unclip the new window and there will be no disappearing
398             // frame.
399             set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
400         } else {
401             // The disappearing window has one larger dimension. We need to apply scaling, so the
402             // first frame of the entry animation matches the old window.
403             set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
404             // We might not be going exactly full screen, but instead be aligned under the status
405             // bar using cropping. We still need to account for the cropped part, which will also
406             // be scaled.
407             translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
408         }
409 
410         // We animate the translation from the old position of the removed window, to the new
411         // position of the added window. The latter might not be full screen, for example docked for
412         // docked windows.
413         TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
414                 0, top - containingFrame.top - translateAdjustment, 0);
415         set.addAnimation(translate);
416         set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
417         set.setZAdjustment(Animation.ZORDER_TOP);
418         return set;
419     }
420 
setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect)421     private void setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect) {
422         if (startRect == null) {
423             Slog.e(mTag, "Starting rect for app requested, but none available", new Throwable());
424             rect.setEmpty();
425         } else {
426             rect.set(startRect);
427         }
428     }
429 
createClipRevealAnimationLocked(@ransitionType int transit, int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect)430     public Animation createClipRevealAnimationLocked(@TransitionType int transit,
431             int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
432         return createClipRevealAnimationLockedCompat(
433                 getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame,
434                 startRect);
435     }
436 
createClipRevealAnimationLockedCompat(@ransitionOldType int transit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect)437     public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit,
438             boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
439         final Animation anim;
440         if (enter) {
441             final int appWidth = appFrame.width();
442             final int appHeight = appFrame.height();
443 
444             // mTmpRect will contain an area around the launcher icon that was pressed. We will
445             // clip reveal from that area in the final area of the app.
446             setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
447 
448             float t = 0f;
449             if (appHeight > 0) {
450                 t = (float) mTmpRect.top / displayFrame.height();
451             }
452             int translationY = mClipRevealTranslationY + (int) (displayFrame.height() / 7f * t);
453             int translationX = 0;
454             int translationYCorrection = translationY;
455             int centerX = mTmpRect.centerX();
456             int centerY = mTmpRect.centerY();
457             int halfWidth = mTmpRect.width() / 2;
458             int halfHeight = mTmpRect.height() / 2;
459             int clipStartX = centerX - halfWidth - appFrame.left;
460             int clipStartY = centerY - halfHeight - appFrame.top;
461             boolean cutOff = false;
462 
463             // If the starting rectangle is fully or partially outside of the target rectangle, we
464             // need to start the clipping at the edge and then achieve the rest with translation
465             // and extending the clip rect from that edge.
466             if (appFrame.top > centerY - halfHeight) {
467                 translationY = (centerY - halfHeight) - appFrame.top;
468                 translationYCorrection = 0;
469                 clipStartY = 0;
470                 cutOff = true;
471             }
472             if (appFrame.left > centerX - halfWidth) {
473                 translationX = (centerX - halfWidth) - appFrame.left;
474                 clipStartX = 0;
475                 cutOff = true;
476             }
477             if (appFrame.right < centerX + halfWidth) {
478                 translationX = (centerX + halfWidth) - appFrame.right;
479                 clipStartX = appWidth - mTmpRect.width();
480                 cutOff = true;
481             }
482             final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
483                     translationY, displayFrame);
484 
485             // Clip third of the from size of launch icon, expand to full width/height
486             Animation clipAnimLR = new ClipRectLRAnimation(
487                     clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
488             clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
489             clipAnimLR.setDuration((long) (duration / 2.5f));
490 
491             TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
492             translate.setInterpolator(cutOff ? mTouchResponseInterpolator
493                     : mLinearOutSlowInInterpolator);
494             translate.setDuration(duration);
495 
496             Animation clipAnimTB = new ClipRectTBAnimation(
497                     clipStartY, clipStartY + mTmpRect.height(),
498                     0, appHeight,
499                     translationYCorrection, 0,
500                     mLinearOutSlowInInterpolator);
501             clipAnimTB.setInterpolator(mTouchResponseInterpolator);
502             clipAnimTB.setDuration(duration);
503 
504             // Quick fade-in from icon to app window
505             final long alphaDuration = duration / 4;
506             AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
507             alpha.setDuration(alphaDuration);
508             alpha.setInterpolator(mLinearOutSlowInInterpolator);
509 
510             AnimationSet set = new AnimationSet(false);
511             set.addAnimation(clipAnimLR);
512             set.addAnimation(clipAnimTB);
513             set.addAnimation(translate);
514             set.addAnimation(alpha);
515             set.setZAdjustment(Animation.ZORDER_TOP);
516             set.initialize(appWidth, appHeight, appWidth, appHeight);
517             anim = set;
518         } else {
519             final long duration;
520             switch (transit) {
521                 case TRANSIT_OLD_ACTIVITY_OPEN:
522                 case TRANSIT_OLD_ACTIVITY_CLOSE:
523                     duration = mConfigShortAnimTime;
524                     break;
525                 default:
526                     duration = DEFAULT_APP_TRANSITION_DURATION;
527                     break;
528             }
529             if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
530                     || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
531                 // If we are on top of the wallpaper, we need an animation that
532                 // correctly handles the wallpaper staying static behind all of
533                 // the animated elements.  To do this, will just have the existing
534                 // element fade out.
535                 anim = new AlphaAnimation(1, 0);
536                 anim.setDetachWallpaper(true);
537             } else {
538                 // For normal animations, the exiting element just holds in place.
539                 anim = new AlphaAnimation(1, 1);
540             }
541             anim.setInterpolator(mDecelerateInterpolator);
542             anim.setDuration(duration);
543             anim.setFillAfter(true);
544         }
545         return anim;
546     }
547 
createScaleUpAnimationLocked(@ransitionType int transit, int wallpaperTransit, boolean enter, Rect containingFrame, Rect startRect)548     public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit,
549             boolean enter, Rect containingFrame, Rect startRect) {
550         return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit),
551                 enter, containingFrame, startRect);
552     }
553 
createScaleUpAnimationLockedCompat(@ransitionOldType int transit, boolean enter, Rect containingFrame, Rect startRect)554     public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit,
555             boolean enter, Rect containingFrame, Rect startRect) {
556         Animation a;
557         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
558         final int appWidth = containingFrame.width();
559         final int appHeight = containingFrame.height();
560         if (enter) {
561             // Entering app zooms out from the center of the initial rect.
562             float scaleW = mTmpRect.width() / (float) appWidth;
563             float scaleH = mTmpRect.height() / (float) appHeight;
564             Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
565                     computePivot(mTmpRect.left, scaleW),
566                     computePivot(mTmpRect.top, scaleH));
567             scale.setInterpolator(mDecelerateInterpolator);
568 
569             Animation alpha = new AlphaAnimation(0, 1);
570             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
571 
572             AnimationSet set = new AnimationSet(false);
573             set.addAnimation(scale);
574             set.addAnimation(alpha);
575             set.setDetachWallpaper(true);
576             a = set;
577         } else  if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
578                 || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
579             // If we are on top of the wallpaper, we need an animation that
580             // correctly handles the wallpaper staying static behind all of
581             // the animated elements.  To do this, will just have the existing
582             // element fade out.
583             a = new AlphaAnimation(1, 0);
584             a.setDetachWallpaper(true);
585         } else {
586             // For normal animations, the exiting element just holds in place.
587             a = new AlphaAnimation(1, 1);
588         }
589 
590         // Pick the desired duration.  If this is an inter-activity transition,
591         // it  is the standard duration for that.  Otherwise we use the longer
592         // task transition duration.
593         final long duration;
594         switch (transit) {
595             case TRANSIT_OLD_ACTIVITY_OPEN:
596             case TRANSIT_OLD_ACTIVITY_CLOSE:
597                 duration = mConfigShortAnimTime;
598                 break;
599             default:
600                 duration = DEFAULT_APP_TRANSITION_DURATION;
601                 break;
602         }
603         a.setDuration(duration);
604         a.setFillAfter(true);
605         a.setInterpolator(mDecelerateInterpolator);
606         a.initialize(appWidth, appHeight, appWidth, appHeight);
607         return a;
608     }
609 
createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp, Rect containingFrame, @TransitionType int transit, int wallpaperTransit, HardwareBuffer thumbnailHeader, Rect startRect)610     public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp,
611             Rect containingFrame, @TransitionType int transit, int wallpaperTransit,
612             HardwareBuffer thumbnailHeader, Rect startRect) {
613         return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame,
614                 getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect);
615     }
616 
617     /**
618      * This animation is created when we are doing a thumbnail transition, for the activity that is
619      * leaving, and the activity that is entering.
620      */
createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp, Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader, Rect startRect)621     public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp,
622             Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader,
623             Rect startRect) {
624         final int appWidth = containingFrame.width();
625         final int appHeight = containingFrame.height();
626         Animation a;
627         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
628         final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
629         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
630         final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
631         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
632         final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
633 
634         switch (thumbTransitState) {
635             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
636                 // Entering app scales up with the thumbnail
637                 float scaleW = thumbWidth / appWidth;
638                 float scaleH = thumbHeight / appHeight;
639                 a = new ScaleAnimation(scaleW, 1, scaleH, 1,
640                         computePivot(mTmpRect.left, scaleW),
641                         computePivot(mTmpRect.top, scaleH));
642                 break;
643             }
644             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
645                 // Exiting app while the thumbnail is scaling up should fade or stay in place
646                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
647                     // Fade out while bringing up selected activity. This keeps the
648                     // current activity from showing through a launching wallpaper
649                     // activity.
650                     a = new AlphaAnimation(1, 0);
651                 } else {
652                     // noop animation
653                     a = new AlphaAnimation(1, 1);
654                 }
655                 break;
656             }
657             case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
658                 // Entering the other app, it should just be visible while we scale the thumbnail
659                 // down above it
660                 a = new AlphaAnimation(1, 1);
661                 break;
662             }
663             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
664                 // Exiting the current app, the app should scale down with the thumbnail
665                 float scaleW = thumbWidth / appWidth;
666                 float scaleH = thumbHeight / appHeight;
667                 Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
668                         computePivot(mTmpRect.left, scaleW),
669                         computePivot(mTmpRect.top, scaleH));
670 
671                 Animation alpha = new AlphaAnimation(1, 0);
672 
673                 AnimationSet set = new AnimationSet(true);
674                 set.addAnimation(scale);
675                 set.addAnimation(alpha);
676                 set.setZAdjustment(Animation.ZORDER_TOP);
677                 a = set;
678                 break;
679             }
680             default:
681                 throw new RuntimeException("Invalid thumbnail transition state");
682         }
683 
684         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
685     }
686 
687     /**
688      * This alternate animation is created when we are doing a thumbnail transition, for the
689      * activity that is leaving, and the activity that is entering.
690      */
createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets, @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform, Rect startRect, Rect defaultStartRect)691     public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter,
692             boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets,
693             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
694             Rect startRect, Rect defaultStartRect) {
695         Animation a;
696         final int appWidth = containingFrame.width();
697         final int appHeight = containingFrame.height();
698         setupDefaultNextAppTransitionStartRect(defaultStartRect, mTmpRect);
699         final int thumbWidthI = mTmpRect.width();
700         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
701         final int thumbHeightI = mTmpRect.height();
702         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
703         final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
704         final int thumbStartY = mTmpRect.top - containingFrame.top;
705         final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
706 
707         switch (thumbTransitState) {
708             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
709             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
710                 if (freeform && scaleUp) {
711                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
712                             containingFrame, surfaceInsets, startRect, defaultStartRect);
713                 } else if (freeform) {
714                     a = createAspectScaledThumbnailExitFreeformAnimationLocked(
715                             containingFrame, surfaceInsets, startRect, defaultStartRect);
716                 } else {
717                     AnimationSet set = new AnimationSet(true);
718 
719                     // In portrait, we scale to fit the width
720                     mTmpFromClipRect.set(containingFrame);
721                     mTmpToClipRect.set(containingFrame);
722 
723                     // Containing frame is in screen space, but we need the clip rect in the
724                     // app space.
725                     mTmpFromClipRect.offsetTo(0, 0);
726                     mTmpToClipRect.offsetTo(0, 0);
727 
728                     // Exclude insets region from the source clip.
729                     mTmpFromClipRect.inset(contentInsets);
730 
731                     if (shouldScaleDownThumbnailTransition(orientation)) {
732                         // We scale the width and clip to the top/left square
733                         float scale =
734                                 thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
735                         if (!mGridLayoutRecentsEnabled) {
736                             int unscaledThumbHeight = (int) (thumbHeight / scale);
737                             mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
738                         }
739 
740                         Animation scaleAnim = new ScaleAnimation(
741                                 scaleUp ? scale : 1, scaleUp ? 1 : scale,
742                                 scaleUp ? scale : 1, scaleUp ? 1 : scale,
743                                 containingFrame.width() / 2f,
744                                 containingFrame.height() / 2f + contentInsets.top);
745                         final float targetX = (mTmpRect.left - containingFrame.left);
746                         final float x = containingFrame.width() / 2f
747                                 - containingFrame.width() / 2f * scale;
748                         final float targetY = (mTmpRect.top - containingFrame.top);
749                         float y = containingFrame.height() / 2f
750                                 - containingFrame.height() / 2f * scale;
751 
752                         // During transition may require clipping offset from any top stable insets
753                         // such as the statusbar height when statusbar is hidden
754                         if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) {
755                             mTmpFromClipRect.top += stableInsets.top;
756                             y += stableInsets.top;
757                         }
758                         final float startX = targetX - x;
759                         final float startY = targetY - y;
760                         Animation clipAnim = scaleUp
761                                 ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
762                                 : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
763                         Animation translateAnim = scaleUp
764                                 ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
765                                 : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
766 
767                         set.addAnimation(clipAnim);
768                         set.addAnimation(scaleAnim);
769                         set.addAnimation(translateAnim);
770 
771                     } else {
772                         // In landscape, we don't scale at all and only crop
773                         mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
774                         mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
775 
776                         Animation clipAnim = scaleUp
777                                 ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
778                                 : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
779                         Animation translateAnim = scaleUp
780                                 ? createCurvedMotion(thumbStartX, 0,
781                                 thumbStartY - contentInsets.top, 0)
782                                 : createCurvedMotion(0, thumbStartX, 0,
783                                         thumbStartY - contentInsets.top);
784 
785                         set.addAnimation(clipAnim);
786                         set.addAnimation(translateAnim);
787                     }
788                     a = set;
789                     a.setZAdjustment(Animation.ZORDER_TOP);
790                 }
791                 break;
792             }
793             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
794                 // Previous app window during the scale up
795                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
796                     // Fade out the source activity if we are animating to a wallpaper
797                     // activity.
798                     a = new AlphaAnimation(1, 0);
799                 } else {
800                     a = new AlphaAnimation(1, 1);
801                 }
802                 break;
803             }
804             case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
805                 // Target app window during the scale down
806                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
807                     // Fade in the destination activity if we are animating from a wallpaper
808                     // activity.
809                     a = new AlphaAnimation(0, 1);
810                 } else {
811                     a = new AlphaAnimation(1, 1);
812                 }
813                 break;
814             }
815             default:
816                 throw new RuntimeException("Invalid thumbnail transition state");
817         }
818 
819         return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
820                 THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator);
821     }
822 
823     /**
824      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
825      * when a thumbnail is specified with the pending animation override.
826      */
createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation, Rect startRect, Rect defaultStartRect, boolean scaleUp)827     public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
828             @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
829             Rect startRect, Rect defaultStartRect, boolean scaleUp) {
830         Animation a;
831         final int thumbWidthI = thumbnailHeader.getWidth();
832         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
833         final int thumbHeightI = thumbnailHeader.getHeight();
834         final int appWidth = appRect.width();
835 
836         float scaleW = appWidth / thumbWidth;
837         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
838         final float fromX;
839         float fromY;
840         final float toX;
841         float toY;
842         final float pivotX;
843         final float pivotY;
844         if (shouldScaleDownThumbnailTransition(orientation)) {
845             fromX = mTmpRect.left;
846             fromY = mTmpRect.top;
847 
848             // For the curved translate animation to work, the pivot points needs to be at the
849             // same absolute position as the one from the real surface.
850             toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
851             toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
852             pivotX = mTmpRect.width() / 2;
853             pivotY = appRect.height() / 2 / scaleW;
854             if (mGridLayoutRecentsEnabled) {
855                 // In the grid layout, the header is displayed above the thumbnail instead of
856                 // overlapping it.
857                 fromY -= thumbHeightI;
858                 toY -= thumbHeightI * scaleW;
859             }
860         } else {
861             pivotX = 0;
862             pivotY = 0;
863             fromX = mTmpRect.left;
864             fromY = mTmpRect.top;
865             toX = appRect.left;
866             toY = appRect.top;
867         }
868         if (scaleUp) {
869             // Animation up from the thumbnail to the full screen
870             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
871             scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
872             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
873             Animation alpha = new AlphaAnimation(1f, 0f);
874             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
875             alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
876             Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
877             translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
878             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
879 
880             mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
881             mTmpToClipRect.set(appRect);
882 
883             // Containing frame is in screen space, but we need the clip rect in the
884             // app space.
885             mTmpToClipRect.offsetTo(0, 0);
886             mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
887             mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
888 
889             if (contentInsets != null) {
890                 mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
891                         (int) (-contentInsets.top * scaleW),
892                         (int) (-contentInsets.right * scaleW),
893                         (int) (-contentInsets.bottom * scaleW));
894             }
895 
896             Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
897             clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
898             clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
899 
900             // This AnimationSet uses the Interpolators assigned above.
901             AnimationSet set = new AnimationSet(false);
902             set.addAnimation(scale);
903             if (!mGridLayoutRecentsEnabled) {
904                 // In the grid layout, the header should be shown for the whole animation.
905                 set.addAnimation(alpha);
906             }
907             set.addAnimation(translate);
908             set.addAnimation(clipAnim);
909             a = set;
910         } else {
911             // Animation down from the full screen to the thumbnail
912             Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
913             scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
914             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
915             Animation alpha = new AlphaAnimation(0f, 1f);
916             alpha.setInterpolator(mThumbnailFadeInInterpolator);
917             alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
918             Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
919             translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
920             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
921 
922             // This AnimationSet uses the Interpolators assigned above.
923             AnimationSet set = new AnimationSet(false);
924             set.addAnimation(scale);
925             if (!mGridLayoutRecentsEnabled) {
926                 // In the grid layout, the header should be shown for the whole animation.
927                 set.addAnimation(alpha);
928             }
929             set.addAnimation(translate);
930             a = set;
931 
932         }
933         return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
934                 null);
935     }
936 
937     /**
938      * Creates an overlay with a background color and a thumbnail for the cross profile apps
939      * animation.
940      */
createCrossProfileAppsThumbnail( Drawable thumbnailDrawable, Rect frame)941     public HardwareBuffer createCrossProfileAppsThumbnail(
942             Drawable thumbnailDrawable, Rect frame) {
943         final int width = frame.width();
944         final int height = frame.height();
945 
946         final Picture picture = new Picture();
947         final Canvas canvas = picture.beginRecording(width, height);
948         canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
949         final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
950                 com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
951         thumbnailDrawable.setBounds(
952                 (width - thumbnailSize) / 2,
953                 (height - thumbnailSize) / 2,
954                 (width + thumbnailSize) / 2,
955                 (height + thumbnailSize) / 2);
956         thumbnailDrawable.setTint(mContext.getColor(android.R.color.white));
957         thumbnailDrawable.draw(canvas);
958         picture.endRecording();
959 
960         return Bitmap.createBitmap(picture).getHardwareBuffer();
961     }
962 
963     /**
964      * Prepares the specified animation with a standard duration, interpolator, etc.
965      */
prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, @TransitionOldType int transit)966     private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
967             @TransitionOldType int transit) {
968         // Pick the desired duration.  If this is an inter-activity transition,
969         // it  is the standard duration for that.  Otherwise we use the longer
970         // task transition duration.
971         final int duration;
972         switch (transit) {
973             case TRANSIT_OLD_ACTIVITY_OPEN:
974             case TRANSIT_OLD_ACTIVITY_CLOSE:
975                 duration = mConfigShortAnimTime;
976                 break;
977             default:
978                 duration = DEFAULT_APP_TRANSITION_DURATION;
979                 break;
980         }
981         return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
982                 mDecelerateInterpolator);
983     }
984 
985 
createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame, @Nullable Rect surfaceInsets, @Nullable Rect startRect, @Nullable Rect defaultStartRect)986     private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
987             @Nullable Rect surfaceInsets, @Nullable Rect startRect,
988             @Nullable Rect defaultStartRect) {
989         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
990         return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
991                 true);
992     }
993 
createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame, @Nullable Rect surfaceInsets, @Nullable Rect startRect, @Nullable Rect defaultStartRect)994     private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
995             @Nullable Rect surfaceInsets, @Nullable Rect startRect,
996             @Nullable Rect defaultStartRect) {
997         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
998         return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
999                 false);
1000     }
1001 
getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect)1002     private void getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect) {
1003         if (startRect == null && defaultStartRect == null) {
1004             Slog.e(mTag, "Starting rect for container not available", new Throwable());
1005             rect.setEmpty();
1006         } else {
1007             rect.set(startRect != null ? startRect : defaultStartRect);
1008         }
1009     }
1010 
createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame, Rect destFrame, @Nullable Rect surfaceInsets, boolean enter)1011     private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
1012             Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
1013         final float sourceWidth = sourceFrame.width();
1014         final float sourceHeight = sourceFrame.height();
1015         final float destWidth = destFrame.width();
1016         final float destHeight = destFrame.height();
1017         final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
1018         final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
1019         AnimationSet set = new AnimationSet(true);
1020         final int surfaceInsetsH = surfaceInsets == null
1021                 ? 0 : surfaceInsets.left + surfaceInsets.right;
1022         final int surfaceInsetsV = surfaceInsets == null
1023                 ? 0 : surfaceInsets.top + surfaceInsets.bottom;
1024         // We want the scaling to happen from the center of the surface. In order to achieve that,
1025         // we need to account for surface insets that will be used to enlarge the surface.
1026         final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
1027         final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
1028         final ScaleAnimation scale = enter
1029                 ? new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
1030                 : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
1031         final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
1032         final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
1033         final int destHCenter = destFrame.left + destFrame.width() / 2;
1034         final int destVCenter = destFrame.top + destFrame.height() / 2;
1035         final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
1036         final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
1037         final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
1038                 : new TranslateAnimation(0, fromX, 0, fromY);
1039         set.addAnimation(scale);
1040         set.addAnimation(translation);
1041         return set;
1042     }
1043 
1044     /**
1045      * @return whether the transition should show the thumbnail being scaled down.
1046      */
shouldScaleDownThumbnailTransition(int orientation)1047     private boolean shouldScaleDownThumbnailTransition(int orientation) {
1048         return mGridLayoutRecentsEnabled
1049                 || orientation == Configuration.ORIENTATION_PORTRAIT;
1050     }
1051 
updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit)1052     private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
1053         if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN
1054                 && anim == R.anim.activity_open_enter) {
1055             return R.anim.activity_translucent_open_enter;
1056         }
1057         if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
1058                 && anim == R.anim.activity_close_exit) {
1059             return R.anim.activity_translucent_close_exit;
1060         }
1061         return anim;
1062     }
1063 
updateToTranslucentAnimIfNeeded(int anim)1064     private static int updateToTranslucentAnimIfNeeded(int anim) {
1065         if (anim == R.anim.activity_open_enter) {
1066             return R.anim.activity_translucent_open_enter;
1067         }
1068         if (anim == R.anim.activity_close_exit) {
1069             return R.anim.activity_translucent_close_exit;
1070         }
1071         return anim;
1072     }
1073 
getTransitCompatType(@ransitionType int transit, int wallpaperTransit)1074     private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
1075             int wallpaperTransit) {
1076         if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
1077             return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
1078         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
1079             return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
1080         } else if (transit == TRANSIT_OPEN) {
1081             return TRANSIT_OLD_ACTIVITY_OPEN;
1082         } else if (transit == TRANSIT_CLOSE) {
1083             return TRANSIT_OLD_ACTIVITY_CLOSE;
1084         }
1085 
1086         // We only do some special handle for above type, so use type NONE for default behavior.
1087         return TRANSIT_OLD_NONE;
1088     }
1089 
1090     /**
1091      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
1092      * the start rect is outside of the target rect, and there is a lot of movement going on.
1093      *
1094      * @param cutOff whether the start rect was not fully contained by the end rect
1095      * @param translationX the total translation the surface moves in x direction
1096      * @param translationY the total translation the surfaces moves in y direction
1097      * @param displayFrame our display frame
1098      *
1099      * @return the duration of the clip reveal animation, in milliseconds
1100      */
calculateClipRevealTransitionDuration(boolean cutOff, float translationX, float translationY, Rect displayFrame)1101     private static long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
1102             float translationY, Rect displayFrame) {
1103         if (!cutOff) {
1104             return DEFAULT_APP_TRANSITION_DURATION;
1105         }
1106         final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
1107                 Math.abs(translationY) / displayFrame.height());
1108         return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction
1109                 * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
1110     }
1111 
1112     /**
1113      * Return the current thumbnail transition state.
1114      */
getThumbnailTransitionState(boolean enter, boolean scaleUp)1115     private int getThumbnailTransitionState(boolean enter, boolean scaleUp) {
1116         if (enter) {
1117             if (scaleUp) {
1118                 return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
1119             } else {
1120                 return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
1121             }
1122         } else {
1123             if (scaleUp) {
1124                 return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
1125             } else {
1126                 return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * Prepares the specified animation with a standard duration, interpolator, etc.
1133      */
prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight, long duration, Interpolator interpolator)1134     public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
1135             int appHeight, long duration, Interpolator interpolator) {
1136         if (a == null) {
1137             return null;
1138         }
1139 
1140         if (duration > 0) {
1141             a.setDuration(duration);
1142         }
1143         a.setFillAfter(true);
1144         if (interpolator != null) {
1145             a.setInterpolator(interpolator);
1146         }
1147         a.initialize(appWidth, appHeight, appWidth, appHeight);
1148         return a;
1149     }
1150 
createCurvedMotion(float fromX, float toX, float fromY, float toY)1151     private static Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
1152         return new TranslateAnimation(fromX, toX, fromY, toY);
1153     }
1154 
1155     /**
1156      * Compute the pivot point for an animation that is scaling from a small
1157      * rect on screen to a larger rect.  The pivot point varies depending on
1158      * the distance between the inner and outer edges on both sides.  This
1159      * function computes the pivot point for one dimension.
1160      * @param startPos  Offset from left/top edge of outer rectangle to
1161      * left/top edge of inner rectangle.
1162      * @param finalScale The scaling factor between the size of the outer
1163      * and inner rectangles.
1164      */
computePivot(int startPos, float finalScale)1165     public static float computePivot(int startPos, float finalScale) {
1166 
1167         /*
1168         Theorem of intercepting lines:
1169 
1170           +      +   +-----------------------------------------------+
1171           |      |   |                                               |
1172           |      |   |                                               |
1173           |      |   |                                               |
1174           |      |   |                                               |
1175         x |    y |   |                                               |
1176           |      |   |                                               |
1177           |      |   |                                               |
1178           |      |   |                                               |
1179           |      |   |                                               |
1180           |      +   |             +--------------------+            |
1181           |          |             |                    |            |
1182           |          |             |                    |            |
1183           |          |             |                    |            |
1184           |          |             |                    |            |
1185           |          |             |                    |            |
1186           |          |             |                    |            |
1187           |          |             |                    |            |
1188           |          |             |                    |            |
1189           |          |             |                    |            |
1190           |          |             |                    |            |
1191           |          |             |                    |            |
1192           |          |             |                    |            |
1193           |          |             |                    |            |
1194           |          |             |                    |            |
1195           |          |             |                    |            |
1196           |          |             |                    |            |
1197           |          |             |                    |            |
1198           |          |             +--------------------+            |
1199           |          |                                               |
1200           |          |                                               |
1201           |          |                                               |
1202           |          |                                               |
1203           |          |                                               |
1204           |          |                                               |
1205           |          |                                               |
1206           |          +-----------------------------------------------+
1207           |
1208           |
1209           |
1210           |
1211           |
1212           |
1213           |
1214           |
1215           |
1216           +                                 ++
1217                                          p  ++
1218 
1219         scale = (x - y) / x
1220         <=> x = -y / (scale - 1)
1221         */
1222         final float denom = finalScale - 1;
1223         if (Math.abs(denom) < .0001f) {
1224             return startPos;
1225         }
1226         return -startPos / denom;
1227     }
1228 
1229     @Nullable
loadAnimationSafely(Context context, int resId, String tag)1230     public static Animation loadAnimationSafely(Context context, int resId, String tag) {
1231         try {
1232             return AnimationUtils.loadAnimation(context, resId);
1233         } catch (Resources.NotFoundException e) {
1234             Slog.w(tag, "Unable to load animation resource", e);
1235             return null;
1236         }
1237     }
1238 
createHiddenByKeyguardExit(Context context, LogDecelerateInterpolator interpolator, boolean onWallpaper, boolean goingToNotificationShade, boolean subtleAnimation)1239     public static Animation createHiddenByKeyguardExit(Context context,
1240             LogDecelerateInterpolator interpolator, boolean onWallpaper,
1241             boolean goingToNotificationShade, boolean subtleAnimation) {
1242         if (goingToNotificationShade) {
1243             return AnimationUtils.loadAnimation(context, R.anim.lock_screen_behind_enter_fade_in);
1244         }
1245 
1246         final int resource;
1247         if (subtleAnimation) {
1248             resource = R.anim.lock_screen_behind_enter_subtle;
1249         } else if (onWallpaper) {
1250             resource = R.anim.lock_screen_behind_enter_wallpaper;
1251         } else  {
1252             resource = R.anim.lock_screen_behind_enter;
1253         }
1254 
1255         AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(context, resource);
1256 
1257         // TODO: Use XML interpolators when we have log interpolators available in XML.
1258         final List<Animation> animations = set.getAnimations();
1259         for (int i = animations.size() - 1; i >= 0; --i) {
1260             animations.get(i).setInterpolator(interpolator);
1261         }
1262 
1263         return set;
1264     }
1265 }
1266