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