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.wm.shell.common.split; 18 19 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 21 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; 22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 24 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 25 26 import static com.android.wm.shell.common.split.SplitLayout.ANIMATING_BACK_APP_VEIL_LAYER; 27 import static com.android.wm.shell.common.split.SplitLayout.ANIMATING_FRONT_APP_VEIL_LAYER; 28 import static com.android.wm.shell.shared.split.SplitScreenConstants.FADE_DURATION; 29 import static com.android.wm.shell.shared.split.SplitScreenConstants.VEIL_DELAY_DURATION; 30 31 import android.animation.Animator; 32 import android.animation.AnimatorListenerAdapter; 33 import android.animation.ValueAnimator; 34 import android.app.ActivityManager; 35 import android.content.Context; 36 import android.content.res.Configuration; 37 import android.graphics.Color; 38 import android.graphics.PixelFormat; 39 import android.graphics.Rect; 40 import android.graphics.drawable.Drawable; 41 import android.os.Binder; 42 import android.view.IWindow; 43 import android.view.LayoutInflater; 44 import android.view.SurfaceControl; 45 import android.view.SurfaceControlViewHost; 46 import android.view.View; 47 import android.view.WindowManager; 48 import android.view.WindowlessWindowManager; 49 import android.widget.FrameLayout; 50 import android.widget.ImageView; 51 52 import androidx.annotation.NonNull; 53 54 import com.android.launcher3.icons.IconProvider; 55 import com.android.wm.shell.R; 56 import com.android.wm.shell.common.ScreenshotUtils; 57 import com.android.wm.shell.common.SurfaceUtils; 58 59 import java.util.function.Consumer; 60 61 /** 62 * Handles additional layers over a running task in a split pair, for example showing a veil with an 63 * app icon when the task is being resized (usually to hide weird layouts while the app is being 64 * stretched). One SplitDecorManager is initialized on each window. 65 * <br> 66 * Currently, we show a veil when: 67 * a) Task is resizing down from a fullscreen window. 68 * b) Task is being stretched past its original bounds. 69 * <br> 70 * Split root 71 * / | \ 72 * Stage root Divider Stage root 73 * / \ 74 * Task *this class* 75 * 76 */ 77 public class SplitDecorManager extends WindowlessWindowManager { 78 private static final String TAG = SplitDecorManager.class.getSimpleName(); 79 private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground"; 80 private static final String GAP_BACKGROUND_SURFACE_NAME = "GapBackground"; 81 82 private final IconProvider mIconProvider; 83 84 private Drawable mIcon; 85 private ImageView mVeilIconView; 86 private SurfaceControlViewHost mViewHost; 87 /** The parent surface that this is attached to. Should be the stage root. */ 88 private SurfaceControl mHostLeash; 89 private SurfaceControl mIconLeash; 90 private SurfaceControl mBackgroundLeash; 91 private SurfaceControl mGapBackgroundLeash; 92 private SurfaceControl mScreenshot; 93 94 private boolean mShown; 95 /** True if the task is going through some kind of transition (moving or changing size). */ 96 private boolean mIsCurrentlyChanging; 97 /** The original bounds of the main task, captured at the beginning of a resize transition. */ 98 private final Rect mOldMainBounds = new Rect(); 99 /** The original bounds of the side task, captured at the beginning of a resize transition. */ 100 private final Rect mOldSideBounds = new Rect(); 101 /** The current bounds of the main task, mid-resize. */ 102 private final Rect mInstantaneousBounds = new Rect(); 103 private final Rect mTempRect = new Rect(); 104 private ValueAnimator mFadeAnimator; 105 private ValueAnimator mScreenshotAnimator; 106 107 private int mIconSize; 108 private int mOffsetX; 109 private int mOffsetY; 110 private int mRunningAnimationCount = 0; 111 SplitDecorManager(Configuration configuration, IconProvider iconProvider)112 public SplitDecorManager(Configuration configuration, IconProvider iconProvider) { 113 super(configuration, null /* rootSurface */, null /* hostInputToken */); 114 mIconProvider = iconProvider; 115 } 116 117 @Override getParentSurface(IWindow window, WindowManager.LayoutParams attrs)118 protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { 119 // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. 120 final SurfaceControl.Builder builder = new SurfaceControl.Builder() 121 .setContainerLayer() 122 .setName(TAG) 123 .setHidden(true) 124 .setParent(mHostLeash) 125 .setCallsite("SplitDecorManager#attachToParentSurface"); 126 mIconLeash = builder.build(); 127 return mIconLeash; 128 } 129 130 /** Inflates split decor surface on the root surface. */ inflate(Context context, SurfaceControl rootLeash)131 public void inflate(Context context, SurfaceControl rootLeash) { 132 if (mIconLeash != null && mViewHost != null) { 133 return; 134 } 135 136 context = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, 137 null /* options */); 138 mHostLeash = rootLeash; 139 mViewHost = new SurfaceControlViewHost(context, context.getDisplay(), this, 140 "SplitDecorManager"); 141 142 mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size); 143 final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context) 144 .inflate(R.layout.split_decor, null); 145 mVeilIconView = rootLayout.findViewById(R.id.split_resizing_icon); 146 147 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 148 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, 149 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); 150 lp.width = mIconSize; 151 lp.height = mIconSize; 152 lp.token = new Binder(); 153 lp.setTitle(TAG); 154 lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; 155 lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL; 156 mViewHost.setView(rootLayout, lp); 157 } 158 159 /** 160 * Cancels any currently running animations. 161 */ cancelRunningAnimations()162 public void cancelRunningAnimations() { 163 if (mFadeAnimator != null) { 164 if (mFadeAnimator.isRunning()) { 165 mFadeAnimator.cancel(); 166 } 167 mFadeAnimator = null; 168 } 169 if (mScreenshotAnimator != null) { 170 if (mScreenshotAnimator.isRunning()) { 171 mScreenshotAnimator.cancel(); 172 } 173 mScreenshotAnimator = null; 174 } 175 } 176 177 /** Releases the surfaces for split decor. */ release(SurfaceControl.Transaction t)178 public void release(SurfaceControl.Transaction t) { 179 cancelRunningAnimations(); 180 if (mViewHost != null) { 181 mViewHost.release(); 182 mViewHost = null; 183 } 184 if (mIconLeash != null) { 185 t.remove(mIconLeash); 186 mIconLeash = null; 187 } 188 if (mBackgroundLeash != null) { 189 t.remove(mBackgroundLeash); 190 mBackgroundLeash = null; 191 } 192 if (mGapBackgroundLeash != null) { 193 t.remove(mGapBackgroundLeash); 194 mGapBackgroundLeash = null; 195 } 196 if (mScreenshot != null) { 197 t.remove(mScreenshot); 198 mScreenshot = null; 199 } 200 mHostLeash = null; 201 mIcon = null; 202 mVeilIconView = null; 203 mIsCurrentlyChanging = false; 204 mShown = false; 205 mOldMainBounds.setEmpty(); 206 mOldSideBounds.setEmpty(); 207 mInstantaneousBounds.setEmpty(); 208 } 209 210 /** 211 * Called on every frame when an app is getting resized, and controls the showing & hiding of 212 * the app veil. IMPORTANT: There is one SplitDecorManager for each task, so if two tasks are 213 * getting resized simultaneously, this method is called in parallel on the other 214 * SplitDecorManager too. In general, we want to hide the app behind a veil when: 215 * a) the app is stretching past its original bounds (because app content layout doesn't 216 * update mid-stretch). 217 * b) the app is resizing down from fullscreen (because there is no parallax effect that 218 * makes every app look good in this scenario). 219 * In the world of flexible split, where apps can go offscreen, there is an exception to this: 220 * - We do NOT hide the app when it is going offscreen, even though it is technically 221 * getting larger and would qualify for condition (a). Instead, we use parallax to give 222 * the illusion that the app is getting pushed offscreen by the divider. 223 * 224 * @param resizingTask The task that is getting resized. 225 * @param newBounds The bounds that that we are updating this surface to. This can be an 226 * instantaneous bounds, just for a frame, during a drag or animation. 227 * @param sideBounds The bounds of the OPPOSITE task in the split layout. This is used just for 228 * reference/calculation, the surface of the other app won't be set here. 229 * @param displayBounds The bounds of the entire display. 230 * @param t The transaction on which these changes will be bundled. 231 * @param offsetX The x-translation applied to the task surface for parallax. Will be used to 232 * position the task screenshot and/or icon veil. 233 * @param offsetY The x-translation applied to the task surface for parallax. Will be used to 234 * position the task screenshot and/or icon veil. 235 * @param immediately {@code true} if the veil should transition in/out instantly, with no 236 * animation. 237 */ onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, Rect sideBounds, Rect displayBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately)238 public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, 239 Rect sideBounds, Rect displayBounds, SurfaceControl.Transaction t, int offsetX, 240 int offsetY, boolean immediately) { 241 if (mVeilIconView == null) { 242 return; 243 } 244 245 if (!mIsCurrentlyChanging) { 246 mIsCurrentlyChanging = true; 247 mOldMainBounds.set(newBounds); 248 mOldSideBounds.set(sideBounds); 249 } 250 mInstantaneousBounds.set(newBounds); 251 mOffsetX = offsetX; 252 mOffsetY = offsetY; 253 254 // Show a veil when: 255 // a) Task is resizing down from a fullscreen window. 256 // b) Task is being stretched past its original bounds. 257 final boolean isResizingDownFromFullscreen = 258 mOldSideBounds.width() <= 1 || mOldSideBounds.height() <= 1; 259 final boolean isStretchingPastOriginalBounds = 260 newBounds.width() > mOldMainBounds.width() 261 || newBounds.height() > mOldMainBounds.height(); 262 final boolean isFullyOnscreen = displayBounds.contains(newBounds); 263 boolean showVeil = isFullyOnscreen 264 && (isResizingDownFromFullscreen || isStretchingPastOriginalBounds); 265 266 final boolean update = showVeil != mShown; 267 if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) { 268 // If we need to animate and animator still running, cancel it before we ensure both 269 // background and icon surfaces are non null for next animation. 270 mFadeAnimator.cancel(); 271 } 272 273 if (mBackgroundLeash == null) { 274 mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, 275 RESIZING_BACKGROUND_SURFACE_NAME); 276 t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask)) 277 .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1); 278 } 279 280 if (mGapBackgroundLeash == null && !immediately) { 281 final boolean isLandscape = newBounds.height() == sideBounds.height(); 282 final int left = isLandscape ? mOldMainBounds.width() : 0; 283 final int top = isLandscape ? 0 : mOldMainBounds.height(); 284 mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, 285 GAP_BACKGROUND_SURFACE_NAME); 286 // Fill up another side bounds area. 287 t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask)) 288 .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2) 289 .setPosition(mGapBackgroundLeash, left, top) 290 .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height()); 291 } 292 293 if (mIcon == null && resizingTask.topActivityInfo != null) { 294 mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo); 295 mVeilIconView.setImageDrawable(mIcon); 296 mVeilIconView.setVisibility(View.VISIBLE); 297 298 WindowManager.LayoutParams lp = 299 (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); 300 lp.width = mIconSize; 301 lp.height = mIconSize; 302 mViewHost.relayout(lp); 303 t.setLayer(mIconLeash, Integer.MAX_VALUE); 304 } 305 t.setPosition(mIconLeash, 306 newBounds.width() / 2 - mIconSize / 2, 307 newBounds.height() / 2 - mIconSize / 2); 308 309 if (update) { 310 if (immediately) { 311 t.setAlpha(mBackgroundLeash, showVeil ? 1f : 0f); 312 t.setVisibility(mBackgroundLeash, showVeil); 313 t.setAlpha(mIconLeash, showVeil ? 1f : 0f); 314 t.setVisibility(mIconLeash, showVeil); 315 } else { 316 startFadeAnimation( 317 showVeil, 318 false /* releaseSurface */, 319 null /* finishedCallback */, 320 false /* addDelay */ 321 ); 322 } 323 mShown = showVeil; 324 } 325 } 326 327 /** Stops showing resizing hint. */ onResized(SurfaceControl.Transaction t, Consumer<Boolean> animFinishedCallback)328 public void onResized(SurfaceControl.Transaction t, Consumer<Boolean> animFinishedCallback) { 329 if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { 330 mScreenshotAnimator.cancel(); 331 } 332 333 if (mScreenshot != null) { 334 t.setPosition(mScreenshot, mOffsetX, mOffsetY); 335 336 final SurfaceControl.Transaction animT = new SurfaceControl.Transaction(); 337 mScreenshotAnimator = ValueAnimator.ofFloat(1, 0); 338 mScreenshotAnimator.setDuration(FADE_DURATION); 339 mScreenshotAnimator.addUpdateListener(valueAnimator -> { 340 final float progress = (float) valueAnimator.getAnimatedValue(); 341 animT.setAlpha(mScreenshot, progress); 342 animT.apply(); 343 }); 344 mScreenshotAnimator.addListener(new AnimatorListenerAdapter() { 345 @Override 346 public void onAnimationStart(Animator animation) { 347 mRunningAnimationCount++; 348 } 349 350 @Override 351 public void onAnimationEnd(@NonNull Animator animation) { 352 mRunningAnimationCount--; 353 animT.remove(mScreenshot); 354 animT.apply(); 355 animT.close(); 356 mScreenshot = null; 357 358 if (mRunningAnimationCount == 0 && animFinishedCallback != null) { 359 animFinishedCallback.accept(true); 360 } 361 } 362 }); 363 mScreenshotAnimator.start(); 364 } 365 366 if (mVeilIconView == null) { 367 if (mRunningAnimationCount == 0 && animFinishedCallback != null) { 368 animFinishedCallback.accept(false); 369 } 370 return; 371 } 372 373 mIsCurrentlyChanging = false; 374 mOffsetX = 0; 375 mOffsetY = 0; 376 mOldMainBounds.setEmpty(); 377 mOldSideBounds.setEmpty(); 378 mInstantaneousBounds.setEmpty(); 379 if (mFadeAnimator != null && mFadeAnimator.isRunning()) { 380 if (!mShown) { 381 // If fade-out animation is running, just add release callback to it. 382 SurfaceControl.Transaction finishT = new SurfaceControl.Transaction(); 383 mFadeAnimator.addListener(new AnimatorListenerAdapter() { 384 @Override 385 public void onAnimationEnd(Animator animation) { 386 releaseDecor(finishT); 387 finishT.apply(); 388 finishT.close(); 389 if (mRunningAnimationCount == 0 && animFinishedCallback != null) { 390 animFinishedCallback.accept(true); 391 } 392 } 393 }); 394 return; 395 } 396 } 397 if (mShown) { 398 fadeOutDecor(()-> { 399 if (mRunningAnimationCount == 0 && animFinishedCallback != null) { 400 animFinishedCallback.accept(true); 401 } 402 }, false /* addDelay */); 403 } else { 404 // Decor surface is hidden so release it directly. 405 releaseDecor(t); 406 if (mRunningAnimationCount == 0 && animFinishedCallback != null) { 407 animFinishedCallback.accept(false); 408 } 409 } 410 } 411 412 /** 413 * Called (on every frame) when two split apps are swapping, and a veil is needed. 414 */ drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind, SurfaceControl leash, float iconOffsetX, float iconOffsetY)415 public void drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask, 416 Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind, 417 SurfaceControl leash, float iconOffsetX, float iconOffsetY) { 418 if (mVeilIconView == null) { 419 return; 420 } 421 422 if (!mIsCurrentlyChanging) { 423 mIsCurrentlyChanging = true; 424 } 425 426 mInstantaneousBounds.set(newBounds); 427 mOffsetX = (int) iconOffsetX; 428 mOffsetY = (int) iconOffsetY; 429 430 t.setLayer(leash, isGoingBehind 431 ? ANIMATING_BACK_APP_VEIL_LAYER 432 : ANIMATING_FRONT_APP_VEIL_LAYER); 433 434 if (!mShown) { 435 if (mFadeAnimator != null && mFadeAnimator.isRunning()) { 436 // Cancel mFadeAnimator if it is running 437 mFadeAnimator.cancel(); 438 } 439 } 440 441 if (mBackgroundLeash == null) { 442 // Initialize background 443 mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, 444 RESIZING_BACKGROUND_SURFACE_NAME); 445 t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask)) 446 .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1); 447 } 448 449 if (mIcon == null && resizingTask.topActivityInfo != null) { 450 // Initialize icon 451 mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo); 452 mVeilIconView.setImageDrawable(mIcon); 453 mVeilIconView.setVisibility(View.VISIBLE); 454 455 WindowManager.LayoutParams lp = 456 (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); 457 lp.width = mIconSize; 458 lp.height = mIconSize; 459 mViewHost.relayout(lp); 460 461 t.setLayer(mIconLeash, Integer.MAX_VALUE); 462 } 463 464 t.setPosition(mIconLeash, 465 newBounds.width() / 2 - mIconSize / 2 - mOffsetX, 466 newBounds.height() / 2 - mIconSize / 2 - mOffsetY); 467 468 // If this is the first frame, we need to trigger the veil's fade-in animation. 469 if (!mShown) { 470 startFadeAnimation( 471 true /* show */, 472 false /* releaseSurface */, 473 null /* finishedCallball */, 474 false /* addDelay */ 475 ); 476 mShown = true; 477 } 478 } 479 480 /** Called at the end of the swap animation. */ fadeOutVeilAndCleanUp(SurfaceControl.Transaction t)481 public void fadeOutVeilAndCleanUp(SurfaceControl.Transaction t) { 482 if (mVeilIconView == null) { 483 return; 484 } 485 486 // Recenter icon 487 t.setPosition(mIconLeash, 488 mInstantaneousBounds.width() / 2f - mIconSize / 2f, 489 mInstantaneousBounds.height() / 2f - mIconSize / 2f); 490 491 mIsCurrentlyChanging = false; 492 mOffsetX = 0; 493 mOffsetY = 0; 494 mInstantaneousBounds.setEmpty(); 495 496 fadeOutDecor(() -> {}, true /* addDelay */); 497 } 498 499 /** Screenshot host leash and attach on it if meet some conditions */ screenshotIfNeeded(SurfaceControl.Transaction t)500 public void screenshotIfNeeded(SurfaceControl.Transaction t) { 501 if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) { 502 if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { 503 mScreenshotAnimator.cancel(); 504 } else if (mScreenshot != null) { 505 t.remove(mScreenshot); 506 } 507 508 mTempRect.set(mOldMainBounds); 509 mTempRect.offsetTo(0, 0); 510 mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect, 511 Integer.MAX_VALUE - 1); 512 } 513 } 514 515 /** Set screenshot and attach on host leash it if meet some conditions */ setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t)516 public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) { 517 if (screenshot == null || !screenshot.isValid()) return; 518 519 if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) { 520 if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { 521 mScreenshotAnimator.cancel(); 522 } else if (mScreenshot != null) { 523 t.remove(mScreenshot); 524 } 525 526 mScreenshot = screenshot; 527 t.reparent(screenshot, mHostLeash); 528 t.setLayer(screenshot, Integer.MAX_VALUE - 1); 529 } 530 } 531 532 /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback 533 * directly. */ fadeOutDecor(Runnable finishedCallback, boolean addDelay)534 public void fadeOutDecor(Runnable finishedCallback, boolean addDelay) { 535 if (mShown) { 536 // If previous animation is running, just cancel it. 537 if (mFadeAnimator != null && mFadeAnimator.isRunning()) { 538 mFadeAnimator.cancel(); 539 } 540 541 startFadeAnimation( 542 false /* show */, true /* releaseSurface */, finishedCallback, addDelay); 543 mShown = false; 544 } else { 545 if (finishedCallback != null) finishedCallback.run(); 546 } 547 } 548 549 /** 550 * Fades the veil in or out. Called at the first frame of a movement or resize when a veil is 551 * needed (with show = true), and called again at the end (with show = false). 552 * @param addDelay If true, adds a short delay before fading out to get the app behind the veil 553 * time to redraw. 554 */ startFadeAnimation(boolean show, boolean releaseSurface, Runnable finishedCallback, boolean addDelay)555 private void startFadeAnimation(boolean show, boolean releaseSurface, 556 Runnable finishedCallback, boolean addDelay) { 557 final SurfaceControl.Transaction animT = new SurfaceControl.Transaction(); 558 559 mFadeAnimator = ValueAnimator.ofFloat(0f, 1f); 560 if (addDelay) { 561 mFadeAnimator.setStartDelay(VEIL_DELAY_DURATION); 562 } 563 mFadeAnimator.setDuration(FADE_DURATION); 564 mFadeAnimator.addUpdateListener(valueAnimator-> { 565 final float progress = (float) valueAnimator.getAnimatedValue(); 566 if (mBackgroundLeash != null) { 567 animT.setAlpha(mBackgroundLeash, show ? progress : 1 - progress); 568 } 569 if (mIconLeash != null) { 570 animT.setAlpha(mIconLeash, show ? progress : 1 - progress); 571 } 572 animT.apply(); 573 }); 574 mFadeAnimator.addListener(new AnimatorListenerAdapter() { 575 @Override 576 public void onAnimationStart(@NonNull Animator animation) { 577 mRunningAnimationCount++; 578 if (show) { 579 animT.show(mBackgroundLeash).show(mIconLeash); 580 } 581 if (mGapBackgroundLeash != null) { 582 animT.setVisibility(mGapBackgroundLeash, show); 583 } 584 animT.apply(); 585 } 586 587 @Override 588 public void onAnimationEnd(@NonNull Animator animation) { 589 mRunningAnimationCount--; 590 if (!show) { 591 if (mBackgroundLeash != null) { 592 animT.hide(mBackgroundLeash); 593 } 594 if (mIconLeash != null) { 595 animT.hide(mIconLeash); 596 } 597 } 598 if (releaseSurface) { 599 releaseDecor(animT); 600 } 601 animT.apply(); 602 animT.close(); 603 604 if (mRunningAnimationCount == 0 && finishedCallback != null) { 605 finishedCallback.run(); 606 } 607 } 608 }); 609 mFadeAnimator.start(); 610 } 611 612 /** Release or hide decor hint. */ releaseDecor(SurfaceControl.Transaction t)613 private void releaseDecor(SurfaceControl.Transaction t) { 614 if (mBackgroundLeash != null) { 615 t.remove(mBackgroundLeash); 616 mBackgroundLeash = null; 617 } 618 619 if (mGapBackgroundLeash != null) { 620 t.remove(mGapBackgroundLeash); 621 mGapBackgroundLeash = null; 622 } 623 624 if (mIcon != null) { 625 mVeilIconView.setVisibility(View.GONE); 626 mVeilIconView.setImageDrawable(null); 627 t.hide(mIconLeash); 628 mIcon = null; 629 } 630 } 631 getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo)632 private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { 633 final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); 634 return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents(); 635 } 636 } 637