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