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_WALLPAPER_INTRA_CLOSE;
29 import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
30 import static android.view.WindowManager.TRANSIT_OPEN;
31 
32 import android.annotation.DrawableRes;
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 android package. */
264     @Nullable
loadDefaultAnimationAttr(int animAttr)265     public Animation loadDefaultAnimationAttr(int animAttr) {
266         int resId = Resources.ID_NULL;
267         Context context = mContext;
268         if (animAttr >= 0) {
269             AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE,
270                     mDefaultWindowAnimationStyleResId);
271             if (ent != null) {
272                 context = ent.context;
273                 resId = ent.array.getResourceId(animAttr, 0);
274             }
275         }
276         if (ResourceId.isValid(resId)) {
277             return loadAnimationSafely(context, resId, mTag);
278         }
279         return null;
280     }
281 
282     @Nullable
getCachedAnimations(LayoutParams lp)283     private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
284         if (mDebug) {
285             Slog.v(mTag, "Loading animations: layout params pkg="
286                     + (lp != null ? lp.packageName : null)
287                     + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
288         }
289         if (lp != null && lp.windowAnimations != 0) {
290             // If this is a system resource, don't try to load it from the
291             // application resources.  It is nice to avoid loading application
292             // resources if we can.
293             String packageName = lp.packageName != null ? lp.packageName : DEFAULT_PACKAGE;
294             int resId = getAnimationStyleResId(lp);
295             if ((resId & 0xFF000000) == 0x01000000) {
296                 packageName = DEFAULT_PACKAGE;
297             }
298             if (mDebug) {
299                 Slog.v(mTag, "Loading animations: picked package=" + packageName);
300             }
301             return AttributeCache.instance().get(packageName, resId,
302                     com.android.internal.R.styleable.WindowAnimation);
303         }
304         return null;
305     }
306 
307     @Nullable
getCachedAnimations(String packageName, int resId)308     private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
309         if (mDebug) {
310             Slog.v(mTag, "Loading animations: package="
311                     + packageName + " resId=0x" + Integer.toHexString(resId));
312         }
313         if (packageName != null) {
314             if ((resId & 0xFF000000) == 0x01000000) {
315                 packageName = DEFAULT_PACKAGE;
316             }
317             if (mDebug) {
318                 Slog.v(mTag, "Loading animations: picked package="
319                         + packageName);
320             }
321             return AttributeCache.instance().get(packageName, resId,
322                     com.android.internal.R.styleable.WindowAnimation);
323         }
324         return null;
325     }
326 
327     /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
getAnimationStyleResId(@onNull LayoutParams lp)328     public int getAnimationStyleResId(@NonNull LayoutParams lp) {
329         int resId = lp.windowAnimations;
330         if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) {
331             // Note that we don't want application to customize starting window animation.
332             // Since this window is specific for displaying while app starting,
333             // application should not change its animation directly.
334             // In this case, it will use system resource to get default animation.
335             resId = mDefaultWindowAnimationStyleResId;
336         }
337         return resId;
338     }
339 
createRelaunchAnimation(Rect containingFrame, Rect contentInsets, Rect startRect)340     public Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets,
341             Rect startRect) {
342         setupDefaultNextAppTransitionStartRect(startRect, mTmpFromClipRect);
343         final int left = mTmpFromClipRect.left;
344         final int top = mTmpFromClipRect.top;
345         mTmpFromClipRect.offset(-left, -top);
346         // TODO: Isn't that strange that we ignore exact position of the containingFrame?
347         mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
348         AnimationSet set = new AnimationSet(true);
349         float fromWidth = mTmpFromClipRect.width();
350         float toWidth = mTmpToClipRect.width();
351         float fromHeight = mTmpFromClipRect.height();
352         // While the window might span the whole display, the actual content will be cropped to the
353         // system decoration frame, for example when the window is docked. We need to take into
354         // account the visible height when constructing the animation.
355         float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
356         int translateAdjustment = 0;
357         if (fromWidth <= toWidth && fromHeight <= toHeight) {
358             // The final window is larger in both dimensions than current window (e.g. we are
359             // maximizing), so we can simply unclip the new window and there will be no disappearing
360             // frame.
361             set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
362         } else {
363             // The disappearing window has one larger dimension. We need to apply scaling, so the
364             // first frame of the entry animation matches the old window.
365             set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
366             // We might not be going exactly full screen, but instead be aligned under the status
367             // bar using cropping. We still need to account for the cropped part, which will also
368             // be scaled.
369             translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
370         }
371 
372         // We animate the translation from the old position of the removed window, to the new
373         // position of the added window. The latter might not be full screen, for example docked for
374         // docked windows.
375         TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
376                 0, top - containingFrame.top - translateAdjustment, 0);
377         set.addAnimation(translate);
378         set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
379         set.setZAdjustment(Animation.ZORDER_TOP);
380         return set;
381     }
382 
setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect)383     private void setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect) {
384         if (startRect == null) {
385             Slog.e(mTag, "Starting rect for app requested, but none available", new Throwable());
386             rect.setEmpty();
387         } else {
388             rect.set(startRect);
389         }
390     }
391 
createClipRevealAnimationLocked(@ransitionType int transit, int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect)392     public Animation createClipRevealAnimationLocked(@TransitionType int transit,
393             int wallpaperTransit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
394         return createClipRevealAnimationLockedCompat(
395                 getTransitCompatType(transit, wallpaperTransit), enter, appFrame, displayFrame,
396                 startRect);
397     }
398 
createClipRevealAnimationLockedCompat(@ransitionOldType int transit, boolean enter, Rect appFrame, Rect displayFrame, Rect startRect)399     public Animation createClipRevealAnimationLockedCompat(@TransitionOldType int transit,
400             boolean enter, Rect appFrame, Rect displayFrame, Rect startRect) {
401         final Animation anim;
402         if (enter) {
403             final int appWidth = appFrame.width();
404             final int appHeight = appFrame.height();
405 
406             // mTmpRect will contain an area around the launcher icon that was pressed. We will
407             // clip reveal from that area in the final area of the app.
408             setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
409 
410             float t = 0f;
411             if (appHeight > 0) {
412                 t = (float) mTmpRect.top / displayFrame.height();
413             }
414             int translationY = mClipRevealTranslationY + (int) (displayFrame.height() / 7f * t);
415             int translationX = 0;
416             int translationYCorrection = translationY;
417             int centerX = mTmpRect.centerX();
418             int centerY = mTmpRect.centerY();
419             int halfWidth = mTmpRect.width() / 2;
420             int halfHeight = mTmpRect.height() / 2;
421             int clipStartX = centerX - halfWidth - appFrame.left;
422             int clipStartY = centerY - halfHeight - appFrame.top;
423             boolean cutOff = false;
424 
425             // If the starting rectangle is fully or partially outside of the target rectangle, we
426             // need to start the clipping at the edge and then achieve the rest with translation
427             // and extending the clip rect from that edge.
428             if (appFrame.top > centerY - halfHeight) {
429                 translationY = (centerY - halfHeight) - appFrame.top;
430                 translationYCorrection = 0;
431                 clipStartY = 0;
432                 cutOff = true;
433             }
434             if (appFrame.left > centerX - halfWidth) {
435                 translationX = (centerX - halfWidth) - appFrame.left;
436                 clipStartX = 0;
437                 cutOff = true;
438             }
439             if (appFrame.right < centerX + halfWidth) {
440                 translationX = (centerX + halfWidth) - appFrame.right;
441                 clipStartX = appWidth - mTmpRect.width();
442                 cutOff = true;
443             }
444             final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
445                     translationY, displayFrame);
446 
447             // Clip third of the from size of launch icon, expand to full width/height
448             Animation clipAnimLR = new ClipRectLRAnimation(
449                     clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
450             clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
451             clipAnimLR.setDuration((long) (duration / 2.5f));
452 
453             TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
454             translate.setInterpolator(cutOff ? mTouchResponseInterpolator
455                     : mLinearOutSlowInInterpolator);
456             translate.setDuration(duration);
457 
458             Animation clipAnimTB = new ClipRectTBAnimation(
459                     clipStartY, clipStartY + mTmpRect.height(),
460                     0, appHeight,
461                     translationYCorrection, 0,
462                     mLinearOutSlowInInterpolator);
463             clipAnimTB.setInterpolator(mTouchResponseInterpolator);
464             clipAnimTB.setDuration(duration);
465 
466             // Quick fade-in from icon to app window
467             final long alphaDuration = duration / 4;
468             AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
469             alpha.setDuration(alphaDuration);
470             alpha.setInterpolator(mLinearOutSlowInInterpolator);
471 
472             AnimationSet set = new AnimationSet(false);
473             set.addAnimation(clipAnimLR);
474             set.addAnimation(clipAnimTB);
475             set.addAnimation(translate);
476             set.addAnimation(alpha);
477             set.setZAdjustment(Animation.ZORDER_TOP);
478             set.initialize(appWidth, appHeight, appWidth, appHeight);
479             anim = set;
480         } else {
481             final long duration;
482             switch (transit) {
483                 case TRANSIT_OLD_ACTIVITY_OPEN:
484                 case TRANSIT_OLD_ACTIVITY_CLOSE:
485                     duration = mConfigShortAnimTime;
486                     break;
487                 default:
488                     duration = DEFAULT_APP_TRANSITION_DURATION;
489                     break;
490             }
491             if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
492                     || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
493                 // If we are on top of the wallpaper, we need an animation that
494                 // correctly handles the wallpaper staying static behind all of
495                 // the animated elements.  To do this, will just have the existing
496                 // element fade out.
497                 anim = new AlphaAnimation(1, 0);
498                 anim.setDetachWallpaper(true);
499             } else {
500                 // For normal animations, the exiting element just holds in place.
501                 anim = new AlphaAnimation(1, 1);
502             }
503             anim.setInterpolator(mDecelerateInterpolator);
504             anim.setDuration(duration);
505             anim.setFillAfter(true);
506         }
507         return anim;
508     }
509 
createScaleUpAnimationLocked(@ransitionType int transit, int wallpaperTransit, boolean enter, Rect containingFrame, Rect startRect)510     public Animation createScaleUpAnimationLocked(@TransitionType int transit, int wallpaperTransit,
511             boolean enter, Rect containingFrame, Rect startRect) {
512         return createScaleUpAnimationLockedCompat(getTransitCompatType(transit, wallpaperTransit),
513                 enter, containingFrame, startRect);
514     }
515 
createScaleUpAnimationLockedCompat(@ransitionOldType int transit, boolean enter, Rect containingFrame, Rect startRect)516     public Animation createScaleUpAnimationLockedCompat(@TransitionOldType int transit,
517             boolean enter, Rect containingFrame, Rect startRect) {
518         Animation a;
519         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
520         final int appWidth = containingFrame.width();
521         final int appHeight = containingFrame.height();
522         if (enter) {
523             // Entering app zooms out from the center of the initial rect.
524             float scaleW = mTmpRect.width() / (float) appWidth;
525             float scaleH = mTmpRect.height() / (float) appHeight;
526             Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
527                     computePivot(mTmpRect.left, scaleW),
528                     computePivot(mTmpRect.top, scaleH));
529             scale.setInterpolator(mDecelerateInterpolator);
530 
531             Animation alpha = new AlphaAnimation(0, 1);
532             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
533 
534             AnimationSet set = new AnimationSet(false);
535             set.addAnimation(scale);
536             set.addAnimation(alpha);
537             set.setDetachWallpaper(true);
538             a = set;
539         } else  if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
540                 || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
541             // If we are on top of the wallpaper, we need an animation that
542             // correctly handles the wallpaper staying static behind all of
543             // the animated elements.  To do this, will just have the existing
544             // element fade out.
545             a = new AlphaAnimation(1, 0);
546             a.setDetachWallpaper(true);
547         } else {
548             // For normal animations, the exiting element just holds in place.
549             a = new AlphaAnimation(1, 1);
550         }
551 
552         // Pick the desired duration.  If this is an inter-activity transition,
553         // it  is the standard duration for that.  Otherwise we use the longer
554         // task transition duration.
555         final long duration;
556         switch (transit) {
557             case TRANSIT_OLD_ACTIVITY_OPEN:
558             case TRANSIT_OLD_ACTIVITY_CLOSE:
559                 duration = mConfigShortAnimTime;
560                 break;
561             default:
562                 duration = DEFAULT_APP_TRANSITION_DURATION;
563                 break;
564         }
565         a.setDuration(duration);
566         a.setFillAfter(true);
567         a.setInterpolator(mDecelerateInterpolator);
568         a.initialize(appWidth, appHeight, appWidth, appHeight);
569         return a;
570     }
571 
createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp, Rect containingFrame, @TransitionType int transit, int wallpaperTransit, HardwareBuffer thumbnailHeader, Rect startRect)572     public Animation createThumbnailEnterExitAnimationLocked(boolean enter, boolean scaleUp,
573             Rect containingFrame, @TransitionType int transit, int wallpaperTransit,
574             HardwareBuffer thumbnailHeader, Rect startRect) {
575         return createThumbnailEnterExitAnimationLockedCompat(enter, scaleUp, containingFrame,
576                 getTransitCompatType(transit, wallpaperTransit), thumbnailHeader, startRect);
577     }
578 
579     /**
580      * This animation is created when we are doing a thumbnail transition, for the activity that is
581      * leaving, and the activity that is entering.
582      */
createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp, Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader, Rect startRect)583     public Animation createThumbnailEnterExitAnimationLockedCompat(boolean enter, boolean scaleUp,
584             Rect containingFrame, @TransitionOldType int transit, HardwareBuffer thumbnailHeader,
585             Rect startRect) {
586         final int appWidth = containingFrame.width();
587         final int appHeight = containingFrame.height();
588         Animation a;
589         setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
590         final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
591         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
592         final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
593         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
594         final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
595 
596         switch (thumbTransitState) {
597             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
598                 // Entering app scales up with the thumbnail
599                 float scaleW = thumbWidth / appWidth;
600                 float scaleH = thumbHeight / appHeight;
601                 a = new ScaleAnimation(scaleW, 1, scaleH, 1,
602                         computePivot(mTmpRect.left, scaleW),
603                         computePivot(mTmpRect.top, scaleH));
604                 break;
605             }
606             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
607                 // Exiting app while the thumbnail is scaling up should fade or stay in place
608                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
609                     // Fade out while bringing up selected activity. This keeps the
610                     // current activity from showing through a launching wallpaper
611                     // activity.
612                     a = new AlphaAnimation(1, 0);
613                 } else {
614                     // noop animation
615                     a = new AlphaAnimation(1, 1);
616                 }
617                 break;
618             }
619             case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
620                 // Entering the other app, it should just be visible while we scale the thumbnail
621                 // down above it
622                 a = new AlphaAnimation(1, 1);
623                 break;
624             }
625             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
626                 // Exiting the current app, the app should scale down with the thumbnail
627                 float scaleW = thumbWidth / appWidth;
628                 float scaleH = thumbHeight / appHeight;
629                 Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
630                         computePivot(mTmpRect.left, scaleW),
631                         computePivot(mTmpRect.top, scaleH));
632 
633                 Animation alpha = new AlphaAnimation(1, 0);
634 
635                 AnimationSet set = new AnimationSet(true);
636                 set.addAnimation(scale);
637                 set.addAnimation(alpha);
638                 set.setZAdjustment(Animation.ZORDER_TOP);
639                 a = set;
640                 break;
641             }
642             default:
643                 throw new RuntimeException("Invalid thumbnail transition state");
644         }
645 
646         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
647     }
648 
649     /**
650      * This alternate animation is created when we are doing a thumbnail transition, for the
651      * activity that is leaving, and the activity that is entering.
652      */
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)653     public Animation createAspectScaledThumbnailEnterExitAnimationLocked(boolean enter,
654             boolean scaleUp, int orientation, int transit, Rect containingFrame, Rect contentInsets,
655             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
656             Rect startRect, Rect defaultStartRect) {
657         Animation a;
658         final int appWidth = containingFrame.width();
659         final int appHeight = containingFrame.height();
660         setupDefaultNextAppTransitionStartRect(defaultStartRect, mTmpRect);
661         final int thumbWidthI = mTmpRect.width();
662         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
663         final int thumbHeightI = mTmpRect.height();
664         final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
665         final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
666         final int thumbStartY = mTmpRect.top - containingFrame.top;
667         final int thumbTransitState = getThumbnailTransitionState(enter, scaleUp);
668 
669         switch (thumbTransitState) {
670             case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
671             case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
672                 if (freeform && scaleUp) {
673                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
674                             containingFrame, surfaceInsets, startRect, defaultStartRect);
675                 } else if (freeform) {
676                     a = createAspectScaledThumbnailExitFreeformAnimationLocked(
677                             containingFrame, surfaceInsets, startRect, defaultStartRect);
678                 } else {
679                     AnimationSet set = new AnimationSet(true);
680 
681                     // In portrait, we scale to fit the width
682                     mTmpFromClipRect.set(containingFrame);
683                     mTmpToClipRect.set(containingFrame);
684 
685                     // Containing frame is in screen space, but we need the clip rect in the
686                     // app space.
687                     mTmpFromClipRect.offsetTo(0, 0);
688                     mTmpToClipRect.offsetTo(0, 0);
689 
690                     // Exclude insets region from the source clip.
691                     mTmpFromClipRect.inset(contentInsets);
692 
693                     if (shouldScaleDownThumbnailTransition(orientation)) {
694                         // We scale the width and clip to the top/left square
695                         float scale =
696                                 thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
697                         if (!mGridLayoutRecentsEnabled) {
698                             int unscaledThumbHeight = (int) (thumbHeight / scale);
699                             mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
700                         }
701 
702                         Animation scaleAnim = new ScaleAnimation(
703                                 scaleUp ? scale : 1, scaleUp ? 1 : scale,
704                                 scaleUp ? scale : 1, scaleUp ? 1 : scale,
705                                 containingFrame.width() / 2f,
706                                 containingFrame.height() / 2f + contentInsets.top);
707                         final float targetX = (mTmpRect.left - containingFrame.left);
708                         final float x = containingFrame.width() / 2f
709                                 - containingFrame.width() / 2f * scale;
710                         final float targetY = (mTmpRect.top - containingFrame.top);
711                         float y = containingFrame.height() / 2f
712                                 - containingFrame.height() / 2f * scale;
713 
714                         // During transition may require clipping offset from any top stable insets
715                         // such as the statusbar height when statusbar is hidden
716                         if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) {
717                             mTmpFromClipRect.top += stableInsets.top;
718                             y += stableInsets.top;
719                         }
720                         final float startX = targetX - x;
721                         final float startY = targetY - y;
722                         Animation clipAnim = scaleUp
723                                 ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
724                                 : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
725                         Animation translateAnim = scaleUp
726                                 ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
727                                 : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
728 
729                         set.addAnimation(clipAnim);
730                         set.addAnimation(scaleAnim);
731                         set.addAnimation(translateAnim);
732 
733                     } else {
734                         // In landscape, we don't scale at all and only crop
735                         mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
736                         mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
737 
738                         Animation clipAnim = scaleUp
739                                 ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
740                                 : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
741                         Animation translateAnim = scaleUp
742                                 ? createCurvedMotion(thumbStartX, 0,
743                                 thumbStartY - contentInsets.top, 0)
744                                 : createCurvedMotion(0, thumbStartX, 0,
745                                         thumbStartY - contentInsets.top);
746 
747                         set.addAnimation(clipAnim);
748                         set.addAnimation(translateAnim);
749                     }
750                     a = set;
751                     a.setZAdjustment(Animation.ZORDER_TOP);
752                 }
753                 break;
754             }
755             case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
756                 // Previous app window during the scale up
757                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
758                     // Fade out the source activity if we are animating to a wallpaper
759                     // activity.
760                     a = new AlphaAnimation(1, 0);
761                 } else {
762                     a = new AlphaAnimation(1, 1);
763                 }
764                 break;
765             }
766             case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
767                 // Target app window during the scale down
768                 if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
769                     // Fade in the destination activity if we are animating from a wallpaper
770                     // activity.
771                     a = new AlphaAnimation(0, 1);
772                 } else {
773                     a = new AlphaAnimation(1, 1);
774                 }
775                 break;
776             }
777             default:
778                 throw new RuntimeException("Invalid thumbnail transition state");
779         }
780 
781         return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
782                 THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator);
783     }
784 
785     /**
786      * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
787      * when a thumbnail is specified with the pending animation override.
788      */
createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation, Rect startRect, Rect defaultStartRect, boolean scaleUp)789     public Animation createThumbnailAspectScaleAnimationLocked(Rect appRect,
790             @Nullable Rect contentInsets, HardwareBuffer thumbnailHeader, int orientation,
791             Rect startRect, Rect defaultStartRect, boolean scaleUp) {
792         Animation a;
793         final int thumbWidthI = thumbnailHeader.getWidth();
794         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
795         final int thumbHeightI = thumbnailHeader.getHeight();
796         final int appWidth = appRect.width();
797 
798         float scaleW = appWidth / thumbWidth;
799         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
800         final float fromX;
801         float fromY;
802         final float toX;
803         float toY;
804         final float pivotX;
805         final float pivotY;
806         if (shouldScaleDownThumbnailTransition(orientation)) {
807             fromX = mTmpRect.left;
808             fromY = mTmpRect.top;
809 
810             // For the curved translate animation to work, the pivot points needs to be at the
811             // same absolute position as the one from the real surface.
812             toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
813             toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
814             pivotX = mTmpRect.width() / 2;
815             pivotY = appRect.height() / 2 / scaleW;
816             if (mGridLayoutRecentsEnabled) {
817                 // In the grid layout, the header is displayed above the thumbnail instead of
818                 // overlapping it.
819                 fromY -= thumbHeightI;
820                 toY -= thumbHeightI * scaleW;
821             }
822         } else {
823             pivotX = 0;
824             pivotY = 0;
825             fromX = mTmpRect.left;
826             fromY = mTmpRect.top;
827             toX = appRect.left;
828             toY = appRect.top;
829         }
830         if (scaleUp) {
831             // Animation up from the thumbnail to the full screen
832             Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
833             scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
834             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
835             Animation alpha = new AlphaAnimation(1f, 0f);
836             alpha.setInterpolator(mThumbnailFadeOutInterpolator);
837             alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
838             Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
839             translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
840             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
841 
842             mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
843             mTmpToClipRect.set(appRect);
844 
845             // Containing frame is in screen space, but we need the clip rect in the
846             // app space.
847             mTmpToClipRect.offsetTo(0, 0);
848             mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
849             mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
850 
851             if (contentInsets != null) {
852                 mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
853                         (int) (-contentInsets.top * scaleW),
854                         (int) (-contentInsets.right * scaleW),
855                         (int) (-contentInsets.bottom * scaleW));
856             }
857 
858             Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
859             clipAnim.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
860             clipAnim.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
861 
862             // This AnimationSet uses the Interpolators assigned above.
863             AnimationSet set = new AnimationSet(false);
864             set.addAnimation(scale);
865             if (!mGridLayoutRecentsEnabled) {
866                 // In the grid layout, the header should be shown for the whole animation.
867                 set.addAnimation(alpha);
868             }
869             set.addAnimation(translate);
870             set.addAnimation(clipAnim);
871             a = set;
872         } else {
873             // Animation down from the full screen to the thumbnail
874             Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
875             scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
876             scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
877             Animation alpha = new AlphaAnimation(0f, 1f);
878             alpha.setInterpolator(mThumbnailFadeInInterpolator);
879             alpha.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
880             Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
881             translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
882             translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
883 
884             // This AnimationSet uses the Interpolators assigned above.
885             AnimationSet set = new AnimationSet(false);
886             set.addAnimation(scale);
887             if (!mGridLayoutRecentsEnabled) {
888                 // In the grid layout, the header should be shown for the whole animation.
889                 set.addAnimation(alpha);
890             }
891             set.addAnimation(translate);
892             a = set;
893 
894         }
895         return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
896                 null);
897     }
898 
899     /**
900      * Creates an overlay with a background color and a thumbnail for the cross profile apps
901      * animation.
902      */
createCrossProfileAppsThumbnail( @rawableRes int thumbnailDrawableRes, Rect frame)903     public HardwareBuffer createCrossProfileAppsThumbnail(
904             @DrawableRes int thumbnailDrawableRes, Rect frame) {
905         final int width = frame.width();
906         final int height = frame.height();
907 
908         final Picture picture = new Picture();
909         final Canvas canvas = picture.beginRecording(width, height);
910         canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
911         final int thumbnailSize = mContext.getResources().getDimensionPixelSize(
912                 com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
913         final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes);
914         drawable.setBounds(
915                 (width - thumbnailSize) / 2,
916                 (height - thumbnailSize) / 2,
917                 (width + thumbnailSize) / 2,
918                 (height + thumbnailSize) / 2);
919         drawable.setTint(mContext.getColor(android.R.color.white));
920         drawable.draw(canvas);
921         picture.endRecording();
922 
923         return Bitmap.createBitmap(picture).getHardwareBuffer();
924     }
925 
926     /**
927      * Prepares the specified animation with a standard duration, interpolator, etc.
928      */
prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, @TransitionOldType int transit)929     private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
930             @TransitionOldType int transit) {
931         // Pick the desired duration.  If this is an inter-activity transition,
932         // it  is the standard duration for that.  Otherwise we use the longer
933         // task transition duration.
934         final int duration;
935         switch (transit) {
936             case TRANSIT_OLD_ACTIVITY_OPEN:
937             case TRANSIT_OLD_ACTIVITY_CLOSE:
938                 duration = mConfigShortAnimTime;
939                 break;
940             default:
941                 duration = DEFAULT_APP_TRANSITION_DURATION;
942                 break;
943         }
944         return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
945                 mDecelerateInterpolator);
946     }
947 
948 
createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame, @Nullable Rect surfaceInsets, @Nullable Rect startRect, @Nullable Rect defaultStartRect)949     private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
950             @Nullable Rect surfaceInsets, @Nullable Rect startRect,
951             @Nullable Rect defaultStartRect) {
952         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
953         return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
954                 true);
955     }
956 
createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame, @Nullable Rect surfaceInsets, @Nullable Rect startRect, @Nullable Rect defaultStartRect)957     private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
958             @Nullable Rect surfaceInsets, @Nullable Rect startRect,
959             @Nullable Rect defaultStartRect) {
960         getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
961         return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
962                 false);
963     }
964 
getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect)965     private void getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect) {
966         if (startRect == null && defaultStartRect == null) {
967             Slog.e(mTag, "Starting rect for container not available", new Throwable());
968             rect.setEmpty();
969         } else {
970             rect.set(startRect != null ? startRect : defaultStartRect);
971         }
972     }
973 
createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame, Rect destFrame, @Nullable Rect surfaceInsets, boolean enter)974     private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
975             Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
976         final float sourceWidth = sourceFrame.width();
977         final float sourceHeight = sourceFrame.height();
978         final float destWidth = destFrame.width();
979         final float destHeight = destFrame.height();
980         final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
981         final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
982         AnimationSet set = new AnimationSet(true);
983         final int surfaceInsetsH = surfaceInsets == null
984                 ? 0 : surfaceInsets.left + surfaceInsets.right;
985         final int surfaceInsetsV = surfaceInsets == null
986                 ? 0 : surfaceInsets.top + surfaceInsets.bottom;
987         // We want the scaling to happen from the center of the surface. In order to achieve that,
988         // we need to account for surface insets that will be used to enlarge the surface.
989         final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
990         final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
991         final ScaleAnimation scale = enter
992                 ? new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
993                 : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
994         final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
995         final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
996         final int destHCenter = destFrame.left + destFrame.width() / 2;
997         final int destVCenter = destFrame.top + destFrame.height() / 2;
998         final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
999         final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
1000         final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
1001                 : new TranslateAnimation(0, fromX, 0, fromY);
1002         set.addAnimation(scale);
1003         set.addAnimation(translation);
1004         return set;
1005     }
1006 
1007     /**
1008      * @return whether the transition should show the thumbnail being scaled down.
1009      */
shouldScaleDownThumbnailTransition(int orientation)1010     private boolean shouldScaleDownThumbnailTransition(int orientation) {
1011         return mGridLayoutRecentsEnabled
1012                 || orientation == Configuration.ORIENTATION_PORTRAIT;
1013     }
1014 
updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit)1015     private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
1016         if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN
1017                 && anim == R.anim.activity_open_enter) {
1018             return R.anim.activity_translucent_open_enter;
1019         }
1020         if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
1021                 && anim == R.anim.activity_close_exit) {
1022             return R.anim.activity_translucent_close_exit;
1023         }
1024         return anim;
1025     }
1026 
getTransitCompatType(@ransitionType int transit, int wallpaperTransit)1027     private static @TransitionOldType int getTransitCompatType(@TransitionType int transit,
1028             int wallpaperTransit) {
1029         if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
1030             return TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
1031         } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_CLOSE) {
1032             return TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
1033         } else if (transit == TRANSIT_OPEN) {
1034             return TRANSIT_OLD_ACTIVITY_OPEN;
1035         } else if (transit == TRANSIT_CLOSE) {
1036             return TRANSIT_OLD_ACTIVITY_CLOSE;
1037         }
1038 
1039         // We only do some special handle for above type, so use type NONE for default behavior.
1040         return TRANSIT_OLD_NONE;
1041     }
1042 
1043     /**
1044      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
1045      * the start rect is outside of the target rect, and there is a lot of movement going on.
1046      *
1047      * @param cutOff whether the start rect was not fully contained by the end rect
1048      * @param translationX the total translation the surface moves in x direction
1049      * @param translationY the total translation the surfaces moves in y direction
1050      * @param displayFrame our display frame
1051      *
1052      * @return the duration of the clip reveal animation, in milliseconds
1053      */
calculateClipRevealTransitionDuration(boolean cutOff, float translationX, float translationY, Rect displayFrame)1054     private static long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
1055             float translationY, Rect displayFrame) {
1056         if (!cutOff) {
1057             return DEFAULT_APP_TRANSITION_DURATION;
1058         }
1059         final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
1060                 Math.abs(translationY) / displayFrame.height());
1061         return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction
1062                 * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
1063     }
1064 
1065     /**
1066      * Return the current thumbnail transition state.
1067      */
getThumbnailTransitionState(boolean enter, boolean scaleUp)1068     private int getThumbnailTransitionState(boolean enter, boolean scaleUp) {
1069         if (enter) {
1070             if (scaleUp) {
1071                 return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
1072             } else {
1073                 return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
1074             }
1075         } else {
1076             if (scaleUp) {
1077                 return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
1078             } else {
1079                 return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
1080             }
1081         }
1082     }
1083 
1084     /**
1085      * Prepares the specified animation with a standard duration, interpolator, etc.
1086      */
prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight, long duration, Interpolator interpolator)1087     public static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
1088             int appHeight, long duration, Interpolator interpolator) {
1089         if (a == null) {
1090             return null;
1091         }
1092 
1093         if (duration > 0) {
1094             a.setDuration(duration);
1095         }
1096         a.setFillAfter(true);
1097         if (interpolator != null) {
1098             a.setInterpolator(interpolator);
1099         }
1100         a.initialize(appWidth, appHeight, appWidth, appHeight);
1101         return a;
1102     }
1103 
createCurvedMotion(float fromX, float toX, float fromY, float toY)1104     private static Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
1105         return new TranslateAnimation(fromX, toX, fromY, toY);
1106     }
1107 
1108     /**
1109      * Compute the pivot point for an animation that is scaling from a small
1110      * rect on screen to a larger rect.  The pivot point varies depending on
1111      * the distance between the inner and outer edges on both sides.  This
1112      * function computes the pivot point for one dimension.
1113      * @param startPos  Offset from left/top edge of outer rectangle to
1114      * left/top edge of inner rectangle.
1115      * @param finalScale The scaling factor between the size of the outer
1116      * and inner rectangles.
1117      */
computePivot(int startPos, float finalScale)1118     public static float computePivot(int startPos, float finalScale) {
1119 
1120         /*
1121         Theorem of intercepting lines:
1122 
1123           +      +   +-----------------------------------------------+
1124           |      |   |                                               |
1125           |      |   |                                               |
1126           |      |   |                                               |
1127           |      |   |                                               |
1128         x |    y |   |                                               |
1129           |      |   |                                               |
1130           |      |   |                                               |
1131           |      |   |                                               |
1132           |      |   |                                               |
1133           |      +   |             +--------------------+            |
1134           |          |             |                    |            |
1135           |          |             |                    |            |
1136           |          |             |                    |            |
1137           |          |             |                    |            |
1138           |          |             |                    |            |
1139           |          |             |                    |            |
1140           |          |             |                    |            |
1141           |          |             |                    |            |
1142           |          |             |                    |            |
1143           |          |             |                    |            |
1144           |          |             |                    |            |
1145           |          |             |                    |            |
1146           |          |             |                    |            |
1147           |          |             |                    |            |
1148           |          |             |                    |            |
1149           |          |             |                    |            |
1150           |          |             |                    |            |
1151           |          |             +--------------------+            |
1152           |          |                                               |
1153           |          |                                               |
1154           |          |                                               |
1155           |          |                                               |
1156           |          |                                               |
1157           |          |                                               |
1158           |          |                                               |
1159           |          +-----------------------------------------------+
1160           |
1161           |
1162           |
1163           |
1164           |
1165           |
1166           |
1167           |
1168           |
1169           +                                 ++
1170                                          p  ++
1171 
1172         scale = (x - y) / x
1173         <=> x = -y / (scale - 1)
1174         */
1175         final float denom = finalScale - 1;
1176         if (Math.abs(denom) < .0001f) {
1177             return startPos;
1178         }
1179         return -startPos / denom;
1180     }
1181 
1182     @Nullable
loadAnimationSafely(Context context, int resId, String tag)1183     public static Animation loadAnimationSafely(Context context, int resId, String tag) {
1184         try {
1185             return AnimationUtils.loadAnimation(context, resId);
1186         } catch (Resources.NotFoundException e) {
1187             Slog.w(tag, "Unable to load animation resource", e);
1188             return null;
1189         }
1190     }
1191 
createHiddenByKeyguardExit(Context context, LogDecelerateInterpolator interpolator, boolean onWallpaper, boolean goingToNotificationShade, boolean subtleAnimation)1192     public static Animation createHiddenByKeyguardExit(Context context,
1193             LogDecelerateInterpolator interpolator, boolean onWallpaper,
1194             boolean goingToNotificationShade, boolean subtleAnimation) {
1195         if (goingToNotificationShade) {
1196             return AnimationUtils.loadAnimation(context, R.anim.lock_screen_behind_enter_fade_in);
1197         }
1198 
1199         final int resource;
1200         if (subtleAnimation) {
1201             resource = R.anim.lock_screen_behind_enter_subtle;
1202         } else if (onWallpaper) {
1203             resource = R.anim.lock_screen_behind_enter_wallpaper;
1204         } else  {
1205             resource = R.anim.lock_screen_behind_enter;
1206         }
1207 
1208         AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(context, resource);
1209 
1210         // TODO: Use XML interpolators when we have log interpolators available in XML.
1211         final List<Animation> animations = set.getAnimations();
1212         for (int i = animations.size() - 1; i >= 0; --i) {
1213             animations.get(i).setInterpolator(interpolator);
1214         }
1215 
1216         return set;
1217     }
1218 }
1219