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