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