• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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