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