Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 949be32b authored by Mathias Agopian's avatar Mathias Agopian
Browse files

move lock/unlock implementaion outside of Surface into SurfaceTextureClient

This makes ANativeWindow_lock/ANativeWindow_unlockAndPost work
with ANativeWindows implemented by Surface and SurfaceTextureClient.

Also, Surface now inherits directly from SurfaceTextureClient.

Bug: 5003724
Change-Id: I9f285877c7bae9a262e9a7af91c2bae78804b2ef
parent 8d96f196
Loading
Loading
Loading
Loading
+46 −32
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <gui/SurfaceTexture.h>

#include <ui/egl/android_natives.h>
#include <ui/Region.h>

#include <utils/RefBase.h>
#include <utils/threads.h>
@@ -37,29 +38,24 @@ public:

    sp<ISurfaceTexture> getISurfaceTexture() const;

private:
    friend class Surface;
protected:
    SurfaceTextureClient();
    void setISurfaceTexture(const sp<ISurfaceTexture>& surfaceTexture);

private:
    // can't be copied
    SurfaceTextureClient& operator = (const SurfaceTextureClient& rhs);
    SurfaceTextureClient(const SurfaceTextureClient& rhs);
    void init();

    // ANativeWindow hooks
    static int cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer);
    static int lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int perform(ANativeWindow* window, int operation, ...);
    static int query(const ANativeWindow* window, int what, int* value);
    static int queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int setSwapInterval(ANativeWindow* window, int interval);

    int cancelBuffer(ANativeWindowBuffer* buffer);
    int dequeueBuffer(ANativeWindowBuffer** buffer);
    int lockBuffer(ANativeWindowBuffer* buffer);
    int perform(int operation, va_list args);
    int query(int what, int* value) const;
    int queueBuffer(ANativeWindowBuffer* buffer);
    int setSwapInterval(int interval);
    static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer);
    static int hook_lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int hook_perform(ANativeWindow* window, int operation, ...);
    static int hook_query(const ANativeWindow* window, int what, int* value);
    static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int hook_setSwapInterval(ANativeWindow* window, int interval);

    int dispatchConnect(va_list args);
    int dispatchDisconnect(va_list args);
@@ -71,26 +67,38 @@ private:
    int dispatchSetBuffersTimestamp(va_list args);
    int dispatchSetCrop(va_list args);
    int dispatchSetUsage(va_list args);

    int connect(int api);
    int disconnect(int api);
    int setBufferCount(int bufferCount);
    int setBuffersDimensions(int w, int h);
    int setBuffersFormat(int format);
    int setBuffersTransform(int transform);
    int setBuffersTimestamp(int64_t timestamp);
    int setCrop(Rect const* rect);
    int setUsage(uint32_t reqUsage);

    void freeAllBuffers();
    int getSlotFromBufferLocked(android_native_buffer_t* buffer) const;

    int getConnectedApi() const;
    int dispatchLock(va_list args);
    int dispatchUnlockAndPost(va_list args);

protected:
    virtual int cancelBuffer(ANativeWindowBuffer* buffer);
    virtual int dequeueBuffer(ANativeWindowBuffer** buffer);
    virtual int lockBuffer(ANativeWindowBuffer* buffer);
    virtual int perform(int operation, va_list args);
    virtual int query(int what, int* value) const;
    virtual int queueBuffer(ANativeWindowBuffer* buffer);
    virtual int setSwapInterval(int interval);

    virtual int connect(int api);
    virtual int disconnect(int api);
    virtual int setBufferCount(int bufferCount);
    virtual int setBuffersDimensions(int w, int h);
    virtual int setBuffersFormat(int format);
    virtual int setBuffersTransform(int transform);
    virtual int setBuffersTimestamp(int64_t timestamp);
    virtual int setCrop(Rect const* rect);
    virtual int setUsage(uint32_t reqUsage);
    virtual int lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds);
    virtual int unlockAndPost();

    enum { MIN_UNDEQUEUED_BUFFERS = SurfaceTexture::MIN_UNDEQUEUED_BUFFERS };
    enum { NUM_BUFFER_SLOTS = SurfaceTexture::NUM_BUFFER_SLOTS };
    enum { DEFAULT_FORMAT = PIXEL_FORMAT_RGBA_8888 };

private:
    void freeAllBuffers();
    int getSlotFromBufferLocked(android_native_buffer_t* buffer) const;

    // mSurfaceTexture is the interface to the surface texture server. All
    // operations on the surface texture client ultimately translate into
    // interactions with the server using this interface.
@@ -145,6 +153,12 @@ private:
    // variables of SurfaceTexture objects. It must be locked whenever the
    // member variables are accessed.
    mutable Mutex mMutex;

    // must be used from the lock/unlock thread
    sp<GraphicBuffer>           mLockedBuffer;
    sp<GraphicBuffer>           mPostedBuffer;
    mutable Region              mOldDirtyRegion;
    bool                        mConnectedToCpu;
};

}; // namespace android
+6 −63
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@
#include <ui/Region.h>
#include <ui/egl/android_natives.h>

#include <gui/SurfaceTextureClient.h>

#include <surfaceflinger/ISurface.h>
#include <surfaceflinger/ISurfaceComposerClient.h>

@@ -37,14 +39,9 @@ namespace android {

// ---------------------------------------------------------------------------

class GraphicBuffer;
class GraphicBufferMapper;
class IOMX;
class ISurfaceTexture;
class Rect;
class Surface;
class SurfaceComposerClient;
class SurfaceTextureClient;

// ---------------------------------------------------------------------------

@@ -129,8 +126,7 @@ private:
    
// ---------------------------------------------------------------------------

class Surface 
    : public EGLNativeBase<ANativeWindow, Surface, RefBase>
class Surface : public SurfaceTextureClient
{
public:
    struct SurfaceInfo {
@@ -158,32 +154,14 @@ public:
    sp<ISurfaceTexture> getSurfaceTexture();

    // the lock/unlock APIs must be used from the same thread
    status_t    lock(SurfaceInfo* info, bool blocking = true);
    status_t    lock(SurfaceInfo* info, Region* dirty, bool blocking = true);
    status_t    lock(SurfaceInfo* info, Region* dirty = NULL);
    status_t    unlockAndPost();

    sp<IBinder> asBinder() const;

private:
    /*
     * Android frameworks friends
     * (eventually this should go away and be replaced by proper APIs)
     */
    // camera and camcorder need access to the ISurface binder interface for preview
    friend class CameraService;
    friend class MediaRecorder;
    // MediaPlayer needs access to ISurface for display
    friend class MediaPlayer;
    friend class IOMX;
    friend class SoftwareRenderer;
    // this is just to be able to write some unit tests
    friend class Test;
    // videoEditor preview classes
    friend class VideoEditorPreviewController;
    friend class PreviewRenderer;

private:
    friend class SurfaceComposerClient;
    friend class SurfaceControl;

    // can't be copied
@@ -194,62 +172,27 @@ private:
    Surface(const Parcel& data, const sp<IBinder>& ref);
    ~Surface();


    /*
     *  ANativeWindow hooks
     */
    static int setSwapInterval(ANativeWindow* window, int interval);
    static int dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer);
    static int cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int lockBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer);
    static int query(const ANativeWindow* window, int what, int* value);
    static int perform(ANativeWindow* window, int operation, ...);

    int setSwapInterval(int interval);
    int dequeueBuffer(ANativeWindowBuffer** buffer);
    int lockBuffer(ANativeWindowBuffer* buffer);
    int queueBuffer(ANativeWindowBuffer* buffer);
    int cancelBuffer(ANativeWindowBuffer* buffer);
    int query(int what, int* value) const;
    int perform(int operation, va_list args);

    /*
     *  private stuff...
     */
    void init();
    status_t validate(bool inCancelBuffer = false) const;

    int getConnectedApi() const;
    
    static void cleanCachedSurfacesLocked();

    virtual int query(int what, int* value) const;

    // constants
    status_t                    mInitCheck;
    sp<ISurface>                mSurface;
    sp<SurfaceTextureClient>    mSurfaceTextureClient;
    uint32_t                    mIdentity;
    PixelFormat                 mFormat;
    uint32_t                    mFlags;

    // protected by mSurfaceLock. These are also used from lock/unlock
    // but in that case, they must be called form the same thread.
    mutable Region              mDirtyRegion;

    // must be used from the lock/unlock thread
    sp<GraphicBuffer>           mLockedBuffer;
    sp<GraphicBuffer>           mPostedBuffer;
    mutable Region              mOldDirtyRegion;
    bool                        mReserved;

    // query() must be called from dequeueBuffer() thread
    uint32_t                    mWidth;
    uint32_t                    mHeight;

    // Inherently thread-safe
    mutable Mutex               mSurfaceLock;
    mutable Mutex               mApiLock;

    // A cache of Surface objects that have been deserialized into this process.
    static Mutex sCachedSurfacesLock;
    static DefaultKeyedVector<wp<IBinder>, wp<Surface> > sCachedSurfaces;
+24 −264
Original line number Diff line number Diff line
@@ -46,59 +46,6 @@

namespace android {

// ----------------------------------------------------------------------

static status_t copyBlt(
        const sp<GraphicBuffer>& dst, 
        const sp<GraphicBuffer>& src, 
        const Region& reg)
{
    // src and dst with, height and format must be identical. no verification
    // is done here.
    status_t err;
    uint8_t const * src_bits = NULL;
    err = src->lock(GRALLOC_USAGE_SW_READ_OFTEN, reg.bounds(), (void**)&src_bits);
    LOGE_IF(err, "error locking src buffer %s", strerror(-err));

    uint8_t* dst_bits = NULL;
    err = dst->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, reg.bounds(), (void**)&dst_bits);
    LOGE_IF(err, "error locking dst buffer %s", strerror(-err));

    Region::const_iterator head(reg.begin());
    Region::const_iterator tail(reg.end());
    if (head != tail && src_bits && dst_bits) {
        const size_t bpp = bytesPerPixel(src->format);
        const size_t dbpr = dst->stride * bpp;
        const size_t sbpr = src->stride * bpp;

        while (head != tail) {
            const Rect& r(*head++);
            ssize_t h = r.height();
            if (h <= 0) continue;
            size_t size = r.width() * bpp;
            uint8_t const * s = src_bits + (r.left + src->stride * r.top) * bpp;
            uint8_t       * d = dst_bits + (r.left + dst->stride * r.top) * bpp;
            if (dbpr==sbpr && size==sbpr) {
                size *= h;
                h = 1;
            }
            do {
                memcpy(d, s, size);
                d += dbpr;
                s += sbpr;
            } while (--h > 0);
        }
    }
    
    if (src_bits)
        src->unlock();
    
    if (dst_bits)
        dst->unlock();
    
    return err;
}

// ============================================================================
//  SurfaceControl
// ============================================================================
@@ -277,7 +224,8 @@ sp<Surface> SurfaceControl::getSurface() const
// ---------------------------------------------------------------------------

Surface::Surface(const sp<SurfaceControl>& surface)
    : mInitCheck(NO_INIT),
    : SurfaceTextureClient(),
      mInitCheck(NO_INIT),
      mSurface(surface->mSurface),
      mIdentity(surface->mIdentity),
      mFormat(surface->mFormat), mFlags(surface->mFlags),
@@ -287,7 +235,8 @@ Surface::Surface(const sp<SurfaceControl>& surface)
}

Surface::Surface(const Parcel& parcel, const sp<IBinder>& ref)
    : mInitCheck(NO_INIT)
    : SurfaceTextureClient(),
      mInitCheck(NO_INIT)
{
    mSurface    = interface_cast<ISurface>(ref);
    mIdentity   = parcel.readInt32();
@@ -363,36 +312,21 @@ void Surface::cleanCachedSurfacesLocked() {

void Surface::init()
{
    ANativeWindow::setSwapInterval  = setSwapInterval;
    ANativeWindow::dequeueBuffer    = dequeueBuffer;
    ANativeWindow::cancelBuffer     = cancelBuffer;
    ANativeWindow::lockBuffer       = lockBuffer;
    ANativeWindow::queueBuffer      = queueBuffer;
    ANativeWindow::query            = query;
    ANativeWindow::perform          = perform;

    if (mSurface != NULL) {
        sp<ISurfaceTexture> surfaceTexture(mSurface->getSurfaceTexture());
        LOGE_IF(surfaceTexture==0, "got a NULL ISurfaceTexture from ISurface");
        if (surfaceTexture != NULL) {
            mSurfaceTextureClient = new SurfaceTextureClient(surfaceTexture);
            mSurfaceTextureClient->setUsage(GraphicBuffer::USAGE_HW_RENDER);
            setISurfaceTexture(surfaceTexture);
            setUsage(GraphicBuffer::USAGE_HW_RENDER);
        }

        DisplayInfo dinfo;
        SurfaceComposerClient::getDisplayInfo(0, &dinfo);
        const_cast<float&>(ANativeWindow::xdpi) = dinfo.xdpi;
        const_cast<float&>(ANativeWindow::ydpi) = dinfo.ydpi;

        const_cast<int&>(ANativeWindow::minSwapInterval) =
                mSurfaceTextureClient->minSwapInterval;

        const_cast<int&>(ANativeWindow::maxSwapInterval) =
                mSurfaceTextureClient->maxSwapInterval;

        const_cast<uint32_t&>(ANativeWindow::flags) = 0;

        if (mSurfaceTextureClient != 0) {
        if (surfaceTexture != NULL) {
            mInitCheck = NO_ERROR;
        }
    }
@@ -402,7 +336,6 @@ Surface::~Surface()
{
    // clear all references and trigger an IPC now, to make sure things
    // happen without delay, since these resources are quite heavy.
    mSurfaceTextureClient.clear();
    mSurface.clear();
    IPCThreadState::self()->flushCommands();
}
@@ -431,77 +364,6 @@ sp<IBinder> Surface::asBinder() const {

// ----------------------------------------------------------------------------

int Surface::setSwapInterval(ANativeWindow* window, int interval) {
    Surface* self = getSelf(window);
    return self->setSwapInterval(interval);
}

int Surface::dequeueBuffer(ANativeWindow* window, 
        ANativeWindowBuffer** buffer) {
    Surface* self = getSelf(window);
    return self->dequeueBuffer(buffer);
}

int Surface::cancelBuffer(ANativeWindow* window,
        ANativeWindowBuffer* buffer) {
    Surface* self = getSelf(window);
    return self->cancelBuffer(buffer);
}

int Surface::lockBuffer(ANativeWindow* window, 
        ANativeWindowBuffer* buffer) {
    Surface* self = getSelf(window);
    return self->lockBuffer(buffer);
}

int Surface::queueBuffer(ANativeWindow* window, 
        ANativeWindowBuffer* buffer) {
    Surface* self = getSelf(window);
    return self->queueBuffer(buffer);
}

int Surface::query(const ANativeWindow* window,
        int what, int* value) {
    const Surface* self = getSelf(window);
    return self->query(what, value);
}

int Surface::perform(ANativeWindow* window, 
        int operation, ...) {
    va_list args;
    va_start(args, operation);
    Surface* self = getSelf(window);
    int res = self->perform(operation, args);
    va_end(args);
    return res;
}

// ----------------------------------------------------------------------------

int Surface::setSwapInterval(int interval) {
    return mSurfaceTextureClient->setSwapInterval(interval);
}

int Surface::dequeueBuffer(ANativeWindowBuffer** buffer) {
    status_t err = mSurfaceTextureClient->dequeueBuffer(buffer);
    if (err == NO_ERROR) {
        mDirtyRegion.set(buffer[0]->width, buffer[0]->height);
    }
    return err;
}

int Surface::cancelBuffer(ANativeWindowBuffer* buffer) {
    return mSurfaceTextureClient->cancelBuffer(buffer);
}

int Surface::lockBuffer(ANativeWindowBuffer* buffer) {
    return mSurfaceTextureClient->lockBuffer(buffer);
}

int Surface::queueBuffer(ANativeWindowBuffer* buffer) {
    return mSurfaceTextureClient->queueBuffer(buffer);
}

int Surface::query(int what, int* value) const {
    switch (what) {
    case NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER:
@@ -509,141 +371,39 @@ int Surface::query(int what, int* value) const {
        *value = 1;
        return NO_ERROR;
    case NATIVE_WINDOW_CONCRETE_TYPE:
        // TODO: this is not needed anymore
        *value = NATIVE_WINDOW_SURFACE;
        return NO_ERROR;
    }
    return mSurfaceTextureClient->query(what, value);
}

int Surface::perform(int operation, va_list args) {
    return mSurfaceTextureClient->perform(operation, args);
    return SurfaceTextureClient::query(what, value);
}

// ----------------------------------------------------------------------------

int Surface::getConnectedApi() const {
    return mSurfaceTextureClient->getConnectedApi();
}

// ----------------------------------------------------------------------------
status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn) {
    ANativeWindow_Buffer outBuffer;

status_t Surface::lock(SurfaceInfo* info, bool blocking) {
    return Surface::lock(info, NULL, blocking);
    ARect temp;
    ARect* inOutDirtyBounds = NULL;
    if (dirtyIn) {
        temp = dirtyIn->getBounds();
        inOutDirtyBounds = &temp;
    }

status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking) 
{
    if (getConnectedApi()) {
        LOGE("Surface::lock(%p) failed. Already connected to another API",
                (ANativeWindow*)this);
        CallStack stack;
        stack.update();
        stack.dump("");
        return INVALID_OPERATION;
    }

    if (mApiLock.tryLock() != NO_ERROR) {
        LOGE("calling Surface::lock from different threads!");
        CallStack stack;
        stack.update();
        stack.dump("");
        return WOULD_BLOCK;
    }

    /* Here we're holding mApiLock */
    status_t err = SurfaceTextureClient::lock(&outBuffer, inOutDirtyBounds);

    if (mLockedBuffer != 0) {
        LOGE("Surface::lock failed, already locked");
        mApiLock.unlock();
        return INVALID_OPERATION;
    }

    // we're intending to do software rendering from this point
    mSurfaceTextureClient->setUsage(
            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);

    ANativeWindowBuffer* out;
    status_t err = mSurfaceTextureClient->dequeueBuffer(&out);
    LOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));
    if (err == NO_ERROR) {
        sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
        err = mSurfaceTextureClient->lockBuffer(backBuffer.get());
        LOGE_IF(err, "lockBuffer (handle=%p) failed (%s)",
                backBuffer->handle, strerror(-err));
    if (err == NO_ERROR) {
            const Rect bounds(backBuffer->width, backBuffer->height);
            const Region boundsRegion(bounds);
            Region scratch(boundsRegion);
            Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);
            newDirtyRegion &= boundsRegion;

            // figure out if we can copy the frontbuffer back
            const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
            const bool canCopyBack = (frontBuffer != 0 &&
                    backBuffer->width  == frontBuffer->width &&
                    backBuffer->height == frontBuffer->height &&
                    backBuffer->format == frontBuffer->format &&
                    !(mFlags & ISurfaceComposer::eDestroyBackbuffer));

            // the dirty region we report to surfaceflinger is the one
            // given by the user (as opposed to the one *we* return to the
            // user).
            mDirtyRegion = newDirtyRegion;

            if (canCopyBack) {
                // copy the area that is invalid and not repainted this round
                const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
                if (!copyback.isEmpty())
                    copyBlt(backBuffer, frontBuffer, copyback);
            } else {
                // if we can't copy-back anything, modify the user's dirty
                // region to make sure they redraw the whole buffer
                newDirtyRegion = boundsRegion;
            }

            // keep track of the are of the buffer that is "clean"
            // (ie: that will be redrawn)
            mOldDirtyRegion = newDirtyRegion;

            void* vaddr;
            status_t res = backBuffer->lock(
                    GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                    newDirtyRegion.bounds(), &vaddr);
            
            LOGW_IF(res, "failed locking buffer (handle = %p)", 
                    backBuffer->handle);

            mLockedBuffer = backBuffer;
            other->w      = backBuffer->width;
            other->h      = backBuffer->height;
            other->s      = backBuffer->stride;
            other->usage  = backBuffer->usage;
            other->format = backBuffer->format;
            other->bits   = vaddr;
        }
    }
    mApiLock.unlock();
    return err;
        other->w = uint32_t(outBuffer.width);
        other->h = uint32_t(outBuffer.height);
        other->s = uint32_t(outBuffer.stride);
        other->usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN;
        other->format = uint32_t(outBuffer.format);
        other->bits = outBuffer.bits;
    }
    
status_t Surface::unlockAndPost() 
{
    if (mLockedBuffer == 0) {
        LOGE("Surface::unlockAndPost failed, no locked buffer");
        return INVALID_OPERATION;
    return err;
}

    status_t err = mLockedBuffer->unlock();
    LOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
    
    err = mSurfaceTextureClient->queueBuffer(mLockedBuffer.get());
    LOGE_IF(err, "queueBuffer (handle=%p) failed (%s)",
            mLockedBuffer->handle, strerror(-err));

    mPostedBuffer = mLockedBuffer;
    mLockedBuffer = 0;
    return err;
status_t Surface::unlockAndPost() {
    return SurfaceTextureClient::unlockAndPost();
}

// ----------------------------------------------------------------------------
+6 −2
Original line number Diff line number Diff line
@@ -495,7 +495,7 @@ status_t SurfaceTexture::setTransform(uint32_t transform) {
}

status_t SurfaceTexture::connect(int api) {
    LOGV("SurfaceTexture::connect");
    LOGV("SurfaceTexture::connect(this=%p, %d)", this, api);
    Mutex::Autolock lock(mMutex);
    int err = NO_ERROR;
    switch (api) {
@@ -504,6 +504,8 @@ status_t SurfaceTexture::connect(int api) {
        case NATIVE_WINDOW_API_MEDIA:
        case NATIVE_WINDOW_API_CAMERA:
            if (mConnectedApi != NO_CONNECTED_API) {
                LOGE("connect: already connected (cur=%d, req=%d)",
                        mConnectedApi, api);
                err = -EINVAL;
            } else {
                mConnectedApi = api;
@@ -517,7 +519,7 @@ status_t SurfaceTexture::connect(int api) {
}

status_t SurfaceTexture::disconnect(int api) {
    LOGV("SurfaceTexture::disconnect");
    LOGV("SurfaceTexture::disconnect(this=%p, %d)", this, api);
    Mutex::Autolock lock(mMutex);
    int err = NO_ERROR;
    switch (api) {
@@ -528,6 +530,8 @@ status_t SurfaceTexture::disconnect(int api) {
            if (mConnectedApi == api) {
                mConnectedApi = NO_CONNECTED_API;
            } else {
                LOGE("disconnect: connected to another api (cur=%d, req=%d)",
                        mConnectedApi, api);
                err = -EINVAL;
            }
            break;
+224 −33

File changed.

Preview size limit exceeded, changes collapsed.

Loading