1 /*
2  * Copyright (C) 2023 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.pip2.phone;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManager.TRANSIT_OPEN;
22 import static android.view.WindowManager.TRANSIT_PIP;
23 import static android.view.WindowManager.TRANSIT_TO_BACK;
24 import static android.view.WindowManager.TRANSIT_TO_FRONT;
25 
26 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
27 import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
28 
29 import android.animation.Animator;
30 import android.animation.AnimatorListenerAdapter;
31 import android.animation.ValueAnimator;
32 import android.annotation.NonNull;
33 import android.app.ActivityManager;
34 import android.app.PictureInPictureParams;
35 import android.content.Context;
36 import android.graphics.Rect;
37 import android.os.Bundle;
38 import android.os.IBinder;
39 import android.view.SurfaceControl;
40 import android.window.TransitionInfo;
41 import android.window.TransitionRequestInfo;
42 import android.window.WindowContainerToken;
43 import android.window.WindowContainerTransaction;
44 
45 import androidx.annotation.Nullable;
46 
47 import com.android.internal.util.Preconditions;
48 import com.android.wm.shell.ShellTaskOrganizer;
49 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
50 import com.android.wm.shell.common.pip.PipBoundsState;
51 import com.android.wm.shell.common.pip.PipMenuController;
52 import com.android.wm.shell.common.pip.PipUtils;
53 import com.android.wm.shell.pip.PipContentOverlay;
54 import com.android.wm.shell.pip.PipTransitionController;
55 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
56 import com.android.wm.shell.sysui.ShellInit;
57 import com.android.wm.shell.transition.Transitions;
58 
59 /**
60  * Implementation of transitions for PiP on phone.
61  */
62 public class PipTransition extends PipTransitionController implements
63         PipTransitionState.PipTransitionStateChangedListener {
64     private static final String TAG = PipTransition.class.getSimpleName();
65 
66     // Used when for ENTERING_PIP state update.
67     private static final String PIP_TASK_TOKEN = "pip_task_token";
68     private static final String PIP_TASK_LEASH = "pip_task_leash";
69 
70     // Used for PiP CHANGING_BOUNDS state update.
71     static final String PIP_START_TX = "pip_start_tx";
72     static final String PIP_FINISH_TX = "pip_finish_tx";
73     static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds";
74 
75     /**
76      * The fixed start delay in ms when fading out the content overlay from bounds animation.
77      * The fadeout animation is guaranteed to start after the client has drawn under the new config.
78      */
79     private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 400;
80 
81     //
82     // Dependencies
83     //
84 
85     private final Context mContext;
86     private final PipScheduler mPipScheduler;
87     private final PipTransitionState mPipTransitionState;
88 
89     //
90     // Transition tokens
91     //
92 
93     @Nullable
94     private IBinder mEnterTransition;
95     @Nullable
96     private IBinder mExitViaExpandTransition;
97     @Nullable
98     private IBinder mResizeTransition;
99 
100     //
101     // Internal state and relevant cached info
102     //
103 
104     @Nullable
105     private WindowContainerToken mPipTaskToken;
106     @Nullable
107     private SurfaceControl mPipLeash;
108     @Nullable
109     private Transitions.TransitionFinishCallback mFinishCallback;
110 
PipTransition( Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipScheduler pipScheduler, PipTransitionState pipTransitionState)111     public PipTransition(
112             Context context,
113             @NonNull ShellInit shellInit,
114             @NonNull ShellTaskOrganizer shellTaskOrganizer,
115             @NonNull Transitions transitions,
116             PipBoundsState pipBoundsState,
117             PipMenuController pipMenuController,
118             PipBoundsAlgorithm pipBoundsAlgorithm,
119             PipScheduler pipScheduler,
120             PipTransitionState pipTransitionState) {
121         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
122                 pipBoundsAlgorithm);
123 
124         mContext = context;
125         mPipScheduler = pipScheduler;
126         mPipScheduler.setPipTransitionController(this);
127         mPipTransitionState = pipTransitionState;
128         mPipTransitionState.addPipTransitionStateChangedListener(this);
129     }
130 
131     @Override
onInit()132     protected void onInit() {
133         if (PipUtils.isPip2ExperimentEnabled()) {
134             mTransitions.addHandler(this);
135         }
136     }
137 
138     //
139     // Transition collection stage lifecycle hooks
140     //
141 
142     @Override
startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)143     public void startExitTransition(int type, WindowContainerTransaction out,
144             @Nullable Rect destinationBounds) {
145         if (out == null) {
146             return;
147         }
148         IBinder transition = mTransitions.startTransition(type, out, this);
149         if (type == TRANSIT_EXIT_PIP) {
150             mExitViaExpandTransition = transition;
151         }
152     }
153 
154     @Override
startResizeTransition(WindowContainerTransaction wct)155     public void startResizeTransition(WindowContainerTransaction wct) {
156         if (wct == null) {
157             return;
158         }
159         mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
160     }
161 
162     @Nullable
163     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)164     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
165             @NonNull TransitionRequestInfo request) {
166         if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
167             mEnterTransition = transition;
168             return getEnterPipTransaction(transition, request);
169         }
170         return null;
171     }
172 
173     @Override
augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWct)174     public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
175             @NonNull WindowContainerTransaction outWct) {
176         if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
177             outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
178             mEnterTransition = transition;
179         }
180     }
181 
182     //
183     // Transition playing stage lifecycle hooks
184     //
185 
186     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)187     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
188             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
189             @NonNull Transitions.TransitionFinishCallback finishCallback) {}
190 
191     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)192     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
193             @Nullable SurfaceControl.Transaction finishT) {}
194 
195     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)196     public boolean startAnimation(@NonNull IBinder transition,
197             @NonNull TransitionInfo info,
198             @NonNull SurfaceControl.Transaction startTransaction,
199             @NonNull SurfaceControl.Transaction finishTransaction,
200             @NonNull Transitions.TransitionFinishCallback finishCallback) {
201         if (transition == mEnterTransition || info.getType() == TRANSIT_PIP) {
202             mEnterTransition = null;
203             // If we are in swipe PiP to Home transition we are ENTERING_PIP as a jumpcut transition
204             // is being carried out.
205             TransitionInfo.Change pipChange = getPipChange(info);
206 
207             // If there is no PiP change, exit this transition handler and potentially try others.
208             if (pipChange == null) return false;
209 
210             Bundle extra = new Bundle();
211             extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
212             extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
213             mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
214 
215             if (mPipTransitionState.isInSwipePipToHomeTransition()) {
216                 // If this is the second transition as a part of swipe PiP to home cuj,
217                 // handle this transition as a special case with no-op animation.
218                 return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
219                         finishCallback);
220             }
221             if (isLegacyEnter(info)) {
222                 // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
223                 // then we should run an ALPHA type (cross-fade) animation.
224                 return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction,
225                         finishCallback);
226             }
227             return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction,
228                     finishCallback);
229         } else if (transition == mExitViaExpandTransition) {
230             mExitViaExpandTransition = null;
231             mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
232             return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
233         } else if (transition == mResizeTransition) {
234             mResizeTransition = null;
235             return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
236         }
237 
238         if (isRemovePipTransition(info)) {
239             return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
240         }
241         return false;
242     }
243 
244     //
245     // Animation schedulers and entry points
246     //
247 
startResizeAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)248     private boolean startResizeAnimation(@NonNull TransitionInfo info,
249             @NonNull SurfaceControl.Transaction startTransaction,
250             @NonNull SurfaceControl.Transaction finishTransaction,
251             @NonNull Transitions.TransitionFinishCallback finishCallback) {
252         TransitionInfo.Change pipChange = getPipChange(info);
253         if (pipChange == null) {
254             return false;
255         }
256         SurfaceControl pipLeash = pipChange.getLeash();
257 
258         // Even though the final bounds and crop are applied with finishTransaction since
259         // this is a visible change, we still need to handle the app draw coming in. Snapshot
260         // covering app draw during collection will be removed by startTransaction. So we make
261         // the crop equal to the final bounds and then let the current
262         // animator scale the leash back to starting bounds.
263         // Note: animator is responsible for applying the startTx but NOT finishTx.
264         startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
265                 pipChange.getEndAbsBounds().height());
266 
267         // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
268         // Classes interested in continuing the animation would subscribe to this state update
269         // getting info such as endBounds, startTx, and finishTx as an extra Bundle once
270         // animators are in place. Once done state needs to be updated to CHANGED_PIP_BOUNDS.
271         Bundle extra = new Bundle();
272         extra.putParcelable(PIP_START_TX, startTransaction);
273         extra.putParcelable(PIP_FINISH_TX, finishTransaction);
274         extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds());
275 
276         mFinishCallback = finishCallback;
277         mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra);
278         return true;
279     }
280 
handleSwipePipToHomeTransition(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)281     private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info,
282             @NonNull SurfaceControl.Transaction startTransaction,
283             @NonNull SurfaceControl.Transaction finishTransaction,
284             @NonNull Transitions.TransitionFinishCallback finishCallback) {
285         TransitionInfo.Change pipChange = getPipChange(info);
286         if (pipChange == null) {
287             return false;
288         }
289         WindowContainerToken pipTaskToken = pipChange.getContainer();
290         SurfaceControl pipLeash = pipChange.getLeash();
291 
292         if (pipTaskToken == null || pipLeash == null) {
293             return false;
294         }
295 
296         SurfaceControl overlayLeash = mPipTransitionState.getSwipePipToHomeOverlay();
297         PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
298 
299         Rect appBounds = mPipTransitionState.getSwipePipToHomeAppBounds();
300         Rect destinationBounds = pipChange.getEndAbsBounds();
301 
302         float aspectRatio = pipChange.getTaskInfo().pictureInPictureParams.getAspectRatioFloat();
303 
304         // We fake the source rect hint when the one prvided by the app is invalid for
305         // the animation with an app icon overlay.
306         Rect animationSrcRectHint = overlayLeash == null ? params.getSourceRectHint()
307                 : PipUtils.getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio);
308 
309         WindowContainerTransaction finishWct = new WindowContainerTransaction();
310         SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
311 
312         final float scale = (float) destinationBounds.width() / animationSrcRectHint.width();
313         startTransaction.setWindowCrop(pipLeash, animationSrcRectHint);
314         startTransaction.setPosition(pipLeash,
315                 destinationBounds.left - animationSrcRectHint.left * scale,
316                 destinationBounds.top - animationSrcRectHint.top * scale);
317         startTransaction.setScale(pipLeash, scale, scale);
318 
319         if (overlayLeash != null) {
320             final int overlaySize = PipContentOverlay.PipAppIconOverlay.getOverlaySize(
321                     mPipTransitionState.getSwipePipToHomeAppBounds(), destinationBounds);
322 
323             // Overlay needs to be adjusted once a new draw comes in resetting surface transform.
324             tx.setScale(overlayLeash, 1f, 1f);
325             tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f,
326                     (destinationBounds.height() - overlaySize) / 2f);
327         }
328         startTransaction.apply();
329 
330         tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
331                         this::onClientDrawAtTransitionEnd);
332         finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
333 
334         // Note that finishWct should be free of any actual WM state changes; we are using
335         // it for syncing with the client draw after delayed configuration changes are dispatched.
336         finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
337         return true;
338     }
339 
startOverlayFadeoutAnimation()340     private void startOverlayFadeoutAnimation() {
341         ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
342         animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
343         animator.addListener(new AnimatorListenerAdapter() {
344             @Override
345             public void onAnimationEnd(Animator animation) {
346                 super.onAnimationEnd(animation);
347                 SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
348                 tx.remove(mPipTransitionState.getSwipePipToHomeOverlay());
349                 tx.apply();
350 
351                 // We have fully completed enter-PiP animation after the overlay is gone.
352                 mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
353             }
354         });
355         animator.addUpdateListener(animation -> {
356             float alpha = (float) animation.getAnimatedValue();
357             SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
358             tx.setAlpha(mPipTransitionState.getSwipePipToHomeOverlay(), alpha).apply();
359         });
360         animator.start();
361     }
362 
startBoundsTypeEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)363     private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
364             @NonNull SurfaceControl.Transaction startTransaction,
365             @NonNull SurfaceControl.Transaction finishTransaction,
366             @NonNull Transitions.TransitionFinishCallback finishCallback) {
367         TransitionInfo.Change pipChange = getPipChange(info);
368         if (pipChange == null) {
369             return false;
370         }
371         // cache the PiP task token and leash
372         WindowContainerToken pipTaskToken = pipChange.getContainer();
373 
374         startTransaction.apply();
375         // TODO: b/275910498 Use a new implementation of the PiP animator here.
376         finishCallback.onTransitionFinished(null);
377         return true;
378     }
379 
startAlphaTypeEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)380     private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
381             @NonNull SurfaceControl.Transaction startTransaction,
382             @NonNull SurfaceControl.Transaction finishTransaction,
383             @NonNull Transitions.TransitionFinishCallback finishCallback) {
384         TransitionInfo.Change pipChange = getPipChange(info);
385         if (pipChange == null) {
386             return false;
387         }
388 
389         Rect destinationBounds = pipChange.getEndAbsBounds();
390         SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
391         Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");
392 
393         // Start transition with 0 alpha at the entry bounds.
394         startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top)
395                 .setWindowCrop(pipLeash, destinationBounds.width(), destinationBounds.height())
396                 .setAlpha(pipLeash, 0f);
397 
398         PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
399                 PipAlphaAnimator.FADE_IN);
400         animator.setAnimationEndCallback(() -> {
401             finishCallback.onTransitionFinished(null);
402             // This should update the pip transition state accordingly after we stop playing.
403             onClientDrawAtTransitionEnd();
404         });
405 
406         animator.start();
407         return true;
408     }
409 
startExpandAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)410     private boolean startExpandAnimation(@NonNull TransitionInfo info,
411             @NonNull SurfaceControl.Transaction startTransaction,
412             @NonNull SurfaceControl.Transaction finishTransaction,
413             @NonNull Transitions.TransitionFinishCallback finishCallback) {
414         startTransaction.apply();
415         // TODO: b/275910498 Use a new implementation of the PiP animator here.
416         finishCallback.onTransitionFinished(null);
417         mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
418         return true;
419     }
420 
removePipImmediately(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)421     private boolean removePipImmediately(@NonNull TransitionInfo info,
422             @NonNull SurfaceControl.Transaction startTransaction,
423             @NonNull SurfaceControl.Transaction finishTransaction,
424             @NonNull Transitions.TransitionFinishCallback finishCallback) {
425         startTransaction.apply();
426         finishCallback.onTransitionFinished(null);
427         mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
428         return true;
429     }
430 
431     //
432     // Various helpers to resolve transition requests and infos
433     //
434 
435     @Nullable
getPipChange(TransitionInfo info)436     private TransitionInfo.Change getPipChange(TransitionInfo info) {
437         for (TransitionInfo.Change change : info.getChanges()) {
438             if (change.getTaskInfo() != null
439                     && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
440                 return change;
441             }
442         }
443         return null;
444     }
445 
getEnterPipTransaction(@onNull IBinder transition, @NonNull TransitionRequestInfo request)446     private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
447             @NonNull TransitionRequestInfo request) {
448         // cache the original task token to check for multi-activity case later
449         final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
450         PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
451         mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
452                 pipParams, mPipBoundsAlgorithm);
453 
454         // calculate the entry bounds and notify core to move task to pinned with final bounds
455         final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
456         mPipBoundsState.setBounds(entryBounds);
457 
458         WindowContainerTransaction wct = new WindowContainerTransaction();
459         wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
460         wct.deferConfigToTransitionEnd(pipTask.token);
461         return wct;
462     }
463 
isAutoEnterInButtonNavigation(@onNull TransitionRequestInfo requestInfo)464     private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
465         final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
466         if (pipTask == null) {
467             return false;
468         }
469         if (pipTask.pictureInPictureParams == null) {
470             return false;
471         }
472 
473         // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
474         // implies that we are entering PiP in button navigation mode. This is guaranteed by
475         // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
476         return requestInfo.getType() == TRANSIT_OPEN
477                 && pipTask.pictureInPictureParams.isAutoEnterEnabled();
478     }
479 
isEnterPictureInPictureModeRequest(@onNull TransitionRequestInfo requestInfo)480     private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
481         return requestInfo.getType() == TRANSIT_PIP;
482     }
483 
isLegacyEnter(@onNull TransitionInfo info)484     private boolean isLegacyEnter(@NonNull TransitionInfo info) {
485         TransitionInfo.Change pipChange = getPipChange(info);
486         // If the only change in the changes list is a opening type PiP task,
487         // then this is legacy-enter PiP.
488         return pipChange != null && info.getChanges().size() == 1
489                 && (pipChange.getMode() == TRANSIT_TO_FRONT || pipChange.getMode() == TRANSIT_OPEN);
490     }
491 
isRemovePipTransition(@onNull TransitionInfo info)492     private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
493         if (mPipTransitionState.mPipTaskToken == null) {
494             // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
495             return false;
496         }
497         TransitionInfo.Change pipChange = info.getChange(mPipTransitionState.mPipTaskToken);
498         if (pipChange == null) {
499             // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
500             return false;
501         }
502 
503         boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK
504                 && pipChange.getMode() == TRANSIT_TO_BACK;
505         boolean isPipClosed = info.getType() == TRANSIT_CLOSE
506                 && pipChange.getMode() == TRANSIT_CLOSE;
507         // PiP is being removed if the pinned task is either moved to back or closed.
508         return isPipMovedToBack || isPipClosed;
509     }
510 
511     //
512     // Miscellaneous callbacks and listeners
513     //
514 
onClientDrawAtTransitionEnd()515     private void onClientDrawAtTransitionEnd() {
516         if (mPipTransitionState.getSwipePipToHomeOverlay() != null) {
517             startOverlayFadeoutAnimation();
518         } else if (mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP) {
519             // If we were entering PiP (i.e. playing the animation) with a valid srcRectHint,
520             // and then we get a signal on client finishing its draw after the transition
521             // has ended, then we have fully entered PiP.
522             mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
523         }
524     }
525 
526     @Override
finishTransition(@ullable SurfaceControl.Transaction tx)527     public void finishTransition(@Nullable SurfaceControl.Transaction tx) {
528         WindowContainerTransaction wct = null;
529         if (tx != null && mPipTransitionState.mPipTaskToken != null) {
530             // Outside callers can only provide a transaction to be applied with the final draw.
531             // So no actual WM changes can be applied for this transition after this point.
532             wct = new WindowContainerTransaction();
533             wct.setBoundsChangeTransaction(mPipTransitionState.mPipTaskToken, tx);
534         }
535         if (mFinishCallback != null) {
536             mFinishCallback.onTransitionFinished(wct);
537         }
538     }
539 
540     @Override
onPipTransitionStateChanged(@ipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra)541     public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
542             @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
543         switch (newState) {
544             case PipTransitionState.ENTERING_PIP:
545                 Preconditions.checkState(extra != null,
546                         "No extra bundle for " + mPipTransitionState);
547 
548                 mPipTransitionState.mPipTaskToken = extra.getParcelable(
549                         PIP_TASK_TOKEN, WindowContainerToken.class);
550                 mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
551                         PIP_TASK_LEASH, SurfaceControl.class);
552                 boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
553                         && mPipTransitionState.mPinnedTaskLeash != null;
554 
555                 Preconditions.checkState(hasValidTokenAndLeash,
556                         "Unexpected bundle for " + mPipTransitionState);
557                 break;
558             case PipTransitionState.EXITED_PIP:
559                 mPipTransitionState.mPipTaskToken = null;
560                 mPipTransitionState.mPinnedTaskLeash = null;
561                 break;
562         }
563     }
564 }
565