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 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 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 83 public void removeContainer() { 84 synchronized (mWindowMap) { 85 if (mContainer != null) { 86 mContainer.removeIfPossible(); 87 super.removeContainer(); 88 } 89 } 90 } 91 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 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 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 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 */ 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 195 public void onPipAnimationEndResize() { 196 synchronized (mService.mWindowMap) { 197 mContainer.onPipAnimationEndResize(); 198 } 199 } 200 201 /** 202 * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean) 203 */ 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 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 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 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 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 */ 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 */ 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 */ 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 376 void requestResize(Rect bounds) { 377 mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget(); 378 } 379 380 @Override 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 391 H(WeakReference<StackWindowController> controller, Looper looper) { 392 super(looper); 393 mController = controller; 394 } 395 396 @Override 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