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