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 
21 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
22 
23 import android.app.PictureInPictureParams;
24 import android.app.TaskInfo;
25 import android.content.ComponentName;
26 import android.content.pm.ActivityInfo;
27 import android.graphics.Rect;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.view.SurfaceControl;
31 import android.window.WindowContainerTransaction;
32 
33 import com.android.wm.shell.ShellTaskOrganizer;
34 import com.android.wm.shell.transition.Transitions;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * Responsible supplying PiP Transitions.
41  */
42 public abstract class PipTransitionController implements Transitions.TransitionHandler {
43 
44     protected final PipAnimationController mPipAnimationController;
45     protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
46     protected final PipBoundsState mPipBoundsState;
47     protected final ShellTaskOrganizer mShellTaskOrganizer;
48     protected final PipMenuController mPipMenuController;
49     protected final Transitions mTransitions;
50     private final Handler mMainHandler;
51     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
52     protected PipTaskOrganizer mPipOrganizer;
53 
54     protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
55             new PipAnimationController.PipAnimationCallback() {
56                 @Override
57                 public void onPipAnimationStart(TaskInfo taskInfo,
58                         PipAnimationController.PipTransitionAnimator animator) {
59                     final int direction = animator.getTransitionDirection();
60                     sendOnPipTransitionStarted(direction);
61                 }
62 
63                 @Override
64                 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
65                         PipAnimationController.PipTransitionAnimator animator) {
66                     final int direction = animator.getTransitionDirection();
67                     mPipBoundsState.setBounds(animator.getDestinationBounds());
68                     if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
69                         return;
70                     }
71                     onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
72                     sendOnPipTransitionFinished(direction);
73                 }
74 
75                 @Override
76                 public void onPipAnimationCancel(TaskInfo taskInfo,
77                         PipAnimationController.PipTransitionAnimator animator) {
78                     sendOnPipTransitionCancelled(animator.getTransitionDirection());
79                 }
80             };
81 
82     /**
83      * Called when transition is about to finish. This is usually for performing tasks such as
84      * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
85      */
onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, SurfaceControl.Transaction tx)86     public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
87             @PipAnimationController.TransitionDirection int direction,
88             SurfaceControl.Transaction tx) {
89     }
90 
91     /**
92      * Called to inform the transition that the animation should start with the assumption that
93      * PiP is not animating from its original bounds, but rather a continuation of another
94      * animation. For example, gesture navigation would first fade out the PiP activity, and the
95      * transition should be responsible to animate in (such as fade in) the PiP.
96      */
setIsFullAnimation(boolean isFullAnimation)97     public void setIsFullAnimation(boolean isFullAnimation) {
98     }
99 
100     /**
101      * Called when the Shell wants to starts a transition/animation.
102      */
startTransition(Rect destinationBounds, WindowContainerTransaction out)103     public void startTransition(Rect destinationBounds, WindowContainerTransaction out) {
104         // Default implementation does nothing.
105     }
106 
107     /**
108      * Called when the transition animation can't continue (eg. task is removed during
109      * animation)
110      */
forceFinishTransition()111     public void forceFinishTransition() {
112     }
113 
PipTransitionController(PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, @android.annotation.NonNull ShellTaskOrganizer shellTaskOrganizer)114     public PipTransitionController(PipBoundsState pipBoundsState,
115             PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
116             PipAnimationController pipAnimationController, Transitions transitions,
117             @android.annotation.NonNull ShellTaskOrganizer shellTaskOrganizer) {
118         mPipBoundsState = pipBoundsState;
119         mPipMenuController = pipMenuController;
120         mShellTaskOrganizer = shellTaskOrganizer;
121         mPipBoundsAlgorithm = pipBoundsAlgorithm;
122         mPipAnimationController = pipAnimationController;
123         mTransitions = transitions;
124         mMainHandler = new Handler(Looper.getMainLooper());
125         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
126             transitions.addHandler(this);
127         }
128     }
129 
setPipOrganizer(PipTaskOrganizer pto)130     void setPipOrganizer(PipTaskOrganizer pto) {
131         mPipOrganizer = pto;
132     }
133 
134     /**
135      * Registers {@link PipTransitionCallback} to receive transition callbacks.
136      */
registerPipTransitionCallback(PipTransitionCallback callback)137     public void registerPipTransitionCallback(PipTransitionCallback callback) {
138         mPipTransitionCallbacks.add(callback);
139     }
140 
sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)141     protected void sendOnPipTransitionStarted(
142             @PipAnimationController.TransitionDirection int direction) {
143         final Rect pipBounds = mPipBoundsState.getBounds();
144         for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
145             final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
146             callback.onPipTransitionStarted(direction, pipBounds);
147         }
148     }
149 
sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)150     protected void sendOnPipTransitionFinished(
151             @PipAnimationController.TransitionDirection int direction) {
152         for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
153             final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
154             callback.onPipTransitionFinished(direction);
155         }
156     }
157 
sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)158     protected void sendOnPipTransitionCancelled(
159             @PipAnimationController.TransitionDirection int direction) {
160         for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
161             final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
162             callback.onPipTransitionCanceled(direction);
163         }
164     }
165 
166     /**
167      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
168      * and can be overridden to restore to an alternate windowing mode.
169      */
getOutPipWindowingMode()170     public int getOutPipWindowingMode() {
171         // By default, simply reset the windowing mode to undefined.
172         return WINDOWING_MODE_UNDEFINED;
173     }
174 
setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)175     protected void setBoundsStateForEntry(ComponentName componentName,
176             PictureInPictureParams params,
177             ActivityInfo activityInfo) {
178         mPipBoundsState.setBoundsStateForEntry(componentName,
179                 mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
180                 mPipBoundsAlgorithm.getMinimalSize(activityInfo));
181     }
182 
183     /**
184      * Callback interface for PiP transitions (both from and to PiP mode)
185      */
186     public interface PipTransitionCallback {
187         /**
188          * Callback when the pip transition is started.
189          */
onPipTransitionStarted(int direction, Rect pipBounds)190         void onPipTransitionStarted(int direction, Rect pipBounds);
191 
192         /**
193          * Callback when the pip transition is finished.
194          */
onPipTransitionFinished(int direction)195         void onPipTransitionFinished(int direction);
196 
197         /**
198          * Callback when the pip transition is cancelled.
199          */
onPipTransitionCanceled(int direction)200         void onPipTransitionCanceled(int direction);
201     }
202 }
203