1 /*
2  * Copyright (C) 2017 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.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
22 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
23 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
24 import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
25 
26 import android.app.RemoteAction;
27 import android.graphics.Rect;
28 
29 import java.util.List;
30 
31 /**
32  * Controller for the pinned stack container. See {@link StackWindowController}.
33  */
34 public class PinnedStackWindowController extends StackWindowController {
35 
36     private Rect mTmpFromBounds = new Rect();
37     private Rect mTmpToBounds = new Rect();
38 
PinnedStackWindowController(int stackId, PinnedStackWindowListener listener, int displayId, boolean onTop, Rect outBounds, WindowManagerService service)39     public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
40             int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
41         super(stackId, listener, displayId, onTop, outBounds, service);
42     }
43 
44     /**
45      * @return the {@param currentStackBounds} transformed to the give {@param aspectRatio}.  If
46      *         {@param currentStackBounds} is null, then the {@param aspectRatio} is applied to the
47      *         default bounds.
48      */
getPictureInPictureBounds(float aspectRatio, Rect stackBounds)49     public Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
50         synchronized (mWindowMap) {
51             if (!mService.mSupportsPictureInPicture || mContainer == null) {
52                 return null;
53             }
54 
55             final DisplayContent displayContent = mContainer.getDisplayContent();
56             if (displayContent == null) {
57                 return null;
58             }
59 
60             final PinnedStackController pinnedStackController =
61                     displayContent.getPinnedStackController();
62             if (stackBounds == null) {
63                 // Calculate the aspect ratio bounds from the default bounds
64                 stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
65             }
66 
67             if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
68                 return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
69                         true /* useCurrentMinEdgeSize */);
70             } else {
71                 return stackBounds;
72             }
73         }
74     }
75 
76     /**
77      * Animates the pinned stack.
78      */
animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration, boolean fromFullscreen)79     public void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
80             int animationDuration, boolean fromFullscreen) {
81         synchronized (mWindowMap) {
82             if (mContainer == null) {
83                 throw new IllegalArgumentException("Pinned stack container not found :(");
84             }
85 
86             // Get the from-bounds
87             final Rect fromBounds = new Rect();
88             mContainer.getBounds(fromBounds);
89 
90             // Get non-null fullscreen to-bounds for animating if the bounds are null
91             @SchedulePipModeChangedState int schedulePipModeChangedState =
92                 NO_PIP_MODE_CHANGED_CALLBACKS;
93             final boolean toFullscreen = toBounds == null;
94             if (toFullscreen) {
95                 if (fromFullscreen) {
96                     throw new IllegalArgumentException("Should not defer scheduling PiP mode"
97                             + " change on animation to fullscreen.");
98                 }
99                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
100 
101                 mService.getStackBounds(
102                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
103                 if (!mTmpToBounds.isEmpty()) {
104                     // If there is a fullscreen bounds, use that
105                     toBounds = new Rect(mTmpToBounds);
106                 } else {
107                     // Otherwise, use the display bounds
108                     toBounds = new Rect();
109                     mContainer.getDisplayContent().getBounds(toBounds);
110                 }
111             } else if (fromFullscreen) {
112                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
113             }
114 
115             mContainer.setAnimationFinalBounds(sourceHintBounds, toBounds, toFullscreen);
116 
117             final Rect finalToBounds = toBounds;
118             final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
119                 schedulePipModeChangedState;
120             mService.mBoundsAnimationController.getHandler().post(() -> {
121                 if (mContainer == null) {
122                     return;
123                 }
124                 mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
125                         finalToBounds, animationDuration, finalSchedulePipModeChangedState,
126                         fromFullscreen, toFullscreen);
127             });
128         }
129     }
130 
131     /**
132      * Sets the current picture-in-picture aspect ratio.
133      */
setPictureInPictureAspectRatio(float aspectRatio)134     public void setPictureInPictureAspectRatio(float aspectRatio) {
135         synchronized (mWindowMap) {
136             if (!mService.mSupportsPictureInPicture || mContainer == null) {
137                 return;
138             }
139 
140             final PinnedStackController pinnedStackController =
141                     mContainer.getDisplayContent().getPinnedStackController();
142 
143             if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
144                 mContainer.getAnimationOrCurrentBounds(mTmpFromBounds);
145                 mTmpToBounds.set(mTmpFromBounds);
146                 getPictureInPictureBounds(aspectRatio, mTmpToBounds);
147                 if (!mTmpToBounds.equals(mTmpFromBounds)) {
148                     animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
149                             -1 /* duration */, false /* fromFullscreen */);
150                 }
151                 pinnedStackController.setAspectRatio(
152                         pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
153                                 ? aspectRatio : -1f);
154             }
155         }
156     }
157 
158     /**
159      * Sets the current picture-in-picture actions.
160      */
setPictureInPictureActions(List<RemoteAction> actions)161     public void setPictureInPictureActions(List<RemoteAction> actions) {
162         synchronized (mWindowMap) {
163             if (!mService.mSupportsPictureInPicture || mContainer == null) {
164                 return;
165             }
166 
167             mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
168         }
169     }
170 
171     /**
172      * @return whether the multi-window mode change should be deferred as a part of a transition
173      * from fullscreen to non-fullscreen bounds.
174      */
deferScheduleMultiWindowModeChanged()175     public boolean deferScheduleMultiWindowModeChanged() {
176         synchronized (mWindowMap) {
177             return mContainer.deferScheduleMultiWindowModeChanged();
178         }
179     }
180 
181     /**
182      * @return whether the bounds are currently animating to fullscreen.
183      */
isAnimatingBoundsToFullscreen()184     public boolean isAnimatingBoundsToFullscreen() {
185         synchronized (mWindowMap) {
186             return mContainer.isAnimatingBoundsToFullscreen();
187         }
188     }
189 
190     /**
191      * @return whether the stack can be resized from the bounds animation.
192      */
pinnedStackResizeDisallowed()193     public boolean pinnedStackResizeDisallowed() {
194         synchronized (mWindowMap) {
195             return mContainer.pinnedStackResizeDisallowed();
196         }
197     }
198 
199     /**
200      * The following calls are made from WM to AM.
201      */
202 
203     /** Calls directly into activity manager so window manager lock shouldn't held. */
updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds, boolean forceUpdate)204     public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
205             boolean forceUpdate) {
206         if (mListener != null) {
207             PinnedStackWindowListener listener = (PinnedStackWindowListener) mListener;
208             listener.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds,
209                     forceUpdate);
210         }
211     }
212 }
213