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.pip2.phone; 18 19 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 21 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 24 import android.annotation.NonNull; 25 import android.app.ActivityManager; 26 import android.app.PictureInPictureParams; 27 import android.app.TaskInfo; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.ActivityInfo; 31 import android.content.res.Configuration; 32 import android.graphics.Rect; 33 import android.os.Bundle; 34 import android.os.Debug; 35 import android.util.Log; 36 import android.view.SurfaceControl; 37 import android.window.DesktopExperienceFlags; 38 import android.window.DisplayAreaInfo; 39 import android.window.WindowContainerTransaction; 40 41 import androidx.annotation.BinderThread; 42 import androidx.annotation.Nullable; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.protolog.ProtoLog; 46 import com.android.internal.util.Preconditions; 47 import com.android.wm.shell.R; 48 import com.android.wm.shell.ShellTaskOrganizer; 49 import com.android.wm.shell.common.DisplayChangeController; 50 import com.android.wm.shell.common.DisplayController; 51 import com.android.wm.shell.common.DisplayInsetsController; 52 import com.android.wm.shell.common.DisplayLayout; 53 import com.android.wm.shell.common.ExternalInterfaceBinder; 54 import com.android.wm.shell.common.ImeListener; 55 import com.android.wm.shell.common.RemoteCallable; 56 import com.android.wm.shell.common.ShellExecutor; 57 import com.android.wm.shell.common.SingleInstanceRemoteListener; 58 import com.android.wm.shell.common.TaskStackListenerCallback; 59 import com.android.wm.shell.common.TaskStackListenerImpl; 60 import com.android.wm.shell.common.pip.IPip; 61 import com.android.wm.shell.common.pip.IPipAnimationListener; 62 import com.android.wm.shell.common.pip.PipAppOpsListener; 63 import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 64 import com.android.wm.shell.common.pip.PipBoundsState; 65 import com.android.wm.shell.common.pip.PipDisplayLayoutState; 66 import com.android.wm.shell.common.pip.PipUiEventLogger; 67 import com.android.wm.shell.common.pip.PipUtils; 68 import com.android.wm.shell.pip.Pip; 69 import com.android.wm.shell.protolog.ShellProtoLogGroup; 70 import com.android.wm.shell.sysui.ConfigurationChangeListener; 71 import com.android.wm.shell.sysui.ShellCommandHandler; 72 import com.android.wm.shell.sysui.ShellController; 73 import com.android.wm.shell.sysui.ShellInit; 74 75 import java.io.PrintWriter; 76 import java.util.ArrayList; 77 import java.util.List; 78 import java.util.function.Consumer; 79 80 /** 81 * Manages the picture-in-picture (PIP) UI and states for Phones. 82 */ 83 public class PipController implements ConfigurationChangeListener, 84 PipTransitionState.PipTransitionStateChangedListener, 85 DisplayController.OnDisplaysChangedListener, 86 DisplayChangeController.OnDisplayChangingListener, RemoteCallable<PipController> { 87 private static final String TAG = PipController.class.getSimpleName(); 88 private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds"; 89 private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay"; 90 91 private final Context mContext; 92 private final ShellCommandHandler mShellCommandHandler; 93 private final ShellController mShellController; 94 private final DisplayController mDisplayController; 95 private final DisplayInsetsController mDisplayInsetsController; 96 private final PipBoundsState mPipBoundsState; 97 private final PipBoundsAlgorithm mPipBoundsAlgorithm; 98 private final PipDisplayLayoutState mPipDisplayLayoutState; 99 private final PipScheduler mPipScheduler; 100 private final TaskStackListenerImpl mTaskStackListener; 101 private final ShellTaskOrganizer mShellTaskOrganizer; 102 private final PipTransitionState mPipTransitionState; 103 private final PipTouchHandler mPipTouchHandler; 104 private final PipAppOpsListener mPipAppOpsListener; 105 private final PhonePipMenuController mPipMenuController; 106 private final PipUiEventLogger mPipUiEventLogger; 107 private final ShellExecutor mMainExecutor; 108 private final PipImpl mImpl; 109 private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); 110 111 // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents. 112 @Nullable private PipAnimationListener mPipRecentsAnimationListener; 113 114 @VisibleForTesting 115 interface PipAnimationListener { 116 /** 117 * Notifies the listener that the Pip animation is started. 118 */ onPipAnimationStarted()119 void onPipAnimationStarted(); 120 121 /** 122 * Notifies the listener about PiP resource dimensions changed. 123 * Listener can expect an immediate callback the first time they attach. 124 * 125 * @param cornerRadius the pixel value of the corner radius, zero means it's disabled. 126 * @param shadowRadius the pixel value of the shadow radius, zero means it's disabled. 127 */ onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius)128 void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius); 129 130 /** 131 * Notifies the listener that user leaves PiP by tapping on the expand button. 132 */ onExpandPip()133 void onExpandPip(); 134 } 135 PipController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, PipDisplayLayoutState pipDisplayLayoutState, PipScheduler pipScheduler, TaskStackListenerImpl taskStackListener, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor)136 private PipController(Context context, 137 ShellInit shellInit, 138 ShellCommandHandler shellCommandHandler, 139 ShellController shellController, 140 DisplayController displayController, 141 DisplayInsetsController displayInsetsController, 142 PipBoundsState pipBoundsState, 143 PipBoundsAlgorithm pipBoundsAlgorithm, 144 PipDisplayLayoutState pipDisplayLayoutState, 145 PipScheduler pipScheduler, 146 TaskStackListenerImpl taskStackListener, 147 ShellTaskOrganizer shellTaskOrganizer, 148 PipTransitionState pipTransitionState, 149 PipTouchHandler pipTouchHandler, 150 PipAppOpsListener pipAppOpsListener, 151 PhonePipMenuController pipMenuController, 152 PipUiEventLogger pipUiEventLogger, 153 ShellExecutor mainExecutor) { 154 mContext = context; 155 mShellCommandHandler = shellCommandHandler; 156 mShellController = shellController; 157 mDisplayController = displayController; 158 mDisplayInsetsController = displayInsetsController; 159 mPipBoundsState = pipBoundsState; 160 mPipBoundsAlgorithm = pipBoundsAlgorithm; 161 mPipDisplayLayoutState = pipDisplayLayoutState; 162 mPipScheduler = pipScheduler; 163 mTaskStackListener = taskStackListener; 164 mShellTaskOrganizer = shellTaskOrganizer; 165 mPipTransitionState = pipTransitionState; 166 mPipTransitionState.addPipTransitionStateChangedListener(this); 167 mPipTouchHandler = pipTouchHandler; 168 mPipAppOpsListener = pipAppOpsListener; 169 mPipMenuController = pipMenuController; 170 mPipUiEventLogger = pipUiEventLogger; 171 mMainExecutor = mainExecutor; 172 mImpl = new PipImpl(); 173 174 if (PipUtils.isPip2ExperimentEnabled()) { 175 shellInit.addInitCallback(this::onInit, this); 176 } 177 } 178 179 /** 180 * Instantiates {@link PipController}, returns {@code null} if the feature not supported. 181 */ create(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, PipDisplayLayoutState pipDisplayLayoutState, PipScheduler pipScheduler, TaskStackListenerImpl taskStackListener, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor)182 public static PipController create(Context context, 183 ShellInit shellInit, 184 ShellCommandHandler shellCommandHandler, 185 ShellController shellController, 186 DisplayController displayController, 187 DisplayInsetsController displayInsetsController, 188 PipBoundsState pipBoundsState, 189 PipBoundsAlgorithm pipBoundsAlgorithm, 190 PipDisplayLayoutState pipDisplayLayoutState, 191 PipScheduler pipScheduler, 192 TaskStackListenerImpl taskStackListener, 193 ShellTaskOrganizer shellTaskOrganizer, 194 PipTransitionState pipTransitionState, 195 PipTouchHandler pipTouchHandler, 196 PipAppOpsListener pipAppOpsListener, 197 PhonePipMenuController pipMenuController, 198 PipUiEventLogger pipUiEventLogger, 199 ShellExecutor mainExecutor) { 200 if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { 201 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 202 "%s: Device doesn't support Pip feature", TAG); 203 return null; 204 } 205 return new PipController(context, shellInit, shellCommandHandler, shellController, 206 displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm, 207 pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, 208 pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController, 209 pipUiEventLogger, mainExecutor); 210 } 211 getPipImpl()212 public PipImpl getPipImpl() { 213 return mImpl; 214 } 215 onInit()216 private void onInit() { 217 mShellCommandHandler.addDumpCallback(this::dump, this); 218 // Ensure that we have the display info in case we get calls to update the bounds before the 219 // listener calls back 220 mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId()); 221 DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay()); 222 mPipDisplayLayoutState.setDisplayLayout(layout); 223 224 mDisplayController.addDisplayChangingController(this); 225 mDisplayController.addDisplayWindowListener(this); 226 mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), 227 new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { 228 @Override 229 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { 230 mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight); 231 } 232 }); 233 234 // Allow other outside processes to bind to PiP controller using the key below. 235 mShellController.addExternalInterface(IPip.DESCRIPTOR, 236 this::createExternalInterface, this); 237 mShellController.addConfigurationChangeListener(this); 238 239 mTaskStackListener.addListener(new TaskStackListenerCallback() { 240 @Override 241 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 242 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 243 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 244 "onActivityRestartAttempt: topActivity=%s, wasVisible=%b", 245 task.topActivity, wasVisible); 246 if (task.getWindowingMode() != WINDOWING_MODE_PINNED || !wasVisible) { 247 return; 248 } 249 mPipScheduler.scheduleExitPipViaExpand(); 250 } 251 }); 252 253 mPipAppOpsListener.setCallback(mPipTouchHandler.getMotionHelper()); 254 } 255 createExternalInterface()256 private ExternalInterfaceBinder createExternalInterface() { 257 return new IPipImpl(this); 258 } 259 260 // 261 // RemoteCallable implementations 262 // 263 264 @Override getContext()265 public Context getContext() { 266 return mContext; 267 } 268 269 @Override getRemoteCallExecutor()270 public ShellExecutor getRemoteCallExecutor() { 271 return mMainExecutor; 272 } 273 274 // 275 // ConfigurationChangeListener implementations 276 // 277 278 @Override onConfigurationChanged(Configuration newConfiguration)279 public void onConfigurationChanged(Configuration newConfiguration) { 280 mPipDisplayLayoutState.onConfigurationChanged(); 281 mPipTouchHandler.onConfigurationChanged(); 282 } 283 284 @Override onDensityOrFontScaleChanged()285 public void onDensityOrFontScaleChanged() { 286 onPipResourceDimensionsChanged(); 287 } 288 289 @Override onThemeChanged()290 public void onThemeChanged() { 291 setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay())); 292 } 293 294 // 295 // DisplayController.OnDisplaysChangedListener and 296 // DisplayChangeController.OnDisplayChangingListener implementations 297 // 298 299 @Override onDisplayAdded(int displayId)300 public void onDisplayAdded(int displayId) { 301 if (displayId != mPipDisplayLayoutState.getDisplayId()) { 302 return; 303 } 304 setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); 305 } 306 307 @Override onDisplayRemoved(int displayId)308 public void onDisplayRemoved(int displayId) { 309 // If PiP was active on an external display that is removed, clean up states and set 310 // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY. 311 if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() 312 && mPipTransitionState.isInPip() 313 && displayId == mPipDisplayLayoutState.getDisplayId() 314 && displayId != DEFAULT_DISPLAY) { 315 mPipTransitionState.setState(PipTransitionState.EXITING_PIP); 316 mPipTransitionState.setState(PipTransitionState.EXITED_PIP); 317 318 mPipDisplayLayoutState.setDisplayId(DEFAULT_DISPLAY); 319 mPipDisplayLayoutState.setDisplayLayout( 320 mDisplayController.getDisplayLayout(DEFAULT_DISPLAY)); 321 } 322 } 323 324 /** 325 * A callback for any observed transition that contains a display change in its 326 * {@link android.window.TransitionRequestInfo}, 327 */ 328 @Override onDisplayChange(int displayId, int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t)329 public void onDisplayChange(int displayId, int fromRotation, int toRotation, 330 @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) { 331 if (displayId != mPipDisplayLayoutState.getDisplayId()) { 332 return; 333 } 334 final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()); 335 final float boundsScale = mPipBoundsState.getBoundsScale(); 336 337 // Update the display layout caches even if we are not in PiP. 338 setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); 339 if (toRotation != ROTATION_UNDEFINED) { 340 // Make sure we rotate to final rotation ourselves in case display change is coming 341 // from the remote rotation as a part of an already collecting transition. 342 mPipDisplayLayoutState.rotateTo(toRotation); 343 } 344 345 if (!mPipTransitionState.isInPip() 346 && mPipTransitionState.getState() != PipTransitionState.ENTERING_PIP) { 347 // Skip the PiP-relevant updates if we aren't in a valid PiP state. 348 if (mPipTransitionState.isInFixedRotation()) { 349 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 350 "Fixed rotation flag shouldn't be set while in an invalid PiP state"); 351 } 352 return; 353 } 354 355 mPipMenuController.hideMenu(); 356 357 if (mPipTransitionState.isInFixedRotation()) { 358 // Do not change the bounds when in fixed rotation, but do update the movement bounds 359 // based on the current bounds state and potentially new display layout. 360 mPipTouchHandler.updateMovementBounds(); 361 mPipTransitionState.setInFixedRotation(false); 362 } else { 363 Rect toBounds = new Rect(0, 0, 364 (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale), 365 (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale)); 366 // Update the caches to reflect the new display layout in the movement bounds; 367 // temporarily update bounds to be at the top left for the movement bounds calculation. 368 mPipBoundsState.setBounds(toBounds); 369 mPipTouchHandler.updateMovementBounds(); 370 // The policy is to keep PiP snap fraction invariant. 371 mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); 372 mPipBoundsState.setBounds(toBounds); 373 } 374 if (mPipTransitionState.getPipTaskToken() == null) { 375 Log.wtf(TAG, "PipController.onDisplayChange no PiP task token" 376 + " state=" + mPipTransitionState.getState() 377 + " callers=\n" + Debug.getCallers(4, " ")); 378 } else { 379 t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds()); 380 } 381 // Update the size spec in PipBoundsState afterwards. 382 mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio()); 383 } 384 setDisplayLayout(DisplayLayout layout)385 private void setDisplayLayout(DisplayLayout layout) { 386 mPipDisplayLayoutState.setDisplayLayout(layout); 387 } 388 389 // 390 // IPip Binder stub helpers 391 // 392 getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo, int displayId, PictureInPictureParams pictureInPictureParams, int launcherRotation, Rect hotseatKeepClearArea)393 private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo, 394 int displayId, PictureInPictureParams pictureInPictureParams, 395 int launcherRotation, Rect hotseatKeepClearArea) { 396 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 397 "getSwipePipToHomeBounds: %s", componentName); 398 399 // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct 400 // display info that PiP is entering in. 401 if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) { 402 final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); 403 if (displayLayout != null) { 404 mPipDisplayLayoutState.setDisplayId(displayId); 405 mPipDisplayLayoutState.setDisplayLayout(displayLayout); 406 } 407 } 408 409 // Preemptively add the keep clear area for Hotseat, so that it is taken into account 410 // when calculating the entry destination bounds of PiP window. 411 mPipBoundsState.setNamedUnrestrictedKeepClearArea( 412 PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea); 413 414 // Set the display layout rotation early to calculate final orientation bounds that 415 // the animator expects, this will also be used to detect the fixed rotation when 416 // Shell resolves the type of the animation we are undergoing. 417 mPipDisplayLayoutState.rotateTo(launcherRotation); 418 419 mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams, 420 mPipBoundsAlgorithm); 421 422 // Update the size spec in case aspect ratio is invariant, but display has changed 423 // since the last PiP session, or this is the first PiP session altogether. 424 mPipBoundsState.updateMinMaxSize(mPipBoundsState.getAspectRatio()); 425 return mPipBoundsAlgorithm.getEntryDestinationBounds(); 426 } 427 onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName, Rect destinationBounds, SurfaceControl overlay, Rect appBounds, Rect sourceRectHint)428 private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName, 429 Rect destinationBounds, SurfaceControl overlay, Rect appBounds, 430 Rect sourceRectHint) { 431 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 432 "onSwipePipToHomeAnimationStart: %s", componentName); 433 Bundle extra = new Bundle(); 434 extra.putParcelable(SWIPE_TO_PIP_OVERLAY, overlay); 435 extra.putParcelable(SWIPE_TO_PIP_APP_BOUNDS, appBounds); 436 mPipTransitionState.setState(PipTransitionState.SWIPING_TO_PIP, extra); 437 if (overlay != null) { 438 // Shell transitions might use a root animation leash, which will be removed when 439 // the Recents transition is finished. Launcher attaches the overlay leash to this 440 // animation target leash; thus, we need to reparent it to the actual Task surface now. 441 // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP 442 // transition. 443 SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 444 mShellTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, tx); 445 tx.setLayer(overlay, Integer.MAX_VALUE); 446 tx.apply(); 447 } 448 if (mPipRecentsAnimationListener != null) { 449 mPipRecentsAnimationListener.onPipAnimationStarted(); 450 } 451 } 452 setLauncherKeepClearAreaHeight(boolean visible, int height)453 private void setLauncherKeepClearAreaHeight(boolean visible, int height) { 454 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 455 "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); 456 mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { 457 if (visible) { 458 Rect rect = new Rect( 459 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, 460 mPipDisplayLayoutState.getDisplayBounds().right, 461 mPipDisplayLayoutState.getDisplayBounds().bottom); 462 mPipBoundsState.setNamedUnrestrictedKeepClearArea( 463 PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); 464 } else { 465 mPipBoundsState.setNamedUnrestrictedKeepClearArea( 466 PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); 467 } 468 mPipTouchHandler.onShelfVisibilityChanged(visible, height); 469 }); 470 } 471 472 @Override onPipTransitionStateChanged(@ipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra)473 public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, 474 @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { 475 switch (newState) { 476 case PipTransitionState.SWIPING_TO_PIP: 477 Preconditions.checkState(extra != null, 478 "No extra bundle for " + mPipTransitionState); 479 480 SurfaceControl overlay = extra.getParcelable( 481 SWIPE_TO_PIP_OVERLAY, SurfaceControl.class); 482 Rect appBounds = extra.getParcelable( 483 SWIPE_TO_PIP_APP_BOUNDS, Rect.class); 484 485 Preconditions.checkState(appBounds != null, 486 "App bounds can't be null for " + mPipTransitionState); 487 mPipTransitionState.setSwipePipToHomeState(overlay, appBounds); 488 break; 489 case PipTransitionState.ENTERED_PIP: 490 final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo(); 491 if (taskInfo != null && taskInfo.topActivity != null) { 492 mPipAppOpsListener.onActivityPinned(taskInfo.topActivity.getPackageName()); 493 mPipUiEventLogger.setTaskInfo(taskInfo); 494 } 495 if (mPipTransitionState.isInSwipePipToHomeTransition()) { 496 mPipUiEventLogger.log( 497 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER); 498 mPipTransitionState.resetSwipePipToHomeState(); 499 } else { 500 mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER); 501 } 502 for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { 503 listener.accept(true /* inPip */); 504 } 505 break; 506 case PipTransitionState.EXITED_PIP: 507 mPipAppOpsListener.onActivityUnpinned(); 508 mPipUiEventLogger.setTaskInfo(null); 509 for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { 510 listener.accept(false /* inPip */); 511 } 512 break; 513 } 514 } 515 516 // 517 // IPipAnimationListener Binder proxy helpers 518 // 519 setPipRecentsAnimationListener(PipAnimationListener pipAnimationListener)520 private void setPipRecentsAnimationListener(PipAnimationListener pipAnimationListener) { 521 mPipRecentsAnimationListener = pipAnimationListener; 522 onPipResourceDimensionsChanged(); 523 } 524 onPipResourceDimensionsChanged()525 private void onPipResourceDimensionsChanged() { 526 if (mPipRecentsAnimationListener != null) { 527 mPipRecentsAnimationListener.onPipResourceDimensionsChanged( 528 mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius), 529 mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius)); 530 } 531 } 532 dump(PrintWriter pw, String prefix)533 private void dump(PrintWriter pw, String prefix) { 534 final String innerPrefix = " "; 535 pw.println(TAG); 536 mPipBoundsAlgorithm.dump(pw, innerPrefix); 537 mPipBoundsState.dump(pw, innerPrefix); 538 mPipDisplayLayoutState.dump(pw, innerPrefix); 539 mPipTransitionState.dump(pw, innerPrefix); 540 } 541 addOnIsInPipStateChangedListener(@onNull Consumer<Boolean> callback)542 private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { 543 if (callback != null) { 544 mOnIsInPipStateChangedListeners.add(callback); 545 callback.accept(mPipTransitionState.isInPip()); 546 } 547 } 548 removeOnIsInPipStateChangedListener(@onNull Consumer<Boolean> callback)549 private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { 550 if (callback != null) { 551 mOnIsInPipStateChangedListeners.remove(callback); 552 } 553 } 554 setLauncherAppIconSize(int iconSizePx)555 private void setLauncherAppIconSize(int iconSizePx) { 556 mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); 557 } 558 559 /** 560 * The interface for calls from outside the Shell, within the host process. 561 */ 562 public class PipImpl implements Pip { 563 @Override expandPip()564 public void expandPip() {} 565 566 @Override onSystemUiStateChanged(boolean isSysUiStateValid, long flag)567 public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {} 568 569 @Override addOnIsInPipStateChangedListener(@onNull Consumer<Boolean> callback)570 public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { 571 mMainExecutor.execute(() -> { 572 PipController.this.addOnIsInPipStateChangedListener(callback); 573 }); 574 } 575 576 @Override removeOnIsInPipStateChangedListener(@onNull Consumer<Boolean> callback)577 public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) { 578 mMainExecutor.execute(() -> { 579 PipController.this.removeOnIsInPipStateChangedListener(callback); 580 }); 581 } 582 583 @Override addPipExclusionBoundsChangeListener(Consumer<Rect> listener)584 public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) { 585 mMainExecutor.execute(() -> { 586 mPipBoundsState.addPipExclusionBoundsChangeCallback(listener); 587 }); 588 } 589 590 @Override removePipExclusionBoundsChangeListener(Consumer<Rect> listener)591 public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { 592 mMainExecutor.execute(() -> { 593 mPipBoundsState.removePipExclusionBoundsChangeCallback(listener); 594 }); 595 } 596 597 @Override showPictureInPictureMenu()598 public void showPictureInPictureMenu() {} 599 } 600 601 /** 602 * The interface for calls from outside the host process. 603 */ 604 @BinderThread 605 private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder { 606 private PipController mController; 607 private final SingleInstanceRemoteListener<PipController, IPipAnimationListener> mListener; 608 private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() { 609 @Override 610 public void onPipAnimationStarted() { 611 mListener.call(l -> l.onPipAnimationStarted()); 612 } 613 614 @Override 615 public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) { 616 mListener.call(l -> l.onPipResourceDimensionsChanged(cornerRadius, shadowRadius)); 617 } 618 619 @Override 620 public void onExpandPip() { 621 mListener.call(l -> l.onExpandPip()); 622 } 623 }; 624 IPipImpl(PipController controller)625 IPipImpl(PipController controller) { 626 mController = controller; 627 mListener = new SingleInstanceRemoteListener<>(mController, 628 (cntrl) -> cntrl.setPipRecentsAnimationListener(mPipAnimationListener), 629 (cntrl) -> cntrl.setPipRecentsAnimationListener(null)); 630 } 631 632 /** 633 * Invalidates this instance, preventing future calls from updating the controller. 634 */ 635 @Override invalidate()636 public void invalidate() { 637 mController = null; 638 // Unregister the listener to ensure any registered binder death recipients are unlinked 639 mListener.unregister(); 640 } 641 642 @Override startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo, int launcherRotation, Rect keepClearArea)643 public Rect startSwipePipToHome(ActivityManager.RunningTaskInfo taskInfo, 644 int launcherRotation, Rect keepClearArea) { 645 Rect[] result = new Rect[1]; 646 executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome", 647 (controller) -> { 648 result[0] = controller.getSwipePipToHomeBounds(taskInfo.topActivity, 649 taskInfo.topActivityInfo, taskInfo.displayId, 650 taskInfo.pictureInPictureParams, launcherRotation, keepClearArea); 651 }, true /* blocking */); 652 return result[0]; 653 } 654 655 @Override stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds, SurfaceControl overlay, Rect appBounds, Rect sourceRectHint)656 public void stopSwipePipToHome(int taskId, ComponentName componentName, 657 Rect destinationBounds, SurfaceControl overlay, Rect appBounds, 658 Rect sourceRectHint) { 659 if (overlay != null) { 660 overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome"); 661 } 662 executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", 663 (controller) -> controller.onSwipePipToHomeAnimationStart( 664 taskId, componentName, destinationBounds, overlay, appBounds, 665 sourceRectHint)); 666 } 667 668 @Override abortSwipePipToHome(int taskId, ComponentName componentName)669 public void abortSwipePipToHome(int taskId, ComponentName componentName) {} 670 671 @Override setShelfHeight(boolean visible, int height)672 public void setShelfHeight(boolean visible, int height) {} 673 674 @Override setLauncherKeepClearAreaHeight(boolean visible, int height)675 public void setLauncherKeepClearAreaHeight(boolean visible, int height) { 676 executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight", 677 (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height)); 678 } 679 680 @Override setLauncherAppIconSize(int iconSizePx)681 public void setLauncherAppIconSize(int iconSizePx) { 682 executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize", 683 (controller) -> controller.setLauncherAppIconSize(iconSizePx)); 684 } 685 686 @Override setPipAnimationListener(IPipAnimationListener listener)687 public void setPipAnimationListener(IPipAnimationListener listener) { 688 executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener", 689 (controller) -> { 690 if (listener != null) { 691 mListener.register(listener); 692 } else { 693 mListener.unregister(); 694 } 695 }); 696 } 697 698 @Override setPipAnimationTypeToAlpha()699 public void setPipAnimationTypeToAlpha() {} 700 } 701 } 702