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