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