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