• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.transition;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.view.WindowManager.TRANSIT_PIP;
21 import static android.view.WindowManager.TRANSIT_TO_BACK;
22 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
23 
24 import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_ALPHA;
25 import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
26 import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
27 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
28 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
29 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
30 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
31 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
32 import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.view.SurfaceControl;
37 import android.window.TransitionInfo;
38 
39 import com.android.internal.protolog.ProtoLog;
40 import com.android.wm.shell.common.pip.PipUtils;
41 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
42 import com.android.wm.shell.pip.PipTransitionController;
43 import com.android.wm.shell.protolog.ShellProtoLogGroup;
44 import com.android.wm.shell.splitscreen.SplitScreen;
45 import com.android.wm.shell.splitscreen.StageCoordinator;
46 
47 public class MixedTransitionHelper {
animateEnterPipFromSplit( @onNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler, @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler, boolean replacingPip)48     static boolean animateEnterPipFromSplit(
49             @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
50             @NonNull SurfaceControl.Transaction startTransaction,
51             @NonNull SurfaceControl.Transaction finishTransaction,
52             @NonNull Transitions.TransitionFinishCallback finishCallback,
53             @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler,
54             @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler,
55             boolean replacingPip) {
56         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
57                 + "entering PIP while Split-Screen is foreground.");
58         TransitionInfo.Change pipChange = null;
59         TransitionInfo.Change pipActivityChange = null;
60         TransitionInfo.Change wallpaper = null;
61         final TransitionInfo everythingElse =
62                 subCopy(info, TRANSIT_TO_BACK, true /* changes */);
63         boolean homeIsOpening = false;
64         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
65             TransitionInfo.Change change = info.getChanges().get(i);
66             if (pipHandler.isEnteringPip(change, info.getType())) {
67                 if (pipChange != null) {
68                     throw new IllegalStateException("More than 1 pip-entering changes in one"
69                             + " transition? " + info);
70                 }
71                 pipChange = change;
72                 // going backwards, so remove-by-index is fine.
73                 everythingElse.getChanges().remove(i);
74             } else if (change.getTaskInfo() == null && change.getParent() != null
75                     && pipChange != null && change.getParent().equals(pipChange.getContainer())) {
76                 // Cache the PiP activity if it's a target and cached pip task change is its parent;
77                 // note that we are bottom-to-top, so if such activity has a task
78                 // that is also a target, then it must have been cached already as pipChange.
79                 pipActivityChange = change;
80                 everythingElse.getChanges().remove(i);
81             } else if (isHomeOpening(change)) {
82                 homeIsOpening = true;
83             } else if (isWallpaper(change)) {
84                 wallpaper = change;
85             }
86         }
87         if (pipChange == null) {
88             // um, something probably went wrong.
89             return false;
90         }
91         final boolean isGoingHome = homeIsOpening;
92         Transitions.TransitionFinishCallback finishCB = (wct) -> {
93             --mixed.mInFlightSubAnimations;
94             mixed.joinFinishArgs(wct);
95             if (mixed.mInFlightSubAnimations > 0) return;
96             if (isGoingHome) {
97                 splitHandler.onTransitionAnimationComplete();
98             }
99             finishCallback.onTransitionFinished(mixed.mFinishWCT);
100         };
101         if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
102                 != SPLIT_POSITION_UNDEFINED) {
103             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
104                     + "since entering-PiP caused us to leave split and return home.");
105             // We need to split the transition into 2 parts: the pip part (animated by pip)
106             // and the dismiss-part (animated by launcher).
107             mixed.mInFlightSubAnimations = 2;
108             // immediately make the wallpaper visible (so that we don't see it pop-in during
109             // the time it takes to start recents animation (which is remote).
110             if (wallpaper != null) {
111                 startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
112             }
113             // make a new startTransaction because pip's startEnterAnimation "consumes" it so
114             // we need a separate one to send over to launcher.
115             SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
116             @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
117             if (splitHandler.isSplitScreenVisible() && !replacingPip) {
118                 // The non-going home case, we could be pip-ing one of the split stages and keep
119                 // showing the other
120                 for (int i = info.getChanges().size() - 1; i >= 0; --i) {
121                     TransitionInfo.Change change = info.getChanges().get(i);
122                     if (change == pipChange) {
123                         // Ignore the change/task that's going into Pip
124                         continue;
125                     }
126                     @SplitScreen.StageType int splitItemStage =
127                             splitHandler.getSplitItemStage(change.getLastParent());
128                     if (splitItemStage != STAGE_TYPE_UNDEFINED) {
129                         topStageToKeep = splitItemStage;
130                         break;
131                     }
132                 }
133 
134                 // Let split update internal state for dismiss.
135                 splitHandler.prepareDismissAnimation(topStageToKeep,
136                         EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
137                         finishTransaction);
138             }
139 
140             // We are trying to accommodate launcher's close animation which can't handle the
141             // divider-bar, so if split-handler is closing the divider-bar, just hide it and
142             // remove from transition info.
143             for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
144                 if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR)
145                         != 0) {
146                     everythingElse.getChanges().remove(i);
147                     break;
148                 }
149             }
150 
151             pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
152             if (PipUtils.isPip2ExperimentEnabled()) {
153                 TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */);
154                 pipInfo.getChanges().add(pipChange);
155                 if (pipActivityChange != null) {
156                     pipInfo.getChanges().add(pipActivityChange);
157                 }
158                 pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction,
159                         finishTransaction, finishCB);
160             } else {
161                 pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
162                         finishCB);
163             }
164             // Dispatch the rest of the transition normally. This will most-likely be taken by
165             // recents or default handler.
166             mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
167                     otherStartT, finishTransaction, finishCB, mixedHandler);
168         } else {
169             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
170                     + "forward animation to Pip-Handler.");
171             // This happens if the pip-ing activity is in a multi-activity task (and thus a
172             // new pip task is spawned). In this case, we don't actually exit split so we can
173             // just let pip transition handle the animation verbatim.
174             mixed.mInFlightSubAnimations = 1;
175             pipHandler.startAnimation(
176                     mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
177         }
178         return true;
179     }
180 
181     /**
182      * Check to see if we're only closing split to enter pip or if we're replacing pip with
183      * another task. If we are replacing, this will return the change for the task we are replacing
184      * pip with
185      *
186      * @param info Any number of changes
187      * @param pipChange TransitionInfo.Change indicating the task that is being pipped
188      * @param splitMainStageRootId MainStage's rootTaskInfo's id
189      * @param splitSideStageRootId SideStage's rootTaskInfo's id
190      * @param lastPipSplitStage The last stage that {@param pipChange} was in
191      * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null}
192      *         otherwise
193      */
194     @Nullable
getPipReplacingChange(TransitionInfo info, TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId, @SplitScreen.StageType int lastPipSplitStage)195     public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info,
196             TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId,
197             @SplitScreen.StageType int lastPipSplitStage) {
198         int lastPipParentTask = -1;
199         if (lastPipSplitStage == STAGE_TYPE_MAIN) {
200             lastPipParentTask = splitMainStageRootId;
201         } else if (lastPipSplitStage == STAGE_TYPE_SIDE) {
202             lastPipParentTask = splitSideStageRootId;
203         }
204 
205         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
206             TransitionInfo.Change change = info.getChanges().get(i);
207             if (change == pipChange || !isOpeningMode(change.getMode()) ||
208                     change.getTaskInfo() == null) {
209                 // Ignore the change/task that's going into Pip or not opening
210                 continue;
211             }
212 
213             if (change.getTaskInfo().parentTaskId == lastPipParentTask) {
214                 return change;
215             }
216         }
217         return null;
218     }
219 
isHomeOpening(@onNull TransitionInfo.Change change)220     private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
221         return change.getTaskInfo() != null
222                 && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
223     }
224 
isWallpaper(@onNull TransitionInfo.Change change)225     private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
226         return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
227     }
228 
animateKeyguard( @onNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull KeyguardTransitionHandler keyguardHandler, PipTransitionController pipHandler)229     static boolean animateKeyguard(
230             @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
231             @NonNull SurfaceControl.Transaction startTransaction,
232             @NonNull SurfaceControl.Transaction finishTransaction,
233             @NonNull Transitions.TransitionFinishCallback finishCallback,
234             @NonNull KeyguardTransitionHandler keyguardHandler,
235             PipTransitionController pipHandler) {
236         if (mixed.mFinishT == null) {
237             mixed.mFinishT = finishTransaction;
238             mixed.mFinishCB = finishCallback;
239         }
240         // Sync pip state.
241         if (pipHandler != null) {
242             pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
243         }
244         return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
245     }
246 }
247