1 /* 2 * Copyright (C) 2021 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.pip; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 import static android.view.WindowManager.TRANSIT_PIP; 21 22 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; 23 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; 24 25 import android.annotation.Nullable; 26 import android.app.ActivityTaskManager; 27 import android.app.Flags; 28 import android.app.PictureInPictureParams; 29 import android.app.PictureInPictureUiState; 30 import android.app.TaskInfo; 31 import android.content.ComponentName; 32 import android.content.pm.ActivityInfo; 33 import android.graphics.Rect; 34 import android.os.IBinder; 35 import android.os.RemoteException; 36 import android.view.SurfaceControl; 37 import android.view.WindowManager; 38 import android.window.TransitionInfo; 39 import android.window.TransitionRequestInfo; 40 import android.window.WindowContainerTransaction; 41 42 import androidx.annotation.NonNull; 43 44 import com.android.internal.protolog.common.ProtoLog; 45 import com.android.wm.shell.ShellTaskOrganizer; 46 import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 47 import com.android.wm.shell.common.pip.PipBoundsState; 48 import com.android.wm.shell.common.pip.PipMenuController; 49 import com.android.wm.shell.common.split.SplitScreenUtils; 50 import com.android.wm.shell.protolog.ShellProtoLogGroup; 51 import com.android.wm.shell.sysui.ShellInit; 52 import com.android.wm.shell.transition.DefaultMixedHandler; 53 import com.android.wm.shell.transition.Transitions; 54 55 import java.io.PrintWriter; 56 import java.util.ArrayList; 57 import java.util.List; 58 59 /** 60 * Responsible supplying PiP Transitions. 61 */ 62 public abstract class PipTransitionController implements Transitions.TransitionHandler { 63 64 protected final PipBoundsAlgorithm mPipBoundsAlgorithm; 65 protected final PipBoundsState mPipBoundsState; 66 protected final ShellTaskOrganizer mShellTaskOrganizer; 67 protected final PipMenuController mPipMenuController; 68 protected final Transitions mTransitions; 69 private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); 70 protected PipTaskOrganizer mPipOrganizer; 71 protected DefaultMixedHandler mMixedHandler; 72 73 protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback = 74 new PipAnimationController.PipAnimationCallback() { 75 @Override 76 public void onPipAnimationStart(TaskInfo taskInfo, 77 PipAnimationController.PipTransitionAnimator animator) { 78 final int direction = animator.getTransitionDirection(); 79 sendOnPipTransitionStarted(direction); 80 } 81 82 @Override 83 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, 84 PipAnimationController.PipTransitionAnimator animator) { 85 final int direction = animator.getTransitionDirection(); 86 mPipBoundsState.setBounds(animator.getDestinationBounds()); 87 if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { 88 return; 89 } 90 if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { 91 mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, 92 null /* callback */, true /* withStartDelay*/); 93 } 94 onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx); 95 sendOnPipTransitionFinished(direction); 96 } 97 98 @Override 99 public void onPipAnimationCancel(TaskInfo taskInfo, 100 PipAnimationController.PipTransitionAnimator animator) { 101 final int direction = animator.getTransitionDirection(); 102 if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { 103 mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, 104 null /* callback */, true /* withStartDelay */); 105 } 106 sendOnPipTransitionCancelled(animator.getTransitionDirection()); 107 } 108 }; 109 110 /** 111 * Called when transition is about to finish. This is usually for performing tasks such as 112 * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework. 113 */ onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, SurfaceControl.Transaction tx)114 public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, 115 @PipAnimationController.TransitionDirection int direction, 116 SurfaceControl.Transaction tx) { 117 } 118 119 /** 120 * Called when the Shell wants to start an exit Pip transition/animation. 121 */ startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)122 public void startExitTransition(int type, WindowContainerTransaction out, 123 @Nullable Rect destinationBounds) { 124 // Default implementation does nothing. 125 } 126 127 /** 128 * Called when the Shell wants to start resizing Pip transition/animation. 129 */ startResizeTransition(WindowContainerTransaction wct)130 public void startResizeTransition(WindowContainerTransaction wct) { 131 // Default implementation does nothing. 132 } 133 134 /** 135 * Called when the transition animation can't continue (eg. task is removed during 136 * animation) 137 */ forceFinishTransition()138 public void forceFinishTransition() { 139 } 140 141 /** Called when the fixed rotation started. */ onFixedRotationStarted()142 public void onFixedRotationStarted() { 143 } 144 145 /** Called when the fixed rotation finished. */ onFixedRotationFinished()146 public void onFixedRotationFinished() { 147 } 148 PipTransitionController( @onNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm)149 public PipTransitionController( 150 @NonNull ShellInit shellInit, 151 @NonNull ShellTaskOrganizer shellTaskOrganizer, 152 @NonNull Transitions transitions, 153 PipBoundsState pipBoundsState, 154 PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) { 155 mPipBoundsState = pipBoundsState; 156 mPipMenuController = pipMenuController; 157 mShellTaskOrganizer = shellTaskOrganizer; 158 mPipBoundsAlgorithm = pipBoundsAlgorithm; 159 mTransitions = transitions; 160 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 161 shellInit.addInitCallback(this::onInit, this); 162 } 163 } 164 onInit()165 protected void onInit() { 166 mTransitions.addHandler(this); 167 } 168 setPipOrganizer(PipTaskOrganizer pto)169 void setPipOrganizer(PipTaskOrganizer pto) { 170 mPipOrganizer = pto; 171 } 172 setMixedHandler(DefaultMixedHandler mixedHandler)173 public void setMixedHandler(DefaultMixedHandler mixedHandler) { 174 mMixedHandler = mixedHandler; 175 } 176 applyTransaction(WindowContainerTransaction wct)177 public void applyTransaction(WindowContainerTransaction wct) { 178 mShellTaskOrganizer.applyTransaction(wct); 179 } 180 181 /** 182 * Registers {@link PipTransitionCallback} to receive transition callbacks. 183 */ registerPipTransitionCallback(PipTransitionCallback callback)184 public void registerPipTransitionCallback(PipTransitionCallback callback) { 185 mPipTransitionCallbacks.add(callback); 186 } 187 sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)188 protected void sendOnPipTransitionStarted( 189 @PipAnimationController.TransitionDirection int direction) { 190 final Rect pipBounds = mPipBoundsState.getBounds(); 191 for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { 192 final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); 193 callback.onPipTransitionStarted(direction, pipBounds); 194 } 195 if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { 196 try { 197 ActivityTaskManager.getService().onPictureInPictureUiStateChanged( 198 new PictureInPictureUiState.Builder() 199 .setTransitioningToPip(true) 200 .build()); 201 } catch (RemoteException | IllegalStateException e) { 202 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 203 "Failed to set alert PiP state change."); 204 } 205 } 206 } 207 sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)208 protected void sendOnPipTransitionFinished( 209 @PipAnimationController.TransitionDirection int direction) { 210 for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { 211 final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); 212 callback.onPipTransitionFinished(direction); 213 } 214 if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { 215 try { 216 ActivityTaskManager.getService().onPictureInPictureUiStateChanged( 217 new PictureInPictureUiState.Builder() 218 .setTransitioningToPip(false) 219 .build()); 220 } catch (RemoteException | IllegalStateException e) { 221 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 222 "Failed to set alert PiP state change."); 223 } 224 } 225 } 226 sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)227 protected void sendOnPipTransitionCancelled( 228 @PipAnimationController.TransitionDirection int direction) { 229 for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { 230 final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); 231 callback.onPipTransitionCanceled(direction); 232 } 233 } 234 235 /** 236 * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined 237 * and can be overridden to restore to an alternate windowing mode. 238 */ getOutPipWindowingMode()239 public int getOutPipWindowingMode() { 240 // By default, simply reset the windowing mode to undefined. 241 return WINDOWING_MODE_UNDEFINED; 242 } 243 setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)244 protected void setBoundsStateForEntry(ComponentName componentName, 245 PictureInPictureParams params, 246 ActivityInfo activityInfo) { 247 mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params, 248 mPipBoundsAlgorithm); 249 } 250 251 /** 252 * Called when the display is going to rotate. 253 * 254 * @return {@code true} if it was handled, otherwise the existing pip logic 255 * will deal with rotation. 256 */ handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct)257 public boolean handleRotateDisplay(int startRotation, int endRotation, 258 WindowContainerTransaction wct) { 259 return false; 260 } 261 262 /** @return whether the transition-request represents a pip-entry. */ requestHasPipEnter(@onNull TransitionRequestInfo request)263 public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) { 264 return request.getType() == TRANSIT_PIP; 265 } 266 267 /** Whether a particular change is a window that is entering pip. */ isEnteringPip(@onNull TransitionInfo.Change change, @WindowManager.TransitionType int transitType)268 public boolean isEnteringPip(@NonNull TransitionInfo.Change change, 269 @WindowManager.TransitionType int transitType) { 270 return false; 271 } 272 273 /** Whether a particular package is same as current pip package. */ isPackageActiveInPip(String packageName)274 public boolean isPackageActiveInPip(String packageName) { 275 final TaskInfo inPipTask = mPipOrganizer.getTaskInfo(); 276 return packageName != null && inPipTask != null && mPipOrganizer.isInPip() 277 && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent)); 278 } 279 280 /** Add PiP-related changes to `outWCT` for the given request. */ augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)281 public void augmentRequest(@NonNull IBinder transition, 282 @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { 283 throw new IllegalStateException("Request isn't entering PiP"); 284 } 285 286 /** Sets the type of animation when a PiP task appears. */ setEnterAnimationType(@ipAnimationController.AnimationType int type)287 public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { 288 } 289 290 /** Play a transition animation for entering PiP on a specific PiP change. */ startEnterAnimation(@onNull final TransitionInfo.Change pipChange, @NonNull final SurfaceControl.Transaction startTransaction, @NonNull final SurfaceControl.Transaction finishTransaction, @NonNull final Transitions.TransitionFinishCallback finishCallback)291 public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, 292 @NonNull final SurfaceControl.Transaction startTransaction, 293 @NonNull final SurfaceControl.Transaction finishTransaction, 294 @NonNull final Transitions.TransitionFinishCallback finishCallback) { 295 } 296 297 /** 298 * Applies the proper surface states (rounded corners/shadows) to pip surfaces in `info`. 299 * This is intended to be used when PiP is part of another animation but isn't, itself, 300 * animating (eg. unlocking). 301 * @return `true` if there was a pip in `info`. 302 */ syncPipSurfaceState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)303 public boolean syncPipSurfaceState(@NonNull TransitionInfo info, 304 @NonNull SurfaceControl.Transaction startTransaction, 305 @NonNull SurfaceControl.Transaction finishTransaction) { 306 return false; 307 } 308 309 /** End the currently-playing PiP animation. */ end()310 public void end() { 311 } 312 313 /** 314 * Finish the current transition if possible. 315 * 316 * @param tx transaction to be applied with a potentially new draw after finishing. 317 */ finishTransition(@ullable SurfaceControl.Transaction tx)318 public void finishTransition(@Nullable SurfaceControl.Transaction tx) { 319 } 320 321 /** 322 * End the currently-playing PiP animation. 323 * 324 * @param onTransitionEnd callback to run upon finishing the playing transition. 325 */ end(@ullable Runnable onTransitionEnd)326 public void end(@Nullable Runnable onTransitionEnd) { 327 } 328 329 /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */ startHighPerfSession()330 public void startHighPerfSession() {} 331 332 /** Closes the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */ closeHighPerfSession()333 public void closeHighPerfSession() {} 334 335 /** 336 * Callback interface for PiP transitions (both from and to PiP mode) 337 */ 338 public interface PipTransitionCallback { 339 /** 340 * Callback when the pip transition is started. 341 */ onPipTransitionStarted(int direction, Rect pipBounds)342 void onPipTransitionStarted(int direction, Rect pipBounds); 343 344 /** 345 * Callback when the pip transition is finished. 346 */ onPipTransitionFinished(int direction)347 void onPipTransitionFinished(int direction); 348 349 /** 350 * Callback when the pip transition is cancelled. 351 */ onPipTransitionCanceled(int direction)352 void onPipTransitionCanceled(int direction); 353 } 354 355 /** 356 * Dumps internal states. 357 */ dump(PrintWriter pw, String prefix)358 public void dump(PrintWriter pw, String prefix) {} 359 } 360