TaskStack.java revision 8e89b31a62fb9ec5ad33908c5e8e9c7ab2fd949f
1/* 2 * Copyright (C) 2013 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.*; 20import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT; 21import static com.android.server.wm.WindowManagerService.TAG; 22 23import android.content.res.Configuration; 24import android.graphics.Rect; 25import android.os.Debug; 26import android.os.RemoteException; 27import android.util.EventLog; 28import android.util.Slog; 29import android.util.SparseArray; 30import android.view.DisplayInfo; 31 32import com.android.server.EventLogTags; 33 34import java.io.PrintWriter; 35import java.util.ArrayList; 36 37public class TaskStack implements DimLayer.DimLayerUser { 38 39 // If the stack should be resized to fullscreen. 40 private static final boolean FULLSCREEN = true; 41 42 /** Unique identifier */ 43 final int mStackId; 44 45 /** The service */ 46 private final WindowManagerService mService; 47 48 /** The display this stack sits under. */ 49 private DisplayContent mDisplayContent; 50 51 /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match 52 * mTaskHistory in the ActivityStack with the same mStackId */ 53 private final ArrayList<Task> mTasks = new ArrayList<>(); 54 55 /** For comparison with DisplayContent bounds. */ 56 private Rect mTmpRect = new Rect(); 57 58 /** Content limits relative to the DisplayContent this sits in. */ 59 private Rect mBounds = new Rect(); 60 61 /** Whether mBounds is fullscreen */ 62 private boolean mFullscreen = true; 63 64 /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */ 65 DimLayer mAnimationBackgroundSurface; 66 67 /** The particular window with an Animation with non-zero background color. */ 68 WindowStateAnimator mAnimationBackgroundAnimator; 69 70 /** Application tokens that are exiting, but still on screen for animations. */ 71 final AppTokenList mExitingAppTokens = new AppTokenList(); 72 73 /** Detach this stack from its display when animation completes. */ 74 boolean mDeferDetach; 75 76 TaskStack(WindowManagerService service, int stackId) { 77 mService = service; 78 mStackId = stackId; 79 EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId); 80 } 81 82 DisplayContent getDisplayContent() { 83 return mDisplayContent; 84 } 85 86 ArrayList<Task> getTasks() { 87 return mTasks; 88 } 89 90 void resizeWindows() { 91 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 92 mTasks.get(taskNdx).resizeWindows(); 93 } 94 } 95 96 boolean allowTaskResize() { 97 return mStackId == FREEFORM_WORKSPACE_STACK_ID 98 || mStackId == DOCKED_STACK_ID; 99 } 100 101 /** 102 * Set the bounds of the stack and its containing tasks. 103 * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen. 104 * @param resizeTasks If true, the tasks within the stack will also be resized. 105 * @param configs Configuration for individual tasks, keyed by task id. 106 * @param taskBounds Bounds for individual tasks, keyed by task id. 107 * @return True if the stack bounds was changed. 108 * */ 109 boolean setBounds(Rect stackBounds, boolean resizeTasks, SparseArray<Configuration> configs, 110 SparseArray<Rect> taskBounds) { 111 if (!setBounds(stackBounds)) { 112 return false; 113 } 114 115 if (!resizeTasks) { 116 return true; 117 } 118 119 // Update bounds of containing tasks. 120 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 121 final Task task = mTasks.get(taskNdx); 122 Configuration config = configs.get(task.mTaskId); 123 if (config != null) { 124 Rect bounds = taskBounds.get(task.mTaskId); 125 if (bounds == null) { 126 bounds = stackBounds; 127 } 128 task.setBounds(bounds, config); 129 } else { 130 Slog.wtf(TAG, "No config for task: " + task + ", is there a mismatch with AM?"); 131 } 132 } 133 return true; 134 } 135 136 private boolean setBounds(Rect bounds) { 137 boolean oldFullscreen = mFullscreen; 138 if (mDisplayContent != null) { 139 mDisplayContent.getLogicalDisplayRect(mTmpRect); 140 if (bounds == null) { 141 bounds = mTmpRect; 142 mFullscreen = true; 143 } else { 144 // ensure bounds are entirely within the display rect 145 if (!bounds.intersect(mTmpRect)) { 146 // Can't set bounds outside the containing display.. Sorry! 147 return false; 148 } 149 mFullscreen = mTmpRect.equals(bounds); 150 } 151 } 152 153 if (bounds == null) { 154 // Can't set to fullscreen if we don't have a display to get bounds from... 155 return false; 156 } 157 if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) { 158 return false; 159 } 160 161 mAnimationBackgroundSurface.setBounds(bounds); 162 mBounds.set(bounds); 163 return true; 164 } 165 166 void getBounds(Rect out) { 167 out.set(mBounds); 168 } 169 170 void updateDisplayInfo(Rect bounds) { 171 if (mDisplayContent != null) { 172 if (bounds != null) { 173 setBounds(bounds); 174 } else { 175 setBounds(mFullscreen ? null : mBounds); 176 } 177 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 178 mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent); 179 } 180 } 181 } 182 183 boolean isAnimating() { 184 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 185 final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; 186 for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { 187 final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; 188 for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { 189 final WindowStateAnimator winAnimator = windows.get(winNdx).mWinAnimator; 190 if (winAnimator.isAnimating() || winAnimator.mWin.mExiting) { 191 return true; 192 } 193 } 194 } 195 } 196 return false; 197 } 198 199 void addTask(Task task, boolean toTop) { 200 addTask(task, toTop, task.showForAllUsers()); 201 } 202 203 /** 204 * Put a Task in this stack. Used for adding and moving. 205 * @param task The task to add. 206 * @param toTop Whether to add it to the top or bottom. 207 * @param showForAllUsers Whether to show the task regardless of the current user. 208 */ 209 void addTask(Task task, boolean toTop, boolean showForAllUsers) { 210 positionTask(task, toTop ? mTasks.size() : 0, showForAllUsers); 211 } 212 213 void positionTask(Task task, int position, boolean showForAllUsers) { 214 final boolean canShowTask = 215 showForAllUsers || mService.isCurrentProfileLocked(task.mUserId); 216 mTasks.remove(task); 217 int stackSize = mTasks.size(); 218 int minPosition = 0; 219 int maxPosition = stackSize; 220 221 if (canShowTask) { 222 minPosition = computeMinPosition(minPosition, stackSize); 223 } else { 224 maxPosition = computeMaxPosition(maxPosition); 225 } 226 // Reset position based on minimum/maximum possible positions. 227 position = Math.min(Math.max(position, minPosition), maxPosition); 228 229 if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, 230 "positionTask: task=" + task + " position=" + position); 231 mTasks.add(position, task); 232 233 task.mStack = this; 234 task.updateDisplayInfo(mDisplayContent); 235 boolean toTop = position == mTasks.size() - 1; 236 if (toTop) { 237 mDisplayContent.moveStack(this, true); 238 } 239 EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, position); 240 } 241 242 /** Calculate the minimum possible position for a task that can be shown to the user. 243 * The minimum position will be above all other tasks that can't be shown. 244 * @param minPosition The minimum position the caller is suggesting. 245 * We will start adjusting up from here. 246 * @param size The size of the current task list. 247 */ 248 private int computeMinPosition(int minPosition, int size) { 249 while (minPosition < size) { 250 final Task tmpTask = mTasks.get(minPosition); 251 final boolean canShowTmpTask = 252 tmpTask.showForAllUsers() 253 || mService.isCurrentProfileLocked(tmpTask.mUserId); 254 if (canShowTmpTask) { 255 break; 256 } 257 minPosition++; 258 } 259 return minPosition; 260 } 261 262 /** Calculate the maximum possible position for a task that can't be shown to the user. 263 * The maximum position will be below all other tasks that can be shown. 264 * @param maxPosition The maximum position the caller is suggesting. 265 * We will start adjusting down from here. 266 */ 267 private int computeMaxPosition(int maxPosition) { 268 while (maxPosition > 0) { 269 final Task tmpTask = mTasks.get(maxPosition - 1); 270 final boolean canShowTmpTask = 271 tmpTask.showForAllUsers() 272 || mService.isCurrentProfileLocked(tmpTask.mUserId); 273 if (!canShowTmpTask) { 274 break; 275 } 276 maxPosition--; 277 } 278 return maxPosition; 279 } 280 281 void moveTaskToTop(Task task) { 282 if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers=" 283 + Debug.getCallers(6)); 284 mTasks.remove(task); 285 addTask(task, true); 286 } 287 288 void moveTaskToBottom(Task task) { 289 if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task); 290 mTasks.remove(task); 291 addTask(task, false); 292 } 293 294 /** 295 * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the 296 * back. 297 * @param task The Task to delete. 298 */ 299 void removeTask(Task task) { 300 if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task); 301 mTasks.remove(task); 302 if (mDisplayContent != null) { 303 if (mTasks.isEmpty()) { 304 mDisplayContent.moveStack(this, false); 305 } 306 mDisplayContent.layoutNeeded = true; 307 } 308 for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) { 309 final AppWindowToken wtoken = mExitingAppTokens.get(appNdx); 310 if (wtoken.mTask == task) { 311 wtoken.mIsExiting = false; 312 mExitingAppTokens.remove(appNdx); 313 } 314 } 315 } 316 317 void attachDisplayContent(DisplayContent displayContent) { 318 if (mDisplayContent != null) { 319 throw new IllegalStateException("attachDisplayContent: Already attached"); 320 } 321 322 mDisplayContent = displayContent; 323 mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId()); 324 325 Rect bounds = null; 326 final boolean dockedStackExists = mService.mStackIdToStack.get(DOCKED_STACK_ID) != null; 327 if (mStackId == DOCKED_STACK_ID || (dockedStackExists 328 && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) { 329 // The existence of a docked stack affects the size of any static stack created since 330 // the docked stack occupies a dedicated region on screen. 331 bounds = new Rect(); 332 displayContent.getLogicalDisplayRect(mTmpRect); 333 getInitialDockedStackBounds(mTmpRect, bounds, mStackId); 334 } 335 336 updateDisplayInfo(bounds); 337 338 if (mStackId == DOCKED_STACK_ID) { 339 // Attaching a docked stack to the display affects the size of all other static 340 // stacks since the docked stack occupies a dedicated region on screen. 341 // Resize existing static stacks so they are pushed to the side of the docked stack. 342 resizeNonDockedStacks(!FULLSCREEN); 343 } 344 } 345 346 /** 347 * Outputs the initial bounds a stack should be given the presence of a docked stack on the 348 * display. 349 * @param displayRect The bounds of the display the docked stack is on. 350 * @param outBounds Output bounds that should be used for the stack. 351 * @param stackId Id of stack we are calculating the bounds for. 352 */ 353 private static void getInitialDockedStackBounds( 354 Rect displayRect, Rect outBounds, int stackId) { 355 // Docked stack start off occupying half the screen space. 356 // TODO(multi-window): Need to support the selecting which half of the screen the 357 // docked stack uses for snapping windows to the edge of the screen. 358 final boolean splitHorizontally = displayRect.width() > displayRect.height(); 359 outBounds.set(displayRect); 360 if (stackId == DOCKED_STACK_ID) { 361 if (splitHorizontally) { 362 outBounds.right = displayRect.centerX(); 363 } else { 364 outBounds.bottom = displayRect.centerY(); 365 } 366 } else { 367 if (splitHorizontally) { 368 outBounds.left = displayRect.centerX(); 369 } else { 370 outBounds.top = displayRect.centerY(); 371 } 372 } 373 } 374 375 /** Resizes all non-docked stacks in the system to either fullscreen or the appropriate size 376 * based on the presence of a docked stack. 377 * @param fullscreen If true the stacks will be resized to fullscreen, else they will be 378 * resized to the appropriate size based on the presence of a docked stack. 379 */ 380 private void resizeNonDockedStacks(boolean fullscreen) { 381 mDisplayContent.getLogicalDisplayRect(mTmpRect); 382 if (!fullscreen) { 383 getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID); 384 } 385 386 final int count = mService.mStackIdToStack.size(); 387 for (int i = 0; i < count; i++) { 388 final TaskStack otherStack = mService.mStackIdToStack.valueAt(i); 389 final int otherStackId = otherStack.mStackId; 390 if (otherStackId != DOCKED_STACK_ID 391 && otherStackId >= FIRST_STATIC_STACK_ID 392 && otherStackId <= LAST_STATIC_STACK_ID) { 393 try { 394 mService.mActivityManager.resizeStack(otherStackId, mTmpRect); 395 } catch (RemoteException e) { 396 // This will not happen since we are in the same process. 397 } 398 } 399 } 400 } 401 402 void detachDisplay() { 403 EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); 404 405 boolean doAnotherLayoutPass = false; 406 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 407 final AppTokenList appWindowTokens = mTasks.get(taskNdx).mAppTokens; 408 for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) { 409 final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows; 410 for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) { 411 // We are in the middle of changing the state of displays/stacks/tasks. We need 412 // to finish that, before we let layout interfere with it. 413 mService.removeWindowInnerLocked(appWindows.get(winNdx), 414 false /* performLayout */); 415 doAnotherLayoutPass = true; 416 } 417 } 418 } 419 if (doAnotherLayoutPass) { 420 mService.mWindowPlacerLocked.requestTraversal(); 421 } 422 423 if (mStackId == DOCKED_STACK_ID) { 424 // Docked stack was detached from the display, so we no longer need to restrict the 425 // region of the screen other static stacks occupy. Go ahead and make them fullscreen. 426 resizeNonDockedStacks(FULLSCREEN); 427 } 428 429 close(); 430 } 431 432 void resetAnimationBackgroundAnimator() { 433 mAnimationBackgroundAnimator = null; 434 mAnimationBackgroundSurface.hide(); 435 } 436 437 void setAnimationBackground(WindowStateAnimator winAnimator, int color) { 438 int animLayer = winAnimator.mAnimLayer; 439 if (mAnimationBackgroundAnimator == null 440 || animLayer < mAnimationBackgroundAnimator.mAnimLayer) { 441 mAnimationBackgroundAnimator = winAnimator; 442 animLayer = mService.adjustAnimationBackground(winAnimator); 443 mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM, 444 ((color >> 24) & 0xff) / 255f, 0); 445 } 446 } 447 448 void switchUser() { 449 int top = mTasks.size(); 450 for (int taskNdx = 0; taskNdx < top; ++taskNdx) { 451 Task task = mTasks.get(taskNdx); 452 if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) { 453 mTasks.remove(taskNdx); 454 mTasks.add(task); 455 --top; 456 } 457 } 458 } 459 460 void close() { 461 if (mAnimationBackgroundSurface != null) { 462 mAnimationBackgroundSurface.destroySurface(); 463 mAnimationBackgroundSurface = null; 464 } 465 for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { 466 mTasks.get(taskNdx).close(); 467 } 468 mDisplayContent = null; 469 } 470 471 public void dump(String prefix, PrintWriter pw) { 472 pw.print(prefix); pw.print("mStackId="); pw.println(mStackId); 473 pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach); 474 for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) { 475 pw.print(prefix); 476 mTasks.get(taskNdx).printTo(prefix + " ", pw); 477 } 478 if (mAnimationBackgroundSurface.isDimming()) { 479 pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:"); 480 mAnimationBackgroundSurface.printTo(prefix + " ", pw); 481 } 482 if (!mExitingAppTokens.isEmpty()) { 483 pw.println(); 484 pw.println(" Exiting application tokens:"); 485 for (int i = mExitingAppTokens.size() - 1; i >= 0; i--) { 486 WindowToken token = mExitingAppTokens.get(i); 487 pw.print(" Exiting App #"); pw.print(i); 488 pw.print(' '); pw.print(token); 489 pw.println(':'); 490 token.dump(pw, " "); 491 } 492 } 493 } 494 495 @Override 496 public boolean isFullscreen() { 497 return mFullscreen; 498 } 499 500 @Override 501 public DisplayInfo getDisplayInfo() { 502 return mDisplayContent.getDisplayInfo(); 503 } 504 505 @Override 506 public String toString() { 507 return "{stackId=" + mStackId + " tasks=" + mTasks + "}"; 508 } 509} 510