BufferQueueConsumer.cpp revision 8b640e6403de9dd328b2605c9f21b62d1d31e7d6
1/*
2 * Copyright 2014 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
17#include <inttypes.h>
18
19#define LOG_TAG "BufferQueueConsumer"
20#define ATRACE_TAG ATRACE_TAG_GRAPHICS
21//#define LOG_NDEBUG 0
22
23#include <gui/BufferItem.h>
24#include <gui/BufferQueueConsumer.h>
25#include <gui/BufferQueueCore.h>
26#include <gui/IConsumerListener.h>
27#include <gui/IProducerListener.h>
28
29#include <binder/IPCThreadState.h>
30#include <binder/PermissionCache.h>
31#include <private/android_filesystem_config.h>
32
33namespace android {
34
35BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) :
36    mCore(core),
37    mSlots(core->mSlots),
38    mConsumerName() {}
39
40BufferQueueConsumer::~BufferQueueConsumer() {}
41
42status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
43        nsecs_t expectedPresent, uint64_t maxFrameNumber) {
44    ATRACE_CALL();
45
46    int numDroppedBuffers = 0;
47    sp<IProducerListener> listener;
48    {
49        Mutex::Autolock lock(mCore->mMutex);
50
51        // Check that the consumer doesn't currently have the maximum number of
52        // buffers acquired. We allow the max buffer count to be exceeded by one
53        // buffer so that the consumer can successfully set up the newly acquired
54        // buffer before releasing the old one.
55        int numAcquiredBuffers = 0;
56        for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
57            if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
58                ++numAcquiredBuffers;
59            }
60        }
61        if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
62            BQ_LOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)",
63                    numAcquiredBuffers, mCore->mMaxAcquiredBufferCount);
64            return INVALID_OPERATION;
65        }
66
67        // Check if the queue is empty.
68        // In asynchronous mode the list is guaranteed to be one buffer deep,
69        // while in synchronous mode we use the oldest buffer.
70        if (mCore->mQueue.empty()) {
71            return NO_BUFFER_AVAILABLE;
72        }
73
74        BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
75
76        // If expectedPresent is specified, we may not want to return a buffer yet.
77        // If it's specified and there's more than one buffer queued, we may want
78        // to drop a buffer.
79        if (expectedPresent != 0) {
80            const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
81
82            // The 'expectedPresent' argument indicates when the buffer is expected
83            // to be presented on-screen. If the buffer's desired present time is
84            // earlier (less) than expectedPresent -- meaning it will be displayed
85            // on time or possibly late if we show it as soon as possible -- we
86            // acquire and return it. If we don't want to display it until after the
87            // expectedPresent time, we return PRESENT_LATER without acquiring it.
88            //
89            // To be safe, we don't defer acquisition if expectedPresent is more
90            // than one second in the future beyond the desired present time
91            // (i.e., we'd be holding the buffer for a long time).
92            //
93            // NOTE: Code assumes monotonic time values from the system clock
94            // are positive.
95
96            // Start by checking to see if we can drop frames. We skip this check if
97            // the timestamps are being auto-generated by Surface. If the app isn't
98            // generating timestamps explicitly, it probably doesn't want frames to
99            // be discarded based on them.
100            while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
101                const BufferItem& bufferItem(mCore->mQueue[1]);
102
103                // If dropping entry[0] would leave us with a buffer that the
104                // consumer is not yet ready for, don't drop it.
105                if (maxFrameNumber && bufferItem.mFrameNumber > maxFrameNumber) {
106                    break;
107                }
108
109                // If entry[1] is timely, drop entry[0] (and repeat). We apply an
110                // additional criterion here: we only drop the earlier buffer if our
111                // desiredPresent falls within +/- 1 second of the expected present.
112                // Otherwise, bogus desiredPresent times (e.g., 0 or a small
113                // relative timestamp), which normally mean "ignore the timestamp
114                // and acquire immediately", would cause us to drop frames.
115                //
116                // We may want to add an additional criterion: don't drop the
117                // earlier buffer if entry[1]'s fence hasn't signaled yet.
118                nsecs_t desiredPresent = bufferItem.mTimestamp;
119                if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
120                        desiredPresent > expectedPresent) {
121                    // This buffer is set to display in the near future, or
122                    // desiredPresent is garbage. Either way we don't want to drop
123                    // the previous buffer just to get this on the screen sooner.
124                    BQ_LOGV("acquireBuffer: nodrop desire=%" PRId64 " expect=%"
125                            PRId64 " (%" PRId64 ") now=%" PRId64,
126                            desiredPresent, expectedPresent,
127                            desiredPresent - expectedPresent,
128                            systemTime(CLOCK_MONOTONIC));
129                    break;
130                }
131
132                BQ_LOGV("acquireBuffer: drop desire=%" PRId64 " expect=%" PRId64
133                        " size=%zu",
134                        desiredPresent, expectedPresent, mCore->mQueue.size());
135                if (mCore->stillTracking(front)) {
136                    // Front buffer is still in mSlots, so mark the slot as free
137                    mSlots[front->mSlot].mBufferState = BufferSlot::FREE;
138                    mCore->mFreeBuffers.push_back(front->mSlot);
139                    listener = mCore->mConnectedProducerListener;
140                    ++numDroppedBuffers;
141                }
142                mCore->mQueue.erase(front);
143                front = mCore->mQueue.begin();
144            }
145
146            // See if the front buffer is ready to be acquired
147            nsecs_t desiredPresent = front->mTimestamp;
148            bool bufferIsDue = desiredPresent <= expectedPresent ||
149                    desiredPresent > expectedPresent + MAX_REASONABLE_NSEC;
150            bool consumerIsReady = maxFrameNumber > 0 ?
151                    front->mFrameNumber <= maxFrameNumber : true;
152            if (!bufferIsDue || !consumerIsReady) {
153                BQ_LOGV("acquireBuffer: defer desire=%" PRId64 " expect=%" PRId64
154                        " (%" PRId64 ") now=%" PRId64 " frame=%" PRIu64
155                        " consumer=%" PRIu64,
156                        desiredPresent, expectedPresent,
157                        desiredPresent - expectedPresent,
158                        systemTime(CLOCK_MONOTONIC),
159                        front->mFrameNumber, maxFrameNumber);
160                return PRESENT_LATER;
161            }
162
163            BQ_LOGV("acquireBuffer: accept desire=%" PRId64 " expect=%" PRId64 " "
164                    "(%" PRId64 ") now=%" PRId64, desiredPresent, expectedPresent,
165                    desiredPresent - expectedPresent,
166                    systemTime(CLOCK_MONOTONIC));
167        }
168
169        int slot = front->mSlot;
170        *outBuffer = *front;
171        ATRACE_BUFFER_INDEX(slot);
172
173        BQ_LOGV("acquireBuffer: acquiring { slot=%d/%" PRIu64 " buffer=%p }",
174                slot, front->mFrameNumber, front->mGraphicBuffer->handle);
175        // If the front buffer is still being tracked, update its slot state
176        if (mCore->stillTracking(front)) {
177            mSlots[slot].mAcquireCalled = true;
178            mSlots[slot].mNeedsCleanupOnRelease = false;
179            mSlots[slot].mBufferState = BufferSlot::ACQUIRED;
180            mSlots[slot].mFence = Fence::NO_FENCE;
181        }
182
183        // If the buffer has previously been acquired by the consumer, set
184        // mGraphicBuffer to NULL to avoid unnecessarily remapping this buffer
185        // on the consumer side
186        if (outBuffer->mAcquireCalled) {
187            outBuffer->mGraphicBuffer = NULL;
188        }
189
190        mCore->mQueue.erase(front);
191
192        // We might have freed a slot while dropping old buffers, or the producer
193        // may be blocked waiting for the number of buffers in the queue to
194        // decrease.
195        mCore->mDequeueCondition.broadcast();
196
197        ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
198
199        mCore->validateConsistencyLocked();
200    }
201
202    if (listener != NULL) {
203        for (int i = 0; i < numDroppedBuffers; ++i) {
204            listener->onBufferReleased();
205        }
206    }
207
208    return NO_ERROR;
209}
210
211status_t BufferQueueConsumer::detachBuffer(int slot) {
212    ATRACE_CALL();
213    ATRACE_BUFFER_INDEX(slot);
214    BQ_LOGV("detachBuffer(C): slot %d", slot);
215    Mutex::Autolock lock(mCore->mMutex);
216
217    if (mCore->mIsAbandoned) {
218        BQ_LOGE("detachBuffer(C): BufferQueue has been abandoned");
219        return NO_INIT;
220    }
221
222    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
223        BQ_LOGE("detachBuffer(C): slot index %d out of range [0, %d)",
224                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
225        return BAD_VALUE;
226    } else if (mSlots[slot].mBufferState != BufferSlot::ACQUIRED) {
227        BQ_LOGE("detachBuffer(C): slot %d is not owned by the consumer "
228                "(state = %d)", slot, mSlots[slot].mBufferState);
229        return BAD_VALUE;
230    }
231
232    mCore->freeBufferLocked(slot);
233    mCore->mDequeueCondition.broadcast();
234    mCore->validateConsistencyLocked();
235
236    return NO_ERROR;
237}
238
239status_t BufferQueueConsumer::attachBuffer(int* outSlot,
240        const sp<android::GraphicBuffer>& buffer) {
241    ATRACE_CALL();
242
243    if (outSlot == NULL) {
244        BQ_LOGE("attachBuffer(P): outSlot must not be NULL");
245        return BAD_VALUE;
246    } else if (buffer == NULL) {
247        BQ_LOGE("attachBuffer(P): cannot attach NULL buffer");
248        return BAD_VALUE;
249    }
250
251    Mutex::Autolock lock(mCore->mMutex);
252
253    // Make sure we don't have too many acquired buffers
254    int numAcquiredBuffers = 0;
255    for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
256        if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
257            ++numAcquiredBuffers;
258        }
259    }
260
261    if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
262        BQ_LOGE("attachBuffer(P): max acquired buffer count reached: %d "
263                "(max %d)", numAcquiredBuffers,
264                mCore->mMaxAcquiredBufferCount);
265        return INVALID_OPERATION;
266    }
267
268    if (buffer->getGenerationNumber() != mCore->mGenerationNumber) {
269        BQ_LOGE("attachBuffer: generation number mismatch [buffer %u] "
270                "[queue %u]", buffer->getGenerationNumber(),
271                mCore->mGenerationNumber);
272        return BAD_VALUE;
273    }
274
275    // Find a free slot to put the buffer into
276    int found = BufferQueueCore::INVALID_BUFFER_SLOT;
277    if (!mCore->mFreeSlots.empty()) {
278        auto slot = mCore->mFreeSlots.begin();
279        found = *slot;
280        mCore->mFreeSlots.erase(slot);
281    } else if (!mCore->mFreeBuffers.empty()) {
282        found = mCore->mFreeBuffers.front();
283        mCore->mFreeBuffers.remove(found);
284    }
285    if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
286        BQ_LOGE("attachBuffer(P): could not find free buffer slot");
287        return NO_MEMORY;
288    }
289
290    *outSlot = found;
291    ATRACE_BUFFER_INDEX(*outSlot);
292    BQ_LOGV("attachBuffer(C): returning slot %d", *outSlot);
293
294    mSlots[*outSlot].mGraphicBuffer = buffer;
295    mSlots[*outSlot].mBufferState = BufferSlot::ACQUIRED;
296    mSlots[*outSlot].mAttachedByConsumer = true;
297    mSlots[*outSlot].mNeedsCleanupOnRelease = false;
298    mSlots[*outSlot].mFence = Fence::NO_FENCE;
299    mSlots[*outSlot].mFrameNumber = 0;
300
301    // mAcquireCalled tells BufferQueue that it doesn't need to send a valid
302    // GraphicBuffer pointer on the next acquireBuffer call, which decreases
303    // Binder traffic by not un/flattening the GraphicBuffer. However, it
304    // requires that the consumer maintain a cached copy of the slot <--> buffer
305    // mappings, which is why the consumer doesn't need the valid pointer on
306    // acquire.
307    //
308    // The StreamSplitter is one of the primary users of the attach/detach
309    // logic, and while it is running, all buffers it acquires are immediately
310    // detached, and all buffers it eventually releases are ones that were
311    // attached (as opposed to having been obtained from acquireBuffer), so it
312    // doesn't make sense to maintain the slot/buffer mappings, which would
313    // become invalid for every buffer during detach/attach. By setting this to
314    // false, the valid GraphicBuffer pointer will always be sent with acquire
315    // for attached buffers.
316    mSlots[*outSlot].mAcquireCalled = false;
317
318    mCore->validateConsistencyLocked();
319
320    return NO_ERROR;
321}
322
323status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
324        const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
325        EGLSyncKHR eglFence) {
326    ATRACE_CALL();
327    ATRACE_BUFFER_INDEX(slot);
328
329    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
330            releaseFence == NULL) {
331        BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
332                releaseFence.get());
333        return BAD_VALUE;
334    }
335
336    sp<IProducerListener> listener;
337    { // Autolock scope
338        Mutex::Autolock lock(mCore->mMutex);
339
340        // If the frame number has changed because the buffer has been reallocated,
341        // we can ignore this releaseBuffer for the old buffer
342        if (frameNumber != mSlots[slot].mFrameNumber) {
343            return STALE_BUFFER_SLOT;
344        }
345
346        // Make sure this buffer hasn't been queued while acquired by the consumer
347        BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
348        while (current != mCore->mQueue.end()) {
349            if (current->mSlot == slot) {
350                BQ_LOGE("releaseBuffer: buffer slot %d pending release is "
351                        "currently queued", slot);
352                return BAD_VALUE;
353            }
354            ++current;
355        }
356
357        if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
358            mSlots[slot].mEglDisplay = eglDisplay;
359            mSlots[slot].mEglFence = eglFence;
360            mSlots[slot].mFence = releaseFence;
361            mSlots[slot].mBufferState = BufferSlot::FREE;
362            mCore->mFreeBuffers.push_back(slot);
363            listener = mCore->mConnectedProducerListener;
364            BQ_LOGV("releaseBuffer: releasing slot %d", slot);
365        } else if (mSlots[slot].mNeedsCleanupOnRelease) {
366            BQ_LOGV("releaseBuffer: releasing a stale buffer slot %d "
367                    "(state = %d)", slot, mSlots[slot].mBufferState);
368            mSlots[slot].mNeedsCleanupOnRelease = false;
369            return STALE_BUFFER_SLOT;
370        } else {
371            BQ_LOGE("releaseBuffer: attempted to release buffer slot %d "
372                    "but its state was %d", slot, mSlots[slot].mBufferState);
373            return BAD_VALUE;
374        }
375
376        mCore->mDequeueCondition.broadcast();
377        mCore->validateConsistencyLocked();
378    } // Autolock scope
379
380    // Call back without lock held
381    if (listener != NULL) {
382        listener->onBufferReleased();
383    }
384
385    return NO_ERROR;
386}
387
388status_t BufferQueueConsumer::connect(
389        const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
390    ATRACE_CALL();
391
392    if (consumerListener == NULL) {
393        BQ_LOGE("connect(C): consumerListener may not be NULL");
394        return BAD_VALUE;
395    }
396
397    BQ_LOGV("connect(C): controlledByApp=%s",
398            controlledByApp ? "true" : "false");
399
400    Mutex::Autolock lock(mCore->mMutex);
401
402    if (mCore->mIsAbandoned) {
403        BQ_LOGE("connect(C): BufferQueue has been abandoned");
404        return NO_INIT;
405    }
406
407    mCore->mConsumerListener = consumerListener;
408    mCore->mConsumerControlledByApp = controlledByApp;
409
410    return NO_ERROR;
411}
412
413status_t BufferQueueConsumer::disconnect() {
414    ATRACE_CALL();
415
416    BQ_LOGV("disconnect(C)");
417
418    Mutex::Autolock lock(mCore->mMutex);
419
420    if (mCore->mConsumerListener == NULL) {
421        BQ_LOGE("disconnect(C): no consumer is connected");
422        return BAD_VALUE;
423    }
424
425    mCore->mIsAbandoned = true;
426    mCore->mConsumerListener = NULL;
427    mCore->mQueue.clear();
428    mCore->freeAllBuffersLocked();
429    mCore->mDequeueCondition.broadcast();
430    return NO_ERROR;
431}
432
433status_t BufferQueueConsumer::getReleasedBuffers(uint64_t *outSlotMask) {
434    ATRACE_CALL();
435
436    if (outSlotMask == NULL) {
437        BQ_LOGE("getReleasedBuffers: outSlotMask may not be NULL");
438        return BAD_VALUE;
439    }
440
441    Mutex::Autolock lock(mCore->mMutex);
442
443    if (mCore->mIsAbandoned) {
444        BQ_LOGE("getReleasedBuffers: BufferQueue has been abandoned");
445        return NO_INIT;
446    }
447
448    uint64_t mask = 0;
449    for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
450        if (!mSlots[s].mAcquireCalled) {
451            mask |= (1ULL << s);
452        }
453    }
454
455    // Remove from the mask queued buffers for which acquire has been called,
456    // since the consumer will not receive their buffer addresses and so must
457    // retain their cached information
458    BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
459    while (current != mCore->mQueue.end()) {
460        if (current->mAcquireCalled) {
461            mask &= ~(1ULL << current->mSlot);
462        }
463        ++current;
464    }
465
466    BQ_LOGV("getReleasedBuffers: returning mask %#" PRIx64, mask);
467    *outSlotMask = mask;
468    return NO_ERROR;
469}
470
471status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
472        uint32_t height) {
473    ATRACE_CALL();
474
475    if (width == 0 || height == 0) {
476        BQ_LOGV("setDefaultBufferSize: dimensions cannot be 0 (width=%u "
477                "height=%u)", width, height);
478        return BAD_VALUE;
479    }
480
481    BQ_LOGV("setDefaultBufferSize: width=%u height=%u", width, height);
482
483    Mutex::Autolock lock(mCore->mMutex);
484    mCore->mDefaultWidth = width;
485    mCore->mDefaultHeight = height;
486    return NO_ERROR;
487}
488
489status_t BufferQueueConsumer::setDefaultMaxBufferCount(int bufferCount) {
490    ATRACE_CALL();
491    Mutex::Autolock lock(mCore->mMutex);
492    return mCore->setDefaultMaxBufferCountLocked(bufferCount);
493}
494
495status_t BufferQueueConsumer::disableAsyncBuffer() {
496    ATRACE_CALL();
497
498    Mutex::Autolock lock(mCore->mMutex);
499
500    if (mCore->mConsumerListener != NULL) {
501        BQ_LOGE("disableAsyncBuffer: consumer already connected");
502        return INVALID_OPERATION;
503    }
504
505    BQ_LOGV("disableAsyncBuffer");
506    mCore->mUseAsyncBuffer = false;
507    return NO_ERROR;
508}
509
510status_t BufferQueueConsumer::setMaxAcquiredBufferCount(
511        int maxAcquiredBuffers) {
512    ATRACE_CALL();
513
514    if (maxAcquiredBuffers < 1 ||
515            maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
516        BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d",
517                maxAcquiredBuffers);
518        return BAD_VALUE;
519    }
520
521    Mutex::Autolock lock(mCore->mMutex);
522
523    if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
524        BQ_LOGE("setMaxAcquiredBufferCount: producer is already connected");
525        return INVALID_OPERATION;
526    }
527
528    BQ_LOGV("setMaxAcquiredBufferCount: %d", maxAcquiredBuffers);
529    mCore->mMaxAcquiredBufferCount = maxAcquiredBuffers;
530    return NO_ERROR;
531}
532
533void BufferQueueConsumer::setConsumerName(const String8& name) {
534    ATRACE_CALL();
535    BQ_LOGV("setConsumerName: '%s'", name.string());
536    Mutex::Autolock lock(mCore->mMutex);
537    mCore->mConsumerName = name;
538    mConsumerName = name;
539}
540
541status_t BufferQueueConsumer::setDefaultBufferFormat(PixelFormat defaultFormat) {
542    ATRACE_CALL();
543    BQ_LOGV("setDefaultBufferFormat: %u", defaultFormat);
544    Mutex::Autolock lock(mCore->mMutex);
545    mCore->mDefaultBufferFormat = defaultFormat;
546    return NO_ERROR;
547}
548
549status_t BufferQueueConsumer::setDefaultBufferDataSpace(
550        android_dataspace defaultDataSpace) {
551    ATRACE_CALL();
552    BQ_LOGV("setDefaultBufferDataSpace: %u", defaultDataSpace);
553    Mutex::Autolock lock(mCore->mMutex);
554    mCore->mDefaultBufferDataSpace = defaultDataSpace;
555    return NO_ERROR;
556}
557
558status_t BufferQueueConsumer::setConsumerUsageBits(uint32_t usage) {
559    ATRACE_CALL();
560    BQ_LOGV("setConsumerUsageBits: %#x", usage);
561    Mutex::Autolock lock(mCore->mMutex);
562    mCore->mConsumerUsageBits = usage;
563    return NO_ERROR;
564}
565
566status_t BufferQueueConsumer::setTransformHint(uint32_t hint) {
567    ATRACE_CALL();
568    BQ_LOGV("setTransformHint: %#x", hint);
569    Mutex::Autolock lock(mCore->mMutex);
570    mCore->mTransformHint = hint;
571    return NO_ERROR;
572}
573
574sp<NativeHandle> BufferQueueConsumer::getSidebandStream() const {
575    return mCore->mSidebandStream;
576}
577
578void BufferQueueConsumer::dump(String8& result, const char* prefix) const {
579    const IPCThreadState* ipc = IPCThreadState::self();
580    const pid_t pid = ipc->getCallingPid();
581    const uid_t uid = ipc->getCallingUid();
582    if ((uid != AID_SHELL)
583            && !PermissionCache::checkPermission(String16(
584            "android.permission.DUMP"), pid, uid)) {
585        result.appendFormat("Permission Denial: can't dump BufferQueueConsumer "
586                "from pid=%d, uid=%d\n", pid, uid);
587    } else {
588        mCore->dump(result, prefix);
589    }
590}
591
592} // namespace android
593