DisplayContent.java revision b15758ab7a6481717d0d29612e870d7241061c31
1/* 2 * Copyright (C) 2012 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.DOCKED_STACK_ID; 20import static android.app.ActivityManager.StackId.HOME_STACK_ID; 21import static android.app.ActivityManager.StackId.PINNED_STACK_ID; 22import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; 23import static com.android.server.wm.WindowManagerService.TAG; 24import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; 25 26import android.app.ActivityManager.StackId; 27import android.graphics.Rect; 28import android.graphics.Region; 29import android.util.DisplayMetrics; 30import android.util.Slog; 31import android.view.Display; 32import android.view.DisplayInfo; 33import android.view.Surface; 34 35import java.io.PrintWriter; 36import java.util.ArrayList; 37 38class DisplayContentList extends ArrayList<DisplayContent> { 39} 40 41/** 42 * Utility class for keeping track of the WindowStates and other pertinent contents of a 43 * particular Display. 44 * 45 * IMPORTANT: No method from this class should ever be used without holding 46 * WindowManagerService.mWindowMap. 47 */ 48class DisplayContent { 49 50 /** Unique identifier of this stack. */ 51 private final int mDisplayId; 52 53 /** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element 54 * from mDisplayWindows; */ 55 private final WindowList mWindows = new WindowList(); 56 57 int mInitialDisplayWidth = 0; 58 int mInitialDisplayHeight = 0; 59 int mInitialDisplayDensity = 0; 60 int mBaseDisplayWidth = 0; 61 int mBaseDisplayHeight = 0; 62 int mBaseDisplayDensity = 0; 63 boolean mDisplayScalingDisabled; 64 private final DisplayInfo mDisplayInfo = new DisplayInfo(); 65 private final Display mDisplay; 66 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 67 68 Rect mBaseDisplayRect = new Rect(); 69 Rect mContentRect = new Rect(); 70 71 // Accessed directly by all users. 72 boolean layoutNeeded; 73 int pendingLayoutChanges; 74 final boolean isDefaultDisplay; 75 76 /** Window tokens that are in the process of exiting, but still on screen for animations. */ 77 final ArrayList<WindowToken> mExitingTokens = new ArrayList<>(); 78 79 /** Array containing all TaskStacks on this display. Array 80 * is stored in display order with the current bottom stack at 0. */ 81 private final ArrayList<TaskStack> mStacks = new ArrayList<>(); 82 83 /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack 84 * (except a future lockscreen TaskStack) moves to the top. */ 85 private TaskStack mHomeStack = null; 86 87 /** Detect user tapping outside of current focused task bounds .*/ 88 TaskTapPointerEventListener mTapDetector; 89 90 /** Detect user tapping outside of current focused stack bounds .*/ 91 Region mTouchExcludeRegion = new Region(); 92 93 /** Detect user tapping in a non-resizeable task in docked or fullscreen stack .*/ 94 Region mNonResizeableRegion = new Region(); 95 96 /** Save allocating when calculating rects */ 97 private Rect mTmpRect = new Rect(); 98 private Rect mTmpRect2 = new Rect(); 99 100 /** For gathering Task objects in order. */ 101 final ArrayList<Task> mTmpTaskHistory = new ArrayList<Task>(); 102 103 final WindowManagerService mService; 104 105 /** Remove this display when animation on it has completed. */ 106 boolean mDeferredRemoval; 107 108 final DockedStackDividerController mDividerControllerLocked; 109 110 final DimLayerController mDimLayerController; 111 112 /** 113 * @param display May not be null. 114 * @param service You know. 115 */ 116 DisplayContent(Display display, WindowManagerService service) { 117 mDisplay = display; 118 mDisplayId = display.getDisplayId(); 119 display.getDisplayInfo(mDisplayInfo); 120 display.getMetrics(mDisplayMetrics); 121 isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; 122 mService = service; 123 initializeDisplayBaseInfo(); 124 mDividerControllerLocked = new DockedStackDividerController(service.mContext, this); 125 mDimLayerController = new DimLayerController(this); 126 } 127 128 int getDisplayId() { 129 return mDisplayId; 130 } 131 132 WindowList getWindowList() { 133 return mWindows; 134 } 135 136 Display getDisplay() { 137 return mDisplay; 138 } 139 140 DisplayInfo getDisplayInfo() { 141 return mDisplayInfo; 142 } 143 144 DisplayMetrics getDisplayMetrics() { 145 return mDisplayMetrics; 146 } 147 148 DockedStackDividerController getDockedDividerController() { 149 return mDividerControllerLocked; 150 } 151 152 /** 153 * Returns true if the specified UID has access to this display. 154 */ 155 public boolean hasAccess(int uid) { 156 return mDisplay.hasAccess(uid); 157 } 158 159 public boolean isPrivate() { 160 return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0; 161 } 162 163 ArrayList<TaskStack> getStacks() { 164 return mStacks; 165 } 166 167 /** 168 * Retrieve the tasks on this display in stack order from the bottommost TaskStack up. 169 * @return All the Tasks, in order, on this display. 170 */ 171 ArrayList<Task> getTasks() { 172 mTmpTaskHistory.clear(); 173 final int numStacks = mStacks.size(); 174 for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { 175 mTmpTaskHistory.addAll(mStacks.get(stackNdx).getTasks()); 176 } 177 return mTmpTaskHistory; 178 } 179 180 TaskStack getHomeStack() { 181 if (mHomeStack == null && mDisplayId == Display.DEFAULT_DISPLAY) { 182 Slog.e(TAG, "getHomeStack: Returning null from this=" + this); 183 } 184 return mHomeStack; 185 } 186 187 void updateDisplayInfo() { 188 mDisplay.getDisplayInfo(mDisplayInfo); 189 mDisplay.getMetrics(mDisplayMetrics); 190 for (int i = mStacks.size() - 1; i >= 0; --i) { 191 mStacks.get(i).updateDisplayInfo(null); 192 } 193 } 194 195 void initializeDisplayBaseInfo() { 196 // Bootstrap the default logical display from the display manager. 197 final DisplayInfo newDisplayInfo = 198 mService.mDisplayManagerInternal.getDisplayInfo(mDisplayId); 199 if (newDisplayInfo != null) { 200 mDisplayInfo.copyFrom(newDisplayInfo); 201 } 202 mBaseDisplayWidth = mInitialDisplayWidth = mDisplayInfo.logicalWidth; 203 mBaseDisplayHeight = mInitialDisplayHeight = mDisplayInfo.logicalHeight; 204 mBaseDisplayDensity = mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; 205 mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight); 206 } 207 208 void getLogicalDisplayRect(Rect out) { 209 // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. 210 final int orientation = mDisplayInfo.rotation; 211 boolean rotated = (orientation == Surface.ROTATION_90 212 || orientation == Surface.ROTATION_270); 213 final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; 214 final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; 215 int width = mDisplayInfo.logicalWidth; 216 int left = (physWidth - width) / 2; 217 int height = mDisplayInfo.logicalHeight; 218 int top = (physHeight - height) / 2; 219 out.set(left, top, left + width, top + height); 220 } 221 222 /** Refer to {@link WindowManagerService#attachStack(int, int, boolean)} */ 223 void attachStack(TaskStack stack, boolean onTop) { 224 if (stack.mStackId == HOME_STACK_ID) { 225 if (mHomeStack != null) { 226 throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first."); 227 } 228 mHomeStack = stack; 229 } 230 if (onTop) { 231 mStacks.add(stack); 232 } else { 233 mStacks.add(0, stack); 234 } 235 layoutNeeded = true; 236 } 237 238 void moveStack(TaskStack stack, boolean toTop) { 239 if (stack.mStackId == PINNED_STACK_ID && !toTop) { 240 // Pinned stack is always-on-top silly... 241 Slog.w(TAG, "Ignoring move of always-on-top stack=" + stack + " to bottom"); 242 return; 243 } 244 245 if (!mStacks.remove(stack)) { 246 Slog.wtf(TAG, "moving stack that was not added: " + stack, new Throwable()); 247 } 248 249 int addIndex = toTop ? mStacks.size() : 0; 250 251 if (toTop 252 && mService.isStackVisibleLocked(PINNED_STACK_ID) 253 && stack.mStackId != PINNED_STACK_ID) { 254 // The pinned stack is always the top most stack (always-on-top) when it is visible. 255 // So, stack is moved just below the pinned stack. 256 addIndex--; 257 TaskStack topStack = mStacks.get(addIndex); 258 if (topStack.mStackId != PINNED_STACK_ID) { 259 throw new IllegalStateException("Pinned stack isn't top stack??? " + mStacks); 260 } 261 } 262 mStacks.add(addIndex, stack); 263 } 264 265 void detachStack(TaskStack stack) { 266 mDimLayerController.removeDimLayerUser(stack); 267 mStacks.remove(stack); 268 } 269 270 /** 271 * Propagate the new bounds to all child stacks. 272 * @param contentRect The bounds to apply at the top level. 273 */ 274 void resize(Rect contentRect) { 275 mContentRect.set(contentRect); 276 } 277 278 int taskIdFromPoint(int x, int y) { 279 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 280 TaskStack stack = mStacks.get(stackNdx); 281 stack.getBounds(mTmpRect); 282 if (!mTmpRect.contains(x, y)) { 283 continue; 284 } 285 final ArrayList<Task> tasks = stack.getTasks(); 286 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 287 final Task task = tasks.get(taskNdx); 288 final WindowState win = task.getTopVisibleAppMainWindow(); 289 if (win == null) { 290 continue; 291 } 292 // We need to use the task's dim bounds (which is derived from the visible 293 // bounds of its apps windows) for any touch-related tests. Can't use 294 // the task's original bounds because it might be adjusted to fit the 295 // content frame. For example, the presence of the IME adjusting the 296 // windows frames when the app window is the IME target. 297 task.getDimBounds(mTmpRect); 298 if (mTmpRect.contains(x, y)) { 299 return task.mTaskId; 300 } 301 } 302 } 303 return -1; 304 } 305 306 /** 307 * Find the task whose outside touch area (for resizing) (x, y) falls within. 308 * Returns null if the touch doesn't fall into a resizing area. 309 */ 310 Task findTaskForControlPoint(int x, int y) { 311 final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); 312 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 313 TaskStack stack = mStacks.get(stackNdx); 314 if (!StackId.isTaskResizeAllowed(stack.mStackId)) { 315 break; 316 } 317 final ArrayList<Task> tasks = stack.getTasks(); 318 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 319 final Task task = tasks.get(taskNdx); 320 if (task.isFullscreen()) { 321 return null; 322 } 323 324 // We need to use the task's dim bounds (which is derived from the visible 325 // bounds of its apps windows) for any touch-related tests. Can't use 326 // the task's original bounds because it might be adjusted to fit the 327 // content frame. One example is when the task is put to top-left quadrant, 328 // the actual visible area would not start at (0,0) after it's adjusted 329 // for the status bar. 330 task.getDimBounds(mTmpRect); 331 mTmpRect.inset(-delta, -delta); 332 if (mTmpRect.contains(x, y)) { 333 mTmpRect.inset(delta, delta); 334 if (!mTmpRect.contains(x, y)) { 335 return task; 336 } 337 // User touched inside the task. No need to look further, 338 // focus transfer will be handled in ACTION_UP. 339 return null; 340 } 341 } 342 } 343 return null; 344 } 345 346 void setTouchExcludeRegion(Task focusedTask) { 347 mTouchExcludeRegion.set(mBaseDisplayRect); 348 final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); 349 boolean addBackFocusedTask = false; 350 mNonResizeableRegion.setEmpty(); 351 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 352 TaskStack stack = mStacks.get(stackNdx); 353 final ArrayList<Task> tasks = stack.getTasks(); 354 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 355 final Task task = tasks.get(taskNdx); 356 final WindowState win = task.getTopVisibleAppMainWindow(); 357 if (win == null) { 358 continue; 359 } 360 361 /** 362 * Exclusion region is the region that TapDetector doesn't care about. 363 * Here we want to remove all non-focused tasks from the exclusion region. 364 * We also remove the outside touch area for resizing for all freeform 365 * tasks (including the focused). 366 * 367 * (For freeform focused task, the below logic will first remove the enlarged 368 * area, then add back the inner area.) 369 */ 370 final boolean isFreeformed = task.inFreeformWorkspace(); 371 if (task != focusedTask || isFreeformed) { 372 task.getDimBounds(mTmpRect); 373 if (isFreeformed) { 374 // If we're removing a freeform, focused app from the exclusion region, 375 // we need to add back its touchable frame later. Remember the touchable 376 // frame now. 377 if (task == focusedTask) { 378 addBackFocusedTask = true; 379 mTmpRect2.set(mTmpRect); 380 } 381 // If the task is freeformed, enlarge the area to account for outside 382 // touch area for resize. 383 mTmpRect.inset(-delta, -delta); 384 // Intersect with display content rect. If we have system decor (status bar/ 385 // navigation bar), we want to exclude that from the tap detection. 386 // Otherwise, if the app is partially placed under some system button (eg. 387 // Recents, Home), pressing that button would cause a full series of 388 // unwanted transfer focus/resume/pause, before we could go home. 389 mTmpRect.intersect(mContentRect); 390 } 391 mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); 392 } 393 if (task.isDockedInEffect() && !task.isResizeable()) { 394 stack.getBounds(mTmpRect); 395 mNonResizeableRegion.op(mTmpRect, Region.Op.UNION); 396 break; 397 } 398 } 399 } 400 // If we removed the focused task above, add it back and only leave its 401 // outside touch area in the exclusion. TapDectector is not interested in 402 // any touch inside the focused task itself. 403 if (addBackFocusedTask) { 404 mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION); 405 } 406 if (mTapDetector != null) { 407 mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion, mNonResizeableRegion); 408 } 409 } 410 411 void switchUserStacks() { 412 final WindowList windows = getWindowList(); 413 for (int i = 0; i < windows.size(); i++) { 414 final WindowState win = windows.get(i); 415 if (win.isHiddenFromUserLocked()) { 416 if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing, hiding " + win 417 + ", attrs=" + win.mAttrs.type + ", belonging to " + win.mOwnerUid); 418 win.hideLw(false); 419 } 420 } 421 422 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 423 mStacks.get(stackNdx).switchUser(); 424 } 425 } 426 427 void resetAnimationBackgroundAnimator() { 428 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 429 mStacks.get(stackNdx).resetAnimationBackgroundAnimator(); 430 } 431 } 432 433 boolean animateDimLayers() { 434 return mDimLayerController.animateDimLayers(); 435 } 436 437 void resetDimming() { 438 mDimLayerController.resetDimming(); 439 } 440 441 boolean isDimming() { 442 return mDimLayerController.isDimming(); 443 } 444 445 void stopDimmingIfNeeded() { 446 mDimLayerController.stopDimmingIfNeeded(); 447 } 448 449 void close() { 450 mDimLayerController.close(); 451 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 452 mStacks.get(stackNdx).close(); 453 } 454 } 455 456 boolean isAnimating() { 457 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 458 final TaskStack stack = mStacks.get(stackNdx); 459 if (stack.isAnimating()) { 460 return true; 461 } 462 } 463 return false; 464 } 465 466 void checkForDeferredActions() { 467 boolean animating = false; 468 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 469 final TaskStack stack = mStacks.get(stackNdx); 470 if (stack.isAnimating()) { 471 animating = true; 472 } else { 473 if (stack.mDeferDetach) { 474 mService.detachStackLocked(this, stack); 475 } 476 final ArrayList<Task> tasks = stack.getTasks(); 477 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 478 final Task task = tasks.get(taskNdx); 479 AppTokenList tokens = task.mAppTokens; 480 for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { 481 AppWindowToken wtoken = tokens.get(tokenNdx); 482 if (wtoken.mIsExiting) { 483 wtoken.removeAppFromTaskLocked(); 484 } 485 } 486 } 487 } 488 } 489 if (!animating && mDeferredRemoval) { 490 mService.onDisplayRemoved(mDisplayId); 491 } 492 } 493 494 void rotateBounds(int oldRotation, int newRotation, Rect bounds) { 495 final int rotationDelta = DisplayContent.deltaRotation(oldRotation, newRotation); 496 getLogicalDisplayRect(mTmpRect); 497 switch (rotationDelta) { 498 case Surface.ROTATION_0: 499 mTmpRect2.set(bounds); 500 break; 501 case Surface.ROTATION_90: 502 mTmpRect2.top = mTmpRect.bottom - bounds.right; 503 mTmpRect2.left = bounds.top; 504 mTmpRect2.right = mTmpRect2.left + bounds.height(); 505 mTmpRect2.bottom = mTmpRect2.top + bounds.width(); 506 break; 507 case Surface.ROTATION_180: 508 mTmpRect2.top = mTmpRect.bottom - bounds.bottom; 509 mTmpRect2.left = mTmpRect.right - bounds.right; 510 mTmpRect2.right = mTmpRect2.left + bounds.width(); 511 mTmpRect2.bottom = mTmpRect2.top + bounds.height(); 512 break; 513 case Surface.ROTATION_270: 514 mTmpRect2.top = bounds.left; 515 mTmpRect2.left = mTmpRect.right - bounds.bottom; 516 mTmpRect2.right = mTmpRect2.left + bounds.height(); 517 mTmpRect2.bottom = mTmpRect2.top + bounds.width(); 518 break; 519 } 520 bounds.set(mTmpRect2); 521 } 522 523 static int deltaRotation(int oldRotation, int newRotation) { 524 int delta = newRotation - oldRotation; 525 if (delta < 0) delta += 4; 526 return delta; 527 } 528 529 public void dump(String prefix, PrintWriter pw) { 530 pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); 531 final String subPrefix = " " + prefix; 532 pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x"); 533 pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity); 534 pw.print("dpi"); 535 if (mInitialDisplayWidth != mBaseDisplayWidth 536 || mInitialDisplayHeight != mBaseDisplayHeight 537 || mInitialDisplayDensity != mBaseDisplayDensity) { 538 pw.print(" base="); 539 pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); 540 pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi"); 541 } 542 if (mDisplayScalingDisabled) { 543 pw.println(" noscale"); 544 } 545 pw.print(" cur="); 546 pw.print(mDisplayInfo.logicalWidth); 547 pw.print("x"); pw.print(mDisplayInfo.logicalHeight); 548 pw.print(" app="); 549 pw.print(mDisplayInfo.appWidth); 550 pw.print("x"); pw.print(mDisplayInfo.appHeight); 551 pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth); 552 pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight); 553 pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth); 554 pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); 555 pw.print(subPrefix); pw.print("deferred="); pw.print(mDeferredRemoval); 556 pw.print(" layoutNeeded="); pw.println(layoutNeeded); 557 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 558 final TaskStack stack = mStacks.get(stackNdx); 559 pw.print(prefix); pw.print("mStacks[" + stackNdx + "]"); pw.println(stack.mStackId); 560 stack.dump(prefix + " ", pw); 561 } 562 pw.println(); 563 pw.println(" Application tokens in top down Z order:"); 564 int ndx = 0; 565 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { 566 final TaskStack stack = mStacks.get(stackNdx); 567 pw.print(" mStackId="); pw.println(stack.mStackId); 568 ArrayList<Task> tasks = stack.getTasks(); 569 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 570 final Task task = tasks.get(taskNdx); 571 pw.print(" mTaskId="); pw.println(task.mTaskId); 572 AppTokenList tokens = task.mAppTokens; 573 for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx, ++ndx) { 574 final AppWindowToken wtoken = tokens.get(tokenNdx); 575 pw.print(" Activity #"); pw.print(tokenNdx); 576 pw.print(' '); pw.print(wtoken); pw.println(":"); 577 wtoken.dump(pw, " "); 578 } 579 } 580 } 581 if (ndx == 0) { 582 pw.println(" None"); 583 } 584 pw.println(); 585 if (!mExitingTokens.isEmpty()) { 586 pw.println(); 587 pw.println(" Exiting tokens:"); 588 for (int i=mExitingTokens.size()-1; i>=0; i--) { 589 WindowToken token = mExitingTokens.get(i); 590 pw.print(" Exiting #"); pw.print(i); 591 pw.print(' '); pw.print(token); 592 pw.println(':'); 593 token.dump(pw, " "); 594 } 595 } 596 pw.println(); 597 mDimLayerController.dump(prefix + " ", pw); 598 } 599 600 @Override 601 public String toString() { 602 return "Display " + mDisplayId + " info=" + mDisplayInfo + " stacks=" + mStacks; 603 } 604 605 TaskStack getDockedStackLocked() { 606 final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID); 607 return (stack != null && stack.isVisibleLocked()) ? stack : null; 608 } 609} 610