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