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