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