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 static android.app.ActivityManager.StackId.DOCKED_STACK_ID; 20 import static android.app.ActivityManager.StackId.PINNED_STACK_ID; 21 22 import android.app.ActivityManager.StackId; 23 import android.app.RemoteAction; 24 import android.content.res.Configuration; 25 import android.graphics.Rect; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.Message; 29 import android.util.Slog; 30 import android.util.SparseArray; 31 import android.view.DisplayInfo; 32 33 import com.android.server.UiThread; 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.lang.ref.WeakReference; 37 import java.util.List; 38 39 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; 40 import static com.android.server.wm.WindowContainer.POSITION_TOP; 41 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; 42 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 43 44 /** 45 * Controller for the stack container. This is created by activity manager to link activity stacks 46 * to the stack container they use in window manager. 47 * 48 * Test class: {@link StackWindowControllerTests} 49 */ 50 public class StackWindowController 51 extends WindowContainerController<TaskStack, StackWindowListener> { 52 53 final int mStackId; 54 55 private final H mHandler; 56 57 // Temp bounds only used in adjustConfigurationForBounds() 58 private final Rect mTmpRect = new Rect(); 59 private final Rect mTmpStableInsets = new Rect(); 60 private final Rect mTmpNonDecorInsets = new Rect(); 61 private final Rect mTmpDisplayBounds = new Rect(); 62 StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds)63 public StackWindowController(int stackId, StackWindowListener listener, 64 int displayId, boolean onTop, Rect outBounds) { 65 this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance()); 66 } 67 68 @VisibleForTesting StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds, WindowManagerService service)69 public StackWindowController(int stackId, StackWindowListener listener, 70 int displayId, boolean onTop, Rect outBounds, WindowManagerService service) { 71 super(listener, service); 72 mStackId = stackId; 73 mHandler = new H(new WeakReference<>(this), service.mH.getLooper()); 74 75 synchronized (mWindowMap) { 76 final DisplayContent dc = mRoot.getDisplayContent(displayId); 77 if (dc == null) { 78 throw new IllegalArgumentException("Trying to add stackId=" + stackId 79 + " to unknown displayId=" + displayId); 80 } 81 82 final TaskStack stack = dc.addStackToDisplay(stackId, onTop); 83 stack.setController(this); 84 getRawBounds(outBounds); 85 } 86 } 87 88 @Override removeContainer()89 public void removeContainer() { 90 synchronized (mWindowMap) { 91 if (mContainer != null) { 92 mContainer.removeIfPossible(); 93 super.removeContainer(); 94 } 95 } 96 } 97 isVisible()98 public boolean isVisible() { 99 synchronized (mWindowMap) { 100 return mContainer != null && mContainer.isVisible(); 101 } 102 } 103 reparent(int displayId, Rect outStackBounds, boolean onTop)104 public void reparent(int displayId, Rect outStackBounds, boolean onTop) { 105 synchronized (mWindowMap) { 106 if (mContainer == null) { 107 throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId 108 + " to displayId=" + displayId); 109 } 110 111 final DisplayContent targetDc = mRoot.getDisplayContent(displayId); 112 if (targetDc == null) { 113 throw new IllegalArgumentException("Trying to move stackId=" + mStackId 114 + " to unknown displayId=" + displayId); 115 } 116 117 targetDc.moveStackToDisplay(mContainer, onTop); 118 getRawBounds(outStackBounds); 119 } 120 } 121 positionChildAt(TaskWindowContainerController child, int position, Rect bounds, Configuration overrideConfig)122 public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds, 123 Configuration overrideConfig) { 124 synchronized (mWindowMap) { 125 if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child 126 + " at " + position); 127 if (child.mContainer == null) { 128 if (DEBUG_STACK) Slog.i(TAG_WM, 129 "positionChildAt: could not find task=" + this); 130 return; 131 } 132 if (mContainer == null) { 133 if (DEBUG_STACK) Slog.i(TAG_WM, 134 "positionChildAt: could not find stack for task=" + mContainer); 135 return; 136 } 137 child.mContainer.positionAt(position, bounds, overrideConfig); 138 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 139 } 140 } 141 positionChildAtTop(TaskWindowContainerController child, boolean includingParents)142 public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) { 143 if (child == null) { 144 // TODO: Fix the call-points that cause this to happen. 145 return; 146 } 147 148 synchronized(mWindowMap) { 149 final Task childTask = child.mContainer; 150 if (childTask == null) { 151 Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found"); 152 return; 153 } 154 mContainer.positionChildAt(POSITION_TOP, childTask, includingParents); 155 156 if (mService.mAppTransition.isTransitionSet()) { 157 childTask.setSendingToBottom(false); 158 } 159 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 160 } 161 } 162 positionChildAtBottom(TaskWindowContainerController child)163 public void positionChildAtBottom(TaskWindowContainerController child) { 164 if (child == null) { 165 // TODO: Fix the call-points that cause this to happen. 166 return; 167 } 168 169 synchronized(mWindowMap) { 170 final Task childTask = child.mContainer; 171 if (childTask == null) { 172 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found"); 173 return; 174 } 175 mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */); 176 177 if (mService.mAppTransition.isTransitionSet()) { 178 childTask.setSendingToBottom(true); 179 } 180 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded(); 181 } 182 } 183 184 /** 185 * Re-sizes a stack and its containing tasks. 186 * 187 * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen. 188 * @param configs Configurations for tasks in the resized stack, keyed by task id. 189 * @param taskBounds Bounds for tasks in the resized stack, keyed by task id. 190 * @return True if the stack is now fullscreen. 191 */ resize(Rect bounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds)192 public boolean resize(Rect bounds, SparseArray<Configuration> configs, 193 SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) { 194 synchronized (mWindowMap) { 195 if (mContainer == null) { 196 throw new IllegalArgumentException("resizeStack: stack " + this + " not found."); 197 } 198 // We might trigger a configuration change. Save the current task bounds for freezing. 199 mContainer.prepareFreezingTaskBounds(); 200 if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds) 201 && mContainer.isVisible()) { 202 mContainer.getDisplayContent().setLayoutNeeded(); 203 mService.mWindowPlacerLocked.performSurfacePlacement(); 204 } 205 return mContainer.getRawFullscreen(); 206 } 207 } 208 209 /** 210 * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean) 211 */ getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds, boolean ignoreVisibility)212 public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, 213 Rect outTempTaskBounds, boolean ignoreVisibility) { 214 synchronized (mWindowMap) { 215 if (mContainer != null) { 216 mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds, 217 outTempTaskBounds, ignoreVisibility); 218 return; 219 } 220 outStackBounds.setEmpty(); 221 outTempTaskBounds.setEmpty(); 222 } 223 } 224 prepareFreezingTaskBounds()225 public void prepareFreezingTaskBounds() { 226 synchronized (mWindowMap) { 227 if (mContainer == null) { 228 throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this 229 + " not found."); 230 } 231 mContainer.prepareFreezingTaskBounds(); 232 } 233 } 234 getRawBounds(Rect outBounds)235 private void getRawBounds(Rect outBounds) { 236 if (mContainer.getRawFullscreen()) { 237 outBounds.setEmpty(); 238 } else { 239 mContainer.getRawBounds(outBounds); 240 } 241 } 242 getBounds(Rect outBounds)243 public void getBounds(Rect outBounds) { 244 synchronized (mWindowMap) { 245 if (mContainer != null) { 246 mContainer.getBounds(outBounds); 247 return; 248 } 249 outBounds.setEmpty(); 250 } 251 } 252 getBoundsForNewConfiguration(Rect outBounds)253 public void getBoundsForNewConfiguration(Rect outBounds) { 254 synchronized(mWindowMap) { 255 mContainer.getBoundsForNewConfiguration(outBounds); 256 } 257 } 258 259 /** 260 * Adjusts the screen size in dp's for the {@param config} for the given params. 261 */ adjustConfigurationForBounds(Rect bounds, Rect insetBounds, Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, boolean overrideHeight, float density, Configuration config, Configuration parentConfig)262 public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds, 263 Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, 264 boolean overrideHeight, float density, Configuration config, 265 Configuration parentConfig) { 266 synchronized (mWindowMap) { 267 final TaskStack stack = mContainer; 268 final DisplayContent displayContent = stack.getDisplayContent(); 269 final DisplayInfo di = displayContent.getDisplayInfo(); 270 271 // Get the insets and display bounds 272 mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, 273 mTmpStableInsets); 274 mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight, 275 mTmpNonDecorInsets); 276 mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight); 277 278 int width; 279 int height; 280 281 final Rect parentAppBounds = parentConfig.appBounds; 282 283 config.setAppBounds(!bounds.isEmpty() ? bounds : null); 284 boolean intersectParentBounds = false; 285 286 if (StackId.tasksAreFloating(mStackId)) { 287 // Floating tasks should not be resized to the screen's bounds. 288 289 if (mStackId == PINNED_STACK_ID && 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.appBounds.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.appBounds != null) { 323 config.appBounds.intersect(parentAppBounds); 324 } 325 326 config.screenWidthDp = width; 327 config.screenHeightDp = height; 328 config.smallestScreenWidthDp = getSmallestWidthForTaskBounds( 329 insetBounds != null ? insetBounds : bounds, density); 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 {@param bounds}. 352 * 353 * @return the smallest width to be used in the Configuration, in dips 354 */ getSmallestWidthForTaskBounds(Rect bounds, float density)355 private int getSmallestWidthForTaskBounds(Rect bounds, float density) { 356 final DisplayContent displayContent = mContainer.getDisplayContent(); 357 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 358 359 if (bounds == null || (bounds.width() == displayInfo.logicalWidth && 360 bounds.height() == displayInfo.logicalHeight)) { 361 // If the bounds are fullscreen, return the value of the fullscreen configuration 362 return displayContent.getConfiguration().smallestScreenWidthDp; 363 } else if (StackId.tasksAreFloating(mStackId)) { 364 // For floating tasks, calculate the smallest width from the bounds of the task 365 return (int) (Math.min(bounds.width(), bounds.height()) / density); 366 } else { 367 // Iterating across all screen orientations, and return the minimum of the task 368 // width taking into account that the bounds might change because the snap algorithm 369 // snaps to a different value 370 return displayContent.getDockedDividerController() 371 .getSmallestWidthDpForBounds(bounds); 372 } 373 } 374 requestResize(Rect bounds)375 void requestResize(Rect bounds) { 376 mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget(); 377 } 378 379 @Override toString()380 public String toString() { 381 return "{StackWindowController stackId=" + mStackId + "}"; 382 } 383 384 private static final class H extends Handler { 385 386 static final int REQUEST_RESIZE = 0; 387 388 private final WeakReference<StackWindowController> mController; 389 H(WeakReference<StackWindowController> controller, Looper looper)390 H(WeakReference<StackWindowController> controller, Looper looper) { 391 super(looper); 392 mController = controller; 393 } 394 395 @Override handleMessage(Message msg)396 public void handleMessage(Message msg) { 397 final StackWindowController controller = mController.get(); 398 final StackWindowListener listener = (controller != null) 399 ? controller.mListener : null; 400 if (listener == null) { 401 return; 402 } 403 switch (msg.what) { 404 case REQUEST_RESIZE: 405 listener.requestResize((Rect) msg.obj); 406 break; 407 } 408 } 409 } 410 } 411