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 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 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 89 public void removeContainer() { 90 synchronized (mWindowMap) { 91 if (mContainer != null) { 92 mContainer.removeIfPossible(); 93 super.removeContainer(); 94 } 95 } 96 } 97 98 public boolean isVisible() { 99 synchronized (mWindowMap) { 100 return mContainer != null && mContainer.isVisible(); 101 } 102 } 103 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 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 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 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 */ 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 */ 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 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 235 private void getRawBounds(Rect outBounds) { 236 if (mContainer.getRawFullscreen()) { 237 outBounds.setEmpty(); 238 } else { 239 mContainer.getRawBounds(outBounds); 240 } 241 } 242 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 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 */ 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 */ 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 */ 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 375 void requestResize(Rect bounds) { 376 mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget(); 377 } 378 379 @Override 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 390 H(WeakReference<StackWindowController> controller, Looper looper) { 391 super(looper); 392 mController = controller; 393 } 394 395 @Override 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