1 /* 2 * Copyright (C) 2016 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 android.app.WindowConfiguration; 20 import android.content.res.Configuration; 21 import android.graphics.Rect; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.util.Slog; 26 import android.util.SparseArray; 27 import android.view.DisplayCutout; 28 import android.view.DisplayInfo; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.lang.ref.WeakReference; 33 34 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; 35 import static com.android.server.wm.WindowContainer.POSITION_TOP; 36 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; 37 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 38 39 /** 40 * Controller for the stack container. This is created by activity manager to link activity stacks 41 * to the stack container they use in window manager. 42 * 43 * Test class: {@link StackWindowControllerTests} 44 */ 45 public class StackWindowController 46 extends WindowContainerController<TaskStack, StackWindowListener> { 47 48 private final int mStackId; 49 50 private final H mHandler; 51 52 // Temp bounds only used in adjustConfigurationForBounds() 53 private final Rect mTmpRect = new Rect(); 54 private final Rect mTmpStableInsets = new Rect(); 55 private final Rect mTmpNonDecorInsets = new Rect(); 56 private final Rect mTmpDisplayBounds = new Rect(); 57 StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds)58 public StackWindowController(int stackId, StackWindowListener listener, int displayId, 59 boolean onTop, Rect outBounds) { 60 this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance()); 61 } 62 63 @VisibleForTesting StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds, WindowManagerService service)64 public StackWindowController(int stackId, StackWindowListener listener, 65 int displayId, boolean onTop, Rect outBounds, WindowManagerService service) { 66 super(listener, service); 67 mStackId = stackId; 68 mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); 69 70 synchronized (mWindowMap) { 71 final DisplayContent dc = mRoot.getDisplayContent(displayId); 72 if (dc == null) { 73 throw new IllegalArgumentException("Trying to add stackId=" + stackId 74 + " to unknown displayId=" + displayId); 75 } 76 77 dc.createStack(stackId, onTop, this); 78 getRawBounds(outBounds); 79 } 80 } 81 82 @Override removeContainer()83 public void removeContainer() { 84 synchronized (mWindowMap) { 85 if (mContainer != null) { 86 mContainer.removeIfPossible(); 87 super.removeContainer(); 88 } 89 } 90 } 91 reparent(int displayId, Rect outStackBounds, boolean onTop)92 public void reparent(int displayId, Rect outStackBounds, boolean onTop) { 93 synchronized (mWindowMap) { 94 if (mContainer == null) { 95 throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId 96 + " to displayId=" + displayId); 97 } 98 99 final DisplayContent targetDc = mRoot.getDisplayContent(displayId); 100 if (targetDc == null) { 101 throw new IllegalArgumentException("Trying to move stackId=" + mStackId 102 + " to unknown displayId=" + displayId); 103 } 104 105 targetDc.moveStackToDisplay(mContainer, onTop); 106 getRawBounds(outStackBounds); 107 } 108 } 109 positionChildAt(TaskWindowContainerController child, int position)110 public void positionChildAt(TaskWindowContainerController child, int position) { 111 synchronized (mWindowMap) { 112 if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child 113 + " at " + position); 114 if (child.mContainer == null) { 115 if (DEBUG_STACK) Slog.i(TAG_WM, 116 "positionChildAt: could not find task=" + this); 117 return; 118 } 119 if (mContainer == null) { 120 if (DEBUG_STACK) Slog.i(TAG_WM, 121 "positionChildAt: could not find stack for task=" + mContainer); 122 return; 123 } 124 child.mContainer.positionAt(position); 125 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 126 } 127 } 128 positionChildAtTop(TaskWindowContainerController child, boolean includingParents)129 public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { 130 if (child == null) { 131 // TODO: Fix the call-points that cause this to happen. 132 return; 133 } 134 135 synchronized(mWindowMap) { 136 final Task childTask = child.mContainer; 137 if (childTask == null) { 138 Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); 139 return; 140 } 141 mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); 142 143 if (mService.mAppTransition.isTransitionSet()) { 144 childTask.setSendingToBottom(false); 145 } 146 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 147 } 148 } 149 positionChildAtBottom(TaskWindowContainerController child, boolean includingParents)150 public void positionChildAtBottom(TaskWindowContainerController child, 151 boolean includingParents) { 152 if (child == null) { 153 // TODO: Fix the call-points that cause this to happen. 154 return; 155 } 156 157 synchronized(mWindowMap) { 158 final Task childTask = child.mContainer; 159 if (childTask == null) { 160 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); 161 return; 162 } 163 mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents); 164 165 if (mService.mAppTransition.isTransitionSet()) { 166 childTask.setSendingToBottom(true); 167 } 168 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 169 } 170 } 171 172 /** 173 * Re-sizes a stack and its containing tasks. 174 * 175 * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen. 176 * @param taskBounds Bounds for tasks in the resized stack, keyed by task id. 177 * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id. 178 */ resize(Rect bounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds)179 public void resize(Rect bounds, SparseArray<Rect> taskBounds, 180 SparseArray<Rect> taskTempInsetBounds) { 181 synchronized (mWindowMap) { 182 if (mContainer == null) { 183 throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); 184 } 185 // We might trigger a configuration change. Save the current task bounds for freezing. 186 mContainer.prepareFreezingTaskBounds(); 187 if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds) 188 && mContainer.isVisible()) { 189 mContainer.getDisplayContent().setLayoutNeeded(); 190 mService.mWindowPlacerLocked.performSurfacePlacement(); 191 } 192 } 193 } 194 onPipAnimationEndResize()195 public void onPipAnimationEndResize() { 196 synchronized (mService.mWindowMap) { 197 mContainer.onPipAnimationEndResize(); 198 } 199 } 200 201 /** 202 * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean) 203 */ getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds, boolean ignoreVisibility)204 public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, 205 Rect outTempTaskBounds, boolean ignoreVisibility) { 206 synchronized (mWindowMap) { 207 if (mContainer != null) { 208 mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds, 209 outTempTaskBounds, ignoreVisibility); 210 return; 211 } 212 outStackBounds.setEmpty(); 213 outTempTaskBounds.setEmpty(); 214 } 215 } 216 prepareFreezingTaskBounds()217 public void prepareFreezingTaskBounds() { 218 synchronized (mWindowMap) { 219 if (mContainer == null) { 220 throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this 221 + " not found."); 222 } 223 mContainer.prepareFreezingTaskBounds(); 224 } 225 } 226 getRawBounds(Rect outBounds)227 public void getRawBounds(Rect outBounds) { 228 synchronized (mWindowMap) { 229 if (mContainer.matchParentBounds()) { 230 outBounds.setEmpty(); 231 } else { 232 mContainer.getRawBounds(outBounds); 233 } 234 } 235 } 236 getBounds(Rect outBounds)237 public void getBounds(Rect outBounds) { 238 synchronized (mWindowMap) { 239 if (mContainer != null) { 240 mContainer.getBounds(outBounds); 241 return; 242 } 243 outBounds.setEmpty(); 244 } 245 } 246 getBoundsForNewConfiguration(Rect outBounds)247 public void getBoundsForNewConfiguration(Rect outBounds) { 248 synchronized(mWindowMap) { 249 mContainer.getBoundsForNewConfiguration(outBounds); 250 } 251 } 252 253 /** 254 * Adjusts the screen size in dp's for the {@param config} for the given params. The provided 255 * params represent the desired state of a configuration change. Since this utility is used 256 * before mContainer has been updated, any relevant properties (like {@param windowingMode}) 257 * need to be passed in. 258 */ adjustConfigurationForBounds(Rect bounds, Rect insetBounds, Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, boolean overrideHeight, float density, Configuration config, Configuration parentConfig, int windowingMode)259 public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds, 260 Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, 261 boolean overrideHeight, float density, Configuration config, 262 Configuration parentConfig, int windowingMode) { 263 synchronized (mWindowMap) { 264 final TaskStack stack = mContainer; 265 final DisplayContent displayContent = stack.getDisplayContent(); 266 final DisplayInfo di = displayContent.getDisplayInfo(); 267 final DisplayCutout displayCutout = di.displayCutout; 268 269 // Get the insets and display bounds 270 mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, 271 displayCutout, mTmpStableInsets); 272 mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, 273 displayCutout, mTmpNonDecorInsets); 274 mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight); 275 276 int width; 277 int height; 278 279 final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); 280 281 config.windowConfiguration.setBounds(bounds); 282 config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null); 283 boolean intersectParentBounds = false; 284 285 if (WindowConfiguration.isFloating(windowingMode)) { 286 // Floating tasks should not be resized to the screen's bounds. 287 288 if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED 289 && bounds.width() == mTmpDisplayBounds.width() 290 && bounds.height() == mTmpDisplayBounds.height()) { 291 // If the bounds we are animating is the same as the fullscreen stack 292 // dimensions, then apply the same inset calculations that we normally do for 293 // the fullscreen stack, without intersecting it with the display bounds 294 stableBounds.inset(mTmpStableInsets); 295 nonDecorBounds.inset(mTmpNonDecorInsets); 296 // Move app bounds to zero to apply intersection with parent correctly. They are 297 // used only for evaluating width and height, so it's OK to move them around. 298 config.windowConfiguration.getAppBounds().offsetTo(0, 0); 299 intersectParentBounds = true; 300 } 301 width = (int) (stableBounds.width() / density); 302 height = (int) (stableBounds.height() / density); 303 } else { 304 // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen 305 // area, i.e. the screen area without the system bars. 306 // Additionally task dimensions should not be bigger than its parents dimensions. 307 // The non decor inset are areas that could never be removed in Honeycomb. See 308 // {@link WindowManagerPolicy#getNonDecorInsetsLw}. 309 intersectDisplayBoundsExcludeInsets(nonDecorBounds, 310 insetBounds != null ? insetBounds : bounds, mTmpNonDecorInsets, 311 mTmpDisplayBounds, overrideWidth, overrideHeight); 312 intersectDisplayBoundsExcludeInsets(stableBounds, 313 insetBounds != null ? insetBounds : bounds, mTmpStableInsets, 314 mTmpDisplayBounds, overrideWidth, overrideHeight); 315 width = Math.min((int) (stableBounds.width() / density), 316 parentConfig.screenWidthDp); 317 height = Math.min((int) (stableBounds.height() / density), 318 parentConfig.screenHeightDp); 319 intersectParentBounds = true; 320 } 321 322 if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) { 323 config.windowConfiguration.getAppBounds().intersect(parentAppBounds); 324 } 325 326 config.screenWidthDp = width; 327 config.screenHeightDp = height; 328 config.smallestScreenWidthDp = getSmallestWidthForTaskBounds( 329 insetBounds != null ? insetBounds : bounds, density, windowingMode); 330 } 331 } 332 333 /** 334 * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable 335 * inset areas. 336 * 337 * @param inOutBounds The inOutBounds to subtract the stable inset areas from. 338 */ intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds, Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight)339 private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds, 340 Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) { 341 mTmpRect.set(inInsetBounds); 342 mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect); 343 int leftInset = mTmpRect.left - inInsetBounds.left; 344 int topInset = mTmpRect.top - inInsetBounds.top; 345 int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right; 346 int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom; 347 inOutBounds.inset(leftInset, topInset, rightInset, bottomInset); 348 } 349 350 /** 351 * Calculates the smallest width for a task given the target {@param bounds} and 352 * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date. 353 * 354 * @return the smallest width to be used in the Configuration, in dips 355 */ getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode)356 private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) { 357 final DisplayContent displayContent = mContainer.getDisplayContent(); 358 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 359 360 if (bounds == null || (bounds.width() == displayInfo.logicalWidth && 361 bounds.height() == displayInfo.logicalHeight)) { 362 // If the bounds are fullscreen, return the value of the fullscreen configuration 363 return displayContent.getConfiguration().smallestScreenWidthDp; 364 } else if (WindowConfiguration.isFloating(windowingMode)) { 365 // For floating tasks, calculate the smallest width from the bounds of the task 366 return (int) (Math.min(bounds.width(), bounds.height()) / density); 367 } else { 368 // Iterating across all screen orientations, and return the minimum of the task 369 // width taking into account that the bounds might change because the snap algorithm 370 // snaps to a different value 371 return displayContent.getDockedDividerController() 372 .getSmallestWidthDpForBounds(bounds); 373 } 374 } 375 requestResize(Rect bounds)376 void requestResize(Rect bounds) { 377 mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget(); 378 } 379 380 @Override toString()381 public String toString() { 382 return "{StackWindowController stackId=" + mStackId + "}"; 383 } 384 385 private static final class H extends Handler { 386 387 static final int REQUEST_RESIZE = 0; 388 389 private final WeakReference<StackWindowController> mController; 390 H(WeakReference<StackWindowController> controller, Looper looper)391 H(WeakReference<StackWindowController> controller, Looper looper) { 392 super(looper); 393 mController = controller; 394 } 395 396 @Override handleMessage(Message msg)397 public void handleMessage(Message msg) { 398 final StackWindowController controller = mController.get(); 399 final StackWindowListener listener = (controller != null) 400 ? controller.mListener : null; 401 if (listener == null) { 402 return; 403 } 404 switch (msg.what) { 405 case REQUEST_RESIZE: 406 listener.requestResize((Rect) msg.obj); 407 break; 408 } 409 } 410 } 411 } 412