• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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