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