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.ROTATION_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.view.WindowManager.TRANSIT_PIP; 22 23 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; 24 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; 25 26 import android.annotation.IntDef; 27 import android.annotation.Nullable; 28 import android.app.ActivityTaskManager; 29 import android.app.Flags; 30 import android.app.PictureInPictureParams; 31 import android.app.PictureInPictureUiState; 32 import android.app.TaskInfo; 33 import android.content.ComponentName; 34 import android.content.pm.ActivityInfo; 35 import android.graphics.Point; 36 import android.graphics.Rect; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.view.SurfaceControl; 40 import android.view.WindowManager; 41 import android.window.TransitionInfo; 42 import android.window.TransitionRequestInfo; 43 import android.window.WindowContainerTransaction; 44 45 import androidx.annotation.NonNull; 46 47 import com.android.internal.protolog.ProtoLog; 48 import com.android.wm.shell.ShellTaskOrganizer; 49 import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 50 import com.android.wm.shell.common.pip.PipBoundsState; 51 import com.android.wm.shell.common.pip.PipMenuController; 52 import com.android.wm.shell.protolog.ShellProtoLogGroup; 53 import com.android.wm.shell.sysui.ShellInit; 54 import com.android.wm.shell.transition.DefaultMixedHandler; 55 import com.android.wm.shell.transition.Transitions; 56 57 import java.io.PrintWriter; 58 import java.lang.annotation.Retention; 59 import java.lang.annotation.RetentionPolicy; 60 import java.util.HashMap; 61 import java.util.Map; 62 import java.util.concurrent.Executor; 63 64 /** 65 * Responsible supplying PiP Transitions. 66 */ 67 public abstract class PipTransitionController implements Transitions.TransitionHandler { 68 69 protected final PipBoundsAlgorithm mPipBoundsAlgorithm; 70 protected final PipBoundsState mPipBoundsState; 71 protected final ShellTaskOrganizer mShellTaskOrganizer; 72 protected final PipMenuController mPipMenuController; 73 protected final Transitions mTransitions; 74 private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>(); 75 protected PipTaskOrganizer mPipOrganizer; 76 protected DefaultMixedHandler mMixedHandler; 77 78 public static final int ANIM_TYPE_BOUNDS = 0; 79 public static final int ANIM_TYPE_ALPHA = 1; 80 81 @IntDef(prefix = { "ANIM_TYPE_" }, value = { 82 ANIM_TYPE_BOUNDS, 83 ANIM_TYPE_ALPHA 84 }) 85 @Retention(RetentionPolicy.SOURCE) 86 public @interface AnimationType {} 87 88 89 protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback = 90 new PipAnimationController.PipAnimationCallback() { 91 @Override 92 public void onPipAnimationStart(TaskInfo taskInfo, 93 PipAnimationController.PipTransitionAnimator animator) { 94 final int direction = animator.getTransitionDirection(); 95 sendOnPipTransitionStarted(direction); 96 } 97 98 @Override 99 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, 100 PipAnimationController.PipTransitionAnimator animator) { 101 final int direction = animator.getTransitionDirection(); 102 mPipBoundsState.setBounds(animator.getDestinationBounds()); 103 if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { 104 return; 105 } 106 if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { 107 mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, 108 null /* callback */, true /* withStartDelay*/); 109 } 110 onFinishResize(taskInfo, animator.getDestinationBounds(), 111 animator.getLeashOffset(), direction, tx); 112 sendOnPipTransitionFinished(direction); 113 } 114 115 @Override 116 public void onPipAnimationCancel(TaskInfo taskInfo, 117 PipAnimationController.PipTransitionAnimator animator) { 118 final int direction = animator.getTransitionDirection(); 119 if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { 120 mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, 121 null /* callback */, true /* withStartDelay */); 122 } 123 sendOnPipTransitionCancelled(animator.getTransitionDirection()); 124 } 125 }; 126 127 /** 128 * Called when transition is about to finish. This is usually for performing tasks such as 129 * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework. 130 */ onFinishResize(@onNull TaskInfo taskInfo, @NonNull Rect destinationBounds, @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction, @NonNull SurfaceControl.Transaction tx)131 public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds, 132 @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction, 133 @NonNull SurfaceControl.Transaction tx) { 134 } 135 136 /** 137 * Called when the Shell wants to start an exit Pip transition/animation. 138 */ startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)139 public void startExitTransition(int type, WindowContainerTransaction out, 140 @Nullable Rect destinationBounds) { 141 // Default implementation does nothing. 142 } 143 144 /** 145 * Called when the Shell wants to start an exit-via-expand from Pip transition/animation. 146 */ startExpandTransition(WindowContainerTransaction out, boolean toSplit)147 public void startExpandTransition(WindowContainerTransaction out, boolean toSplit) { 148 // Default implementation does nothing. 149 } 150 151 /** 152 * Called when the Shell wants to start a remove Pip transition/animation. 153 */ startRemoveTransition(boolean withFadeout)154 public void startRemoveTransition(boolean withFadeout) { 155 // Default implementation does nothing. 156 } 157 158 /** 159 * Called when the Shell wants to start resizing Pip transition/animation. 160 * 161 * @param duration the suggested duration for resize animation. 162 */ startResizeTransition(WindowContainerTransaction wct, int duration)163 public void startResizeTransition(WindowContainerTransaction wct, int duration) { 164 // Default implementation does nothing. 165 } 166 167 /** 168 * Called when the transition animation can't continue (eg. task is removed during 169 * animation) 170 */ forceFinishTransition()171 public void forceFinishTransition() { 172 } 173 174 /** Called when the fixed rotation started. */ onFixedRotationStarted()175 public void onFixedRotationStarted() { 176 } 177 178 /** Called when the fixed rotation finished. */ onFixedRotationFinished()179 public void onFixedRotationFinished() { 180 } 181 PipTransitionController( @onNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm)182 public PipTransitionController( 183 @NonNull ShellInit shellInit, 184 @NonNull ShellTaskOrganizer shellTaskOrganizer, 185 @NonNull Transitions transitions, 186 PipBoundsState pipBoundsState, 187 PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm) { 188 mPipBoundsState = pipBoundsState; 189 mPipMenuController = pipMenuController; 190 mShellTaskOrganizer = shellTaskOrganizer; 191 mPipBoundsAlgorithm = pipBoundsAlgorithm; 192 mTransitions = transitions; 193 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 194 shellInit.addInitCallback(this::onInit, this); 195 } 196 } 197 onInit()198 protected void onInit() { 199 mTransitions.addHandler(this); 200 } 201 setPipOrganizer(PipTaskOrganizer pto)202 void setPipOrganizer(PipTaskOrganizer pto) { 203 mPipOrganizer = pto; 204 } 205 setMixedHandler(DefaultMixedHandler mixedHandler)206 public void setMixedHandler(DefaultMixedHandler mixedHandler) { 207 mMixedHandler = mixedHandler; 208 } 209 applyTransaction(WindowContainerTransaction wct)210 public void applyTransaction(WindowContainerTransaction wct) { 211 mShellTaskOrganizer.applyTransaction(wct); 212 } 213 214 /** 215 * Registers {@link PipTransitionCallback} to receive transition callbacks. 216 */ registerPipTransitionCallback( @onNull PipTransitionCallback callback, @NonNull Executor executor)217 public void registerPipTransitionCallback( 218 @NonNull PipTransitionCallback callback, @NonNull Executor executor) { 219 mPipTransitionCallbacks.put(callback, executor); 220 } 221 onStartSwipePipToHome()222 protected void onStartSwipePipToHome() { 223 if (Flags.enablePipUiStateCallbackOnEntering()) { 224 try { 225 ActivityTaskManager.getService().onPictureInPictureUiStateChanged( 226 new PictureInPictureUiState.Builder() 227 .setTransitioningToPip(true) 228 .build()); 229 } catch (RemoteException | IllegalStateException e) { 230 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 231 "Failed to set alert PiP state change."); 232 } 233 } 234 } 235 236 /** 237 * Used in {@link #sendOnPipTransitionStarted(int)} to decide whether we should send the 238 * PictureInPictureUiState change callback on transition start. 239 * For instance, in auto-enter-pip case, {@link #onStartSwipePipToHome()} should have signaled 240 * the app, and we can return {@code true} here to avoid double callback. 241 * 242 * @return {@code true} if there is a ongoing swipe pip to home transition. 243 */ isInSwipePipToHomeTransition()244 protected boolean isInSwipePipToHomeTransition() { 245 return false; 246 } 247 sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)248 protected void sendOnPipTransitionStarted( 249 @PipAnimationController.TransitionDirection int direction) { 250 final Rect pipBounds = mPipBoundsState.getBounds(); 251 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 252 "sendOnPipTransitionStarted direction=%d, bounds=%s", direction, pipBounds); 253 for (Map.Entry<PipTransitionCallback, Executor> entry 254 : mPipTransitionCallbacks.entrySet()) { 255 entry.getValue().execute( 256 () -> entry.getKey().onPipTransitionStarted(direction, pipBounds)); 257 } 258 if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering() 259 && !isInSwipePipToHomeTransition()) { 260 try { 261 ActivityTaskManager.getService().onPictureInPictureUiStateChanged( 262 new PictureInPictureUiState.Builder() 263 .setTransitioningToPip(true) 264 .build()); 265 } catch (RemoteException | IllegalStateException e) { 266 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 267 "Failed to set alert PiP state change."); 268 } 269 } 270 } 271 sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)272 protected void sendOnPipTransitionFinished( 273 @PipAnimationController.TransitionDirection int direction) { 274 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 275 "sendOnPipTransitionFinished direction=%d", direction); 276 for (Map.Entry<PipTransitionCallback, Executor> entry 277 : mPipTransitionCallbacks.entrySet()) { 278 entry.getValue().execute( 279 () -> entry.getKey().onPipTransitionFinished(direction)); 280 } 281 if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { 282 try { 283 ActivityTaskManager.getService().onPictureInPictureUiStateChanged( 284 new PictureInPictureUiState.Builder() 285 .setTransitioningToPip(false) 286 .build()); 287 } catch (RemoteException | IllegalStateException e) { 288 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 289 "Failed to set alert PiP state change."); 290 } 291 } 292 } 293 sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)294 protected void sendOnPipTransitionCancelled( 295 @PipAnimationController.TransitionDirection int direction) { 296 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 297 "sendOnPipTransitionCancelled direction=%d", direction); 298 for (Map.Entry<PipTransitionCallback, Executor> entry 299 : mPipTransitionCallbacks.entrySet()) { 300 entry.getValue().execute( 301 () -> entry.getKey().onPipTransitionCanceled(direction)); 302 } 303 } 304 305 /** 306 * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined 307 * and can be overridden to restore to an alternate windowing mode. 308 */ getOutPipWindowingMode()309 public int getOutPipWindowingMode() { 310 // By default, simply reset the windowing mode to undefined. 311 return WINDOWING_MODE_UNDEFINED; 312 } 313 setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)314 protected void setBoundsStateForEntry(ComponentName componentName, 315 PictureInPictureParams params, 316 ActivityInfo activityInfo) { 317 mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params, 318 mPipBoundsAlgorithm); 319 } 320 321 /** 322 * Called when the display is going to rotate. 323 * 324 * @return {@code true} if it was handled, otherwise the existing pip logic 325 * will deal with rotation. 326 */ handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct)327 public boolean handleRotateDisplay(int startRotation, int endRotation, 328 WindowContainerTransaction wct) { 329 return false; 330 } 331 332 /** @return whether the transition-request represents a pip-entry. */ requestHasPipEnter(@onNull TransitionRequestInfo request)333 public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) { 334 return request.getType() == TRANSIT_PIP; 335 } 336 337 /** Whether a particular change is a window that is entering pip. */ isEnteringPip(@onNull TransitionInfo.Change change, @WindowManager.TransitionType int transitType)338 public boolean isEnteringPip(@NonNull TransitionInfo.Change change, 339 @WindowManager.TransitionType int transitType) { 340 return false; 341 } 342 343 /** Whether a particular package is same as current pip package. */ isPackageActiveInPip(@ullable String packageName)344 public boolean isPackageActiveInPip(@Nullable String packageName) { 345 // No-op, to be handled differently in PIP1 and PIP2 346 return false; 347 } 348 349 /** Add PiP-related changes to `outWCT` for the given request. */ augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)350 public void augmentRequest(@NonNull IBinder transition, 351 @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { 352 throw new IllegalStateException("Request isn't entering PiP"); 353 } 354 355 /** Sets the type of animation when a PiP task appears. */ setEnterAnimationType(@nimationType int type)356 public void setEnterAnimationType(@AnimationType int type) { 357 } 358 359 /** 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)360 public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, 361 @NonNull final SurfaceControl.Transaction startTransaction, 362 @NonNull final SurfaceControl.Transaction finishTransaction, 363 @NonNull final Transitions.TransitionFinishCallback finishCallback) { 364 } 365 366 /** 367 * Applies the proper surface states (rounded corners/shadows) to pip surfaces in `info`. 368 * This is intended to be used when PiP is part of another animation but isn't, itself, 369 * animating (eg. unlocking). 370 * @return `true` if there was a pip in `info`. 371 */ syncPipSurfaceState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)372 public boolean syncPipSurfaceState(@NonNull TransitionInfo info, 373 @NonNull SurfaceControl.Transaction startTransaction, 374 @NonNull SurfaceControl.Transaction finishTransaction) { 375 return false; 376 } 377 378 /** 379 * Gets a change amongst the transition targets that is in a different final orientation than 380 * the display, signalling a potential fixed rotation transition. 381 */ 382 @Nullable findFixedRotationChange(@onNull TransitionInfo info)383 public TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { 384 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 385 final TransitionInfo.Change change = info.getChanges().get(i); 386 if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { 387 return change; 388 } 389 } 390 return null; 391 } 392 393 /** End the currently-playing PiP animation. */ end()394 public void end() { 395 } 396 397 /** 398 * Finish the current transition if possible. 399 */ finishTransition()400 public void finishTransition() { 401 } 402 403 /** 404 * End the currently-playing PiP animation. 405 * 406 * @param onTransitionEnd callback to run upon finishing the playing transition. 407 */ end(@ullable Runnable onTransitionEnd)408 public void end(@Nullable Runnable onTransitionEnd) { 409 } 410 411 /** Starts the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */ startHighPerfSession()412 public void startHighPerfSession() {} 413 414 /** Closes the {@link android.window.SystemPerformanceHinter.HighPerfSession}. */ closeHighPerfSession()415 public void closeHighPerfSession() {} 416 417 /** 418 * Callback interface for PiP transitions (both from and to PiP mode) 419 */ 420 public interface PipTransitionCallback { 421 /** 422 * Callback when the pip transition is started. 423 */ onPipTransitionStarted(int direction, Rect pipBounds)424 void onPipTransitionStarted(int direction, Rect pipBounds); 425 426 /** 427 * Callback when the pip transition is finished. 428 */ onPipTransitionFinished(int direction)429 void onPipTransitionFinished(int direction); 430 431 /** 432 * Callback when the pip transition is cancelled. 433 */ onPipTransitionCanceled(int direction)434 void onPipTransitionCanceled(int direction); 435 } 436 437 /** 438 * Dumps internal states. 439 */ dump(PrintWriter pw, String prefix)440 public void dump(PrintWriter pw, String prefix) {} 441 } 442