WindowContainer.java revision d276563b38907647ce70940e1e90603826df6ab4
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 17package com.android.server.wm; 18 19import android.annotation.CallSuper; 20import android.content.res.Configuration; 21import android.util.Pools; 22 23import com.android.internal.util.ToBooleanFunction; 24 25import java.util.Comparator; 26import java.util.LinkedList; 27import java.util.function.Consumer; 28import java.util.function.Predicate; 29 30import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; 31import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; 32import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 33 34/** 35 * Defines common functionality for classes that can hold windows directly or through their 36 * children in a hierarchy form. 37 * The test class is {@link WindowContainerTests} which must be kept up-to-date and ran anytime 38 * changes are made to this class. 39 */ 40class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> { 41 42 static final int POSITION_TOP = Integer.MAX_VALUE; 43 static final int POSITION_BOTTOM = Integer.MIN_VALUE; 44 45 /** 46 * The parent of this window container. 47 * For removing or setting new parent {@link #setParent} should be used, because it also 48 * performs configuration updates based on new parent's settings. 49 */ 50 private WindowContainer mParent = null; 51 52 // List of children for this window container. List is in z-order as the children appear on 53 // screen with the top-most window container at the tail of the list. 54 protected final LinkedList<E> mChildren = new LinkedList(); 55 56 /** Contains override configuration settings applied to this window container. */ 57 private Configuration mOverrideConfiguration = new Configuration(); 58 59 /** 60 * Contains full configuration applied to this window container. Corresponds to full parent's 61 * config with applied {@link #mOverrideConfiguration}. 62 */ 63 private Configuration mFullConfiguration = new Configuration(); 64 65 /** 66 * Contains merged override configuration settings from the top of the hierarchy down to this 67 * particular instance. It is different from {@link #mFullConfiguration} because it starts from 68 * topmost container's override config instead of global config. 69 */ 70 private Configuration mMergedOverrideConfiguration = new Configuration(); 71 72 // The specified orientation for this window container. 73 protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED; 74 75 private final Pools.SynchronizedPool<ForAllWindowsConsumerWrapper> mConsumerWrapperPool = 76 new Pools.SynchronizedPool<>(3); 77 78 final protected WindowContainer getParent() { 79 return mParent; 80 } 81 82 final protected void setParent(WindowContainer parent) { 83 mParent = parent; 84 // Removing parent usually means that we've detached this entity to destroy it or to attach 85 // to another parent. In both cases we don't need to update the configuration now. 86 if (mParent != null) { 87 // Update full configuration of this container and all its children. 88 onConfigurationChanged(mParent.mFullConfiguration); 89 // Update merged override configuration of this container and all its children. 90 onMergedOverrideConfigurationChanged(); 91 } 92 93 onParentSet(); 94 } 95 96 /** 97 * Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called. 98 * Supposed to be overridden and contain actions that should be executed after parent was set. 99 */ 100 void onParentSet() { 101 // Do nothing by default. 102 } 103 104 // Temp. holders for a chain of containers we are currently processing. 105 private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList(); 106 private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList(); 107 108 /** 109 * Adds the input window container has a child of this container in order based on the input 110 * comparator. 111 * @param child The window container to add as a child of this window container. 112 * @param comparator Comparator to use in determining the position the child should be added to. 113 * If null, the child will be added to the top. 114 */ 115 @CallSuper 116 protected void addChild(E child, Comparator<E> comparator) { 117 if (child.getParent() != null) { 118 throw new IllegalArgumentException("addChild: container=" + child.getName() 119 + " is already a child of container=" + child.getParent().getName() 120 + " can't add to container=" + getName()); 121 } 122 123 int positionToAdd = -1; 124 if (comparator != null) { 125 final int count = mChildren.size(); 126 for (int i = 0; i < count; i++) { 127 if (comparator.compare(child, mChildren.get(i)) < 0) { 128 positionToAdd = i; 129 break; 130 } 131 } 132 } 133 134 if (positionToAdd == -1) { 135 mChildren.add(child); 136 } else { 137 mChildren.add(positionToAdd, child); 138 } 139 // Set the parent after we've actually added a child in case a subclass depends on this. 140 child.setParent(this); 141 } 142 143 /** Adds the input window container has a child of this container at the input index. */ 144 @CallSuper 145 void addChild(E child, int index) { 146 if (child.getParent() != null) { 147 throw new IllegalArgumentException("addChild: container=" + child.getName() 148 + " is already a child of container=" + child.getParent().getName() 149 + " can't add to container=" + getName()); 150 } 151 mChildren.add(index, child); 152 // Set the parent after we've actually added a child in case a subclass depends on this. 153 child.setParent(this); 154 } 155 156 /** 157 * Removes the input child container from this container which is its parent. 158 * 159 * @return True if the container did contain the input child and it was detached. 160 */ 161 @CallSuper 162 void removeChild(E child) { 163 if (mChildren.remove(child)) { 164 child.setParent(null); 165 } else { 166 throw new IllegalArgumentException("removeChild: container=" + child.getName() 167 + " is not a child of container=" + getName()); 168 } 169 } 170 171 /** 172 * Removes this window container and its children with no regard for what else might be going on 173 * in the system. For example, the container will be removed during animation if this method is 174 * called which isn't desirable. For most cases you want to call {@link #removeIfPossible()} 175 * which allows the system to defer removal until a suitable time. 176 */ 177 @CallSuper 178 void removeImmediately() { 179 while (!mChildren.isEmpty()) { 180 final WindowContainer child = mChildren.peekLast(); 181 child.removeImmediately(); 182 // Need to do this after calling remove on the child because the child might try to 183 // remove/detach itself from its parent which will cause an exception if we remove 184 // it before calling remove on the child. 185 mChildren.remove(child); 186 } 187 188 if (mParent != null) { 189 mParent.removeChild(this); 190 } 191 } 192 193 /** 194 * Removes this window container and its children taking care not to remove them during a 195 * critical stage in the system. For example, some containers will not be removed during 196 * animation if this method is called. 197 */ 198 // TODO: figure-out implementation that works best for this. 199 // E.g. when do we remove from parent list? maybe not... 200 void removeIfPossible() { 201 for (int i = mChildren.size() - 1; i >= 0; --i) { 202 final WindowContainer wc = mChildren.get(i); 203 wc.removeIfPossible(); 204 } 205 } 206 207 /** Returns true if this window container has the input child. */ 208 boolean hasChild(WindowContainer child) { 209 for (int i = mChildren.size() - 1; i >= 0; --i) { 210 final WindowContainer current = mChildren.get(i); 211 if (current == child || current.hasChild(child)) { 212 return true; 213 } 214 } 215 return false; 216 } 217 218 /** 219 * Move a child from it's current place in siblings list to the specified position, 220 * with an option to move all its parents to top. 221 * @param position Target position to move the child to. 222 * @param child Child to move to selected position. 223 * @param includingParents Flag indicating whether we need to move the entire branch of the 224 * hierarchy when we're moving a child to {@link #POSITION_TOP} or 225 * {@link #POSITION_BOTTOM}. When moving to other intermediate positions 226 * this flag will do nothing. 227 */ 228 @CallSuper 229 void positionChildAt(int position, E child, boolean includingParents) { 230 if ((position < 0 && position != POSITION_BOTTOM) 231 || (position >= mChildren.size() && position != POSITION_TOP)) { 232 throw new IllegalArgumentException("positionAt: invalid position=" + position 233 + ", children number=" + mChildren.size()); 234 } 235 236 if (position == mChildren.size() - 1) { 237 position = POSITION_TOP; 238 } else if (position == 0) { 239 position = POSITION_BOTTOM; 240 } 241 242 switch (position) { 243 case POSITION_TOP: 244 if (mChildren.getLast() != child) { 245 mChildren.remove(child); 246 mChildren.addLast(child); 247 } 248 if (includingParents && getParent() != null) { 249 getParent().positionChildAt(POSITION_TOP, this /* child */, 250 true /* includingParents */); 251 } 252 break; 253 case POSITION_BOTTOM: 254 if (mChildren.getFirst() != child) { 255 mChildren.remove(child); 256 mChildren.addFirst(child); 257 } 258 if (includingParents && getParent() != null) { 259 getParent().positionChildAt(POSITION_BOTTOM, this /* child */, 260 true /* includingParents */); 261 } 262 break; 263 default: 264 mChildren.remove(child); 265 mChildren.add(position, child); 266 } 267 } 268 269 /** 270 * Returns full configuration applied to this window container. 271 * This method should be used for getting settings applied in each particular level of the 272 * hierarchy. 273 */ 274 Configuration getConfiguration() { 275 return mFullConfiguration; 276 } 277 278 /** 279 * Notify that parent config changed and we need to update full configuration. 280 * @see #mFullConfiguration 281 */ 282 void onConfigurationChanged(Configuration newParentConfig) { 283 mFullConfiguration.setTo(newParentConfig); 284 mFullConfiguration.updateFrom(mOverrideConfiguration); 285 for (int i = mChildren.size() - 1; i >= 0; --i) { 286 final WindowContainer child = mChildren.get(i); 287 child.onConfigurationChanged(mFullConfiguration); 288 } 289 } 290 291 /** Returns override configuration applied to this window container. */ 292 Configuration getOverrideConfiguration() { 293 return mOverrideConfiguration; 294 } 295 296 /** 297 * Update override configuration and recalculate full config. 298 * @see #mOverrideConfiguration 299 * @see #mFullConfiguration 300 */ 301 void onOverrideConfigurationChanged(Configuration overrideConfiguration) { 302 mOverrideConfiguration.setTo(overrideConfiguration); 303 // Update full configuration of this container and all its children. 304 onConfigurationChanged(mParent != null ? mParent.getConfiguration() : Configuration.EMPTY); 305 // Update merged override config of this container and all its children. 306 onMergedOverrideConfigurationChanged(); 307 } 308 309 /** 310 * Get merged override configuration from the top of the hierarchy down to this 311 * particular instance. This should be reported to client as override config. 312 */ 313 Configuration getMergedOverrideConfiguration() { 314 return mMergedOverrideConfiguration; 315 } 316 317 /** 318 * Update merged override configuration based on corresponding parent's config and notify all 319 * its children. If there is no parent, merged override configuration will set equal to current 320 * override config. 321 * @see #mMergedOverrideConfiguration 322 */ 323 private void onMergedOverrideConfigurationChanged() { 324 if (mParent != null) { 325 mMergedOverrideConfiguration.setTo(mParent.getMergedOverrideConfiguration()); 326 mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration); 327 } else { 328 mMergedOverrideConfiguration.setTo(mOverrideConfiguration); 329 } 330 for (int i = mChildren.size() - 1; i >= 0; --i) { 331 final WindowContainer child = mChildren.get(i); 332 child.onMergedOverrideConfigurationChanged(); 333 } 334 } 335 336 /** 337 * Notify that the display this container is on has changed. 338 * @param dc The new display this container is on. 339 */ 340 void onDisplayChanged(DisplayContent dc) { 341 for (int i = mChildren.size() - 1; i >= 0; --i) { 342 final WindowContainer child = mChildren.get(i); 343 child.onDisplayChanged(dc); 344 } 345 } 346 347 void setWaitingForDrawnIfResizingChanged() { 348 for (int i = mChildren.size() - 1; i >= 0; --i) { 349 final WindowContainer wc = mChildren.get(i); 350 wc.setWaitingForDrawnIfResizingChanged(); 351 } 352 } 353 354 void onResize() { 355 for (int i = mChildren.size() - 1; i >= 0; --i) { 356 final WindowContainer wc = mChildren.get(i); 357 wc.onResize(); 358 } 359 } 360 361 void onMovedByResize() { 362 for (int i = mChildren.size() - 1; i >= 0; --i) { 363 final WindowContainer wc = mChildren.get(i); 364 wc.onMovedByResize(); 365 } 366 } 367 368 void resetDragResizingChangeReported() { 369 for (int i = mChildren.size() - 1; i >= 0; --i) { 370 final WindowContainer wc = mChildren.get(i); 371 wc.resetDragResizingChangeReported(); 372 } 373 } 374 375 void forceWindowsScaleableInTransaction(boolean force) { 376 for (int i = mChildren.size() - 1; i >= 0; --i) { 377 final WindowContainer wc = mChildren.get(i); 378 wc.forceWindowsScaleableInTransaction(force); 379 } 380 } 381 382 boolean isAnimating() { 383 for (int j = mChildren.size() - 1; j >= 0; j--) { 384 final WindowContainer wc = mChildren.get(j); 385 if (wc.isAnimating()) { 386 return true; 387 } 388 } 389 return false; 390 } 391 392 void sendAppVisibilityToClients() { 393 for (int i = mChildren.size() - 1; i >= 0; --i) { 394 final WindowContainer wc = mChildren.get(i); 395 wc.sendAppVisibilityToClients(); 396 } 397 } 398 399 void setVisibleBeforeClientHidden() { 400 for (int i = mChildren.size() - 1; i >= 0; --i) { 401 final WindowContainer wc = mChildren.get(i); 402 wc.setVisibleBeforeClientHidden(); 403 } 404 } 405 406 /** 407 * Returns true if the container or one of its children as some content it can display or wants 408 * to display (e.g. app views or saved surface). 409 * 410 * NOTE: While this method will return true if the there is some content to display, it doesn't 411 * mean the container is visible. Use {@link #isVisible()} to determine if the container is 412 * visible. 413 */ 414 boolean hasContentToDisplay() { 415 for (int i = mChildren.size() - 1; i >= 0; --i) { 416 final WindowContainer wc = mChildren.get(i); 417 if (wc.hasContentToDisplay()) { 418 return true; 419 } 420 } 421 return false; 422 } 423 424 /** 425 * Returns true if the container or one of its children is considered visible from the 426 * WindowManager perspective which usually means valid surface and some other internal state 427 * are true. 428 * 429 * NOTE: While this method will return true if the surface is visible, it doesn't mean the 430 * client has actually displayed any content. Use {@link #hasContentToDisplay()} to determine if 431 * the container has any content to display. 432 */ 433 boolean isVisible() { 434 // TODO: Will this be more correct if it checks the visibility of its parents? 435 // It depends...For example, Tasks and Stacks are only visible if there children are visible 436 // but, WindowState are not visible if there parent are not visible. Maybe have the 437 // container specify which direction to treverse for for visibility? 438 for (int i = mChildren.size() - 1; i >= 0; --i) { 439 final WindowContainer wc = mChildren.get(i); 440 if (wc.isVisible()) { 441 return true; 442 } 443 } 444 return false; 445 } 446 447 /** Returns the top child container or this container if there are no children. */ 448 WindowContainer getTop() { 449 return mChildren.isEmpty() ? this : mChildren.peekLast(); 450 } 451 452 /** Returns true if there is still a removal being deferred */ 453 boolean checkCompleteDeferredRemoval() { 454 boolean stillDeferringRemoval = false; 455 456 for (int i = mChildren.size() - 1; i >= 0; --i) { 457 final WindowContainer wc = mChildren.get(i); 458 stillDeferringRemoval |= wc.checkCompleteDeferredRemoval(); 459 } 460 461 return stillDeferringRemoval; 462 } 463 464 /** Checks if all windows in an app are all drawn and shows them if needed. */ 465 void checkAppWindowsReadyToShow() { 466 for (int i = mChildren.size() - 1; i >= 0; --i) { 467 final WindowContainer wc = mChildren.get(i); 468 wc.checkAppWindowsReadyToShow(); 469 } 470 } 471 472 /** Step currently ongoing animation for App window containers. */ 473 void stepAppWindowsAnimation(long currentTime) { 474 for (int i = mChildren.size() - 1; i >= 0; --i) { 475 final WindowContainer wc = mChildren.get(i); 476 wc.stepAppWindowsAnimation(currentTime); 477 } 478 } 479 480 void onAppTransitionDone() { 481 for (int i = mChildren.size() - 1; i >= 0; --i) { 482 final WindowContainer wc = mChildren.get(i); 483 wc.onAppTransitionDone(); 484 } 485 } 486 487 void setOrientation(int orientation) { 488 mOrientation = orientation; 489 } 490 491 /** 492 * Returns the specified orientation for this window container or one of its children is there 493 * is one set, or {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSET} if no 494 * specification is set. 495 * NOTE: {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} is a 496 * specification... 497 */ 498 int getOrientation() { 499 500 if (!fillsParent() || !isVisible()) { 501 // Ignore invisible containers or containers that don't completely fills their parents. 502 return SCREEN_ORIENTATION_UNSET; 503 } 504 505 // The container fills its parent so we can use it orientation if it has one specified, 506 // otherwise we prefer to use the orientation of its topmost child that has one 507 // specified and fall back on this container's unset or unspecified value as a candidate 508 // if none of the children have a better candidate for the orientation. 509 if (mOrientation != SCREEN_ORIENTATION_UNSET 510 && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { 511 return mOrientation; 512 } 513 int candidate = mOrientation; 514 515 for (int i = mChildren.size() - 1; i >= 0; --i) { 516 final WindowContainer wc = mChildren.get(i); 517 518 final int orientation = wc.getOrientation(); 519 if (orientation == SCREEN_ORIENTATION_BEHIND) { 520 // container wants us to use the orientation of the container behind it. See if we 521 // can find one. Else return SCREEN_ORIENTATION_BEHIND so the caller can choose to 522 // look behind this container. 523 candidate = orientation; 524 continue; 525 } 526 527 if (orientation == SCREEN_ORIENTATION_UNSET) { 528 continue; 529 } 530 531 if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) { 532 // Use the orientation if the container fills its parent or requested an explicit 533 // orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED. 534 return orientation; 535 } 536 } 537 538 return candidate; 539 } 540 541 /** 542 * Returns true if this container is opaque and fills all the space made available by its parent 543 * container. 544 * 545 * NOTE: It is possible for this container to occupy more space than the parent has (or less), 546 * this is just a signal from the client to window manager stating its intent, but not what it 547 * actually does. 548 */ 549 boolean fillsParent() { 550 return false; 551 } 552 553 // TODO: Users would have their own window containers under the display container? 554 void switchUser() { 555 for (int i = mChildren.size() - 1; i >= 0; --i) { 556 mChildren.get(i).switchUser(); 557 } 558 } 559 560 /** 561 * For all windows at or below this container call the callback. 562 * @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and 563 * stops the search if {@link ToBooleanFunction#apply} returns true. 564 * @param traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of 565 * z-order, else from bottom-to-top. 566 * @return True if the search ended before we reached the end of the hierarchy due to 567 * {@link ToBooleanFunction#apply} returning true. 568 */ 569 boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { 570 if (traverseTopToBottom) { 571 for (int i = mChildren.size() - 1; i >= 0; --i) { 572 if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) { 573 return true; 574 } 575 } 576 } else { 577 final int count = mChildren.size(); 578 for (int i = 0; i < count; i++) { 579 if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) { 580 return true; 581 } 582 } 583 } 584 return false; 585 } 586 587 void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) { 588 ForAllWindowsConsumerWrapper wrapper = obtainConsumerWrapper(callback); 589 forAllWindows(wrapper, traverseTopToBottom); 590 wrapper.release(); 591 } 592 593 WindowState getWindow(Predicate<WindowState> callback) { 594 for (int i = mChildren.size() - 1; i >= 0; --i) { 595 final WindowState w = mChildren.get(i).getWindow(callback); 596 if (w != null) { 597 return w; 598 } 599 } 600 601 return null; 602 } 603 604 /** 605 * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than 606 * the input container in terms of z-order. 607 */ 608 @Override 609 public int compareTo(WindowContainer other) { 610 if (this == other) { 611 return 0; 612 } 613 614 if (mParent != null && mParent == other.mParent) { 615 final LinkedList<WindowContainer> list = mParent.mChildren; 616 return list.indexOf(this) > list.indexOf(other) ? 1 : -1; 617 } 618 619 final LinkedList<WindowContainer> thisParentChain = mTmpChain1; 620 final LinkedList<WindowContainer> otherParentChain = mTmpChain2; 621 getParents(thisParentChain); 622 other.getParents(otherParentChain); 623 624 // Find the common ancestor of both containers. 625 WindowContainer commonAncestor = null; 626 WindowContainer thisTop = thisParentChain.peekLast(); 627 WindowContainer otherTop = otherParentChain.peekLast(); 628 while (thisTop != null && otherTop != null && thisTop == otherTop) { 629 commonAncestor = thisParentChain.removeLast(); 630 otherParentChain.removeLast(); 631 thisTop = thisParentChain.peekLast(); 632 otherTop = otherParentChain.peekLast(); 633 } 634 635 // Containers don't belong to the same hierarchy??? 636 if (commonAncestor == null) { 637 throw new IllegalArgumentException("No in the same hierarchy this=" 638 + thisParentChain + " other=" + otherParentChain); 639 } 640 641 // Children are always considered greater than their parents, so if one of the containers 642 // we are comparing it the parent of the other then whichever is the child is greater. 643 if (commonAncestor == this) { 644 return -1; 645 } else if (commonAncestor == other) { 646 return 1; 647 } 648 649 // The position of the first non-common ancestor in the common ancestor list determines 650 // which is greater the which. 651 final LinkedList<WindowContainer> list = commonAncestor.mChildren; 652 return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast()) 653 ? 1 : -1; 654 } 655 656 private void getParents(LinkedList<WindowContainer> parents) { 657 parents.clear(); 658 WindowContainer current = this; 659 do { 660 parents.addLast(current); 661 current = current.mParent; 662 } while (current != null); 663 } 664 665 /** 666 * Dumps the names of this container children in the input print writer indenting each 667 * level with the input prefix. 668 */ 669 void dumpChildrenNames(StringBuilder out, String prefix) { 670 final String childPrefix = prefix + " "; 671 out.append(getName() + "\n"); 672 for (int i = mChildren.size() - 1; i >= 0; --i) { 673 final WindowContainer wc = mChildren.get(i); 674 out.append(childPrefix + "#" + i + " "); 675 wc.dumpChildrenNames(out, childPrefix); 676 } 677 } 678 679 String getName() { 680 return toString(); 681 } 682 683 private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) { 684 ForAllWindowsConsumerWrapper wrapper = mConsumerWrapperPool.acquire(); 685 if (wrapper == null) { 686 wrapper = new ForAllWindowsConsumerWrapper(); 687 } 688 wrapper.setConsumer(consumer); 689 return wrapper; 690 } 691 692 private final class ForAllWindowsConsumerWrapper implements ToBooleanFunction<WindowState> { 693 694 private Consumer<WindowState> mConsumer; 695 696 void setConsumer(Consumer<WindowState> consumer) { 697 mConsumer = consumer; 698 } 699 700 @Override 701 public boolean apply(WindowState w) { 702 mConsumer.accept(w); 703 return false; 704 } 705 706 void release() { 707 mConsumer = null; 708 mConsumerWrapperPool.release(this); 709 } 710 } 711} 712