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