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