954c267725
Now the file copy is done completely within the media process rather than pushing data to the client via ContProvider.openFile(). File system writes are now interleaved with USB reads, which allows us to copy the data faster and prevents the camera from timing out during transfer. File is automatically inserted in the media provider after a successful import and a Uri is returned to the client. BUG: 2994234 Change-Id: Ie75c63da76f623343d3d966c6a707aa1ae871972 Signed-off-by: Mike Lockwood <lockwood@android.com>
490 lines
14 KiB
C++
490 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "MtpDevice"
|
|
|
|
#include "MtpDebug.h"
|
|
#include "MtpDevice.h"
|
|
#include "MtpDeviceInfo.h"
|
|
#include "MtpObjectInfo.h"
|
|
#include "MtpProperty.h"
|
|
#include "MtpStorageInfo.h"
|
|
#include "MtpStringBuffer.h"
|
|
#include "MtpUtils.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <endian.h>
|
|
|
|
#include <usbhost/usbhost.h>
|
|
|
|
namespace android {
|
|
|
|
MtpDevice::MtpDevice(struct usb_device* device, int interface,
|
|
struct usb_endpoint *ep_in, struct usb_endpoint *ep_out,
|
|
struct usb_endpoint *ep_intr)
|
|
: mDevice(device),
|
|
mInterface(interface),
|
|
mEndpointIn(ep_in),
|
|
mEndpointOut(ep_out),
|
|
mEndpointIntr(ep_intr),
|
|
mDeviceInfo(NULL),
|
|
mID(usb_device_get_unique_id(device)),
|
|
mSessionID(0),
|
|
mTransactionID(0)
|
|
{
|
|
}
|
|
|
|
MtpDevice::~MtpDevice() {
|
|
close();
|
|
for (int i = 0; i < mDeviceProperties.size(); i++)
|
|
delete mDeviceProperties[i];
|
|
}
|
|
|
|
void MtpDevice::initialize() {
|
|
openSession();
|
|
mDeviceInfo = getDeviceInfo();
|
|
if (mDeviceInfo) {
|
|
mDeviceInfo->print();
|
|
|
|
if (mDeviceInfo->mDeviceProperties) {
|
|
int count = mDeviceInfo->mDeviceProperties->size();
|
|
for (int i = 0; i < count; i++) {
|
|
MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i];
|
|
MtpProperty* property = getDevicePropDesc(propCode);
|
|
if (property) {
|
|
property->print();
|
|
mDeviceProperties.push(property);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MtpDevice::close() {
|
|
if (mDevice) {
|
|
usb_device_release_interface(mDevice, mInterface);
|
|
usb_device_close(mDevice);
|
|
mDevice = NULL;
|
|
}
|
|
}
|
|
|
|
const char* MtpDevice::getDeviceName() {
|
|
if (mDevice)
|
|
return usb_device_get_name(mDevice);
|
|
else
|
|
return "???";
|
|
}
|
|
|
|
bool MtpDevice::openSession() {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mSessionID = 0;
|
|
mTransactionID = 0;
|
|
MtpSessionID newSession = 1;
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, newSession);
|
|
if (!sendRequest(MTP_OPERATION_OPEN_SESSION))
|
|
return false;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN)
|
|
newSession = mResponse.getParameter(1);
|
|
else if (ret != MTP_RESPONSE_OK)
|
|
return false;
|
|
|
|
mSessionID = newSession;
|
|
mTransactionID = 1;
|
|
return true;
|
|
}
|
|
|
|
bool MtpDevice::closeSession() {
|
|
// FIXME
|
|
return true;
|
|
}
|
|
|
|
MtpDeviceInfo* MtpDevice::getDeviceInfo() {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
MtpDeviceInfo* info = new MtpDeviceInfo;
|
|
info->read(mData);
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MtpStorageIDList* MtpDevice::getStorageIDs() {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
return mData.getAUInt32();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, storageID);
|
|
if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
MtpStorageInfo* info = new MtpStorageInfo(storageID);
|
|
info->read(mData);
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID,
|
|
MtpObjectFormat format, MtpObjectHandle parent) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, storageID);
|
|
mRequest.setParameter(2, format);
|
|
mRequest.setParameter(3, parent);
|
|
if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
return mData.getAUInt32();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
// FIXME - we might want to add some caching here
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, handle);
|
|
if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
MtpObjectInfo* info = new MtpObjectInfo(handle);
|
|
info->read(mData);
|
|
return info;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, handle);
|
|
if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) {
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
return mData.getData(outLength);
|
|
}
|
|
}
|
|
outLength = 0;
|
|
return NULL;
|
|
}
|
|
|
|
MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
MtpObjectHandle parent = info->mParent;
|
|
if (parent == 0)
|
|
parent = MTP_PARENT_ROOT;
|
|
|
|
mRequest.setParameter(1, info->mStorageID);
|
|
mRequest.setParameter(2, info->mParent);
|
|
|
|
mData.putUInt32(info->mStorageID);
|
|
mData.putUInt16(info->mFormat);
|
|
mData.putUInt16(info->mProtectionStatus);
|
|
mData.putUInt32(info->mCompressedSize);
|
|
mData.putUInt16(info->mThumbFormat);
|
|
mData.putUInt32(info->mThumbCompressedSize);
|
|
mData.putUInt32(info->mThumbPixWidth);
|
|
mData.putUInt32(info->mThumbPixHeight);
|
|
mData.putUInt32(info->mImagePixWidth);
|
|
mData.putUInt32(info->mImagePixHeight);
|
|
mData.putUInt32(info->mImagePixDepth);
|
|
mData.putUInt32(info->mParent);
|
|
mData.putUInt16(info->mAssociationType);
|
|
mData.putUInt32(info->mAssociationDesc);
|
|
mData.putUInt32(info->mSequenceNumber);
|
|
mData.putString(info->mName);
|
|
|
|
char created[100], modified[100];
|
|
formatDateTime(info->mDateCreated, created, sizeof(created));
|
|
formatDateTime(info->mDateModified, modified, sizeof(modified));
|
|
|
|
mData.putString(created);
|
|
mData.putString(modified);
|
|
if (info->mKeywords)
|
|
mData.putString(info->mKeywords);
|
|
else
|
|
mData.putEmptyString();
|
|
|
|
if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) {
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
info->mStorageID = mResponse.getParameter(1);
|
|
info->mParent = mResponse.getParameter(2);
|
|
info->mHandle = mResponse.getParameter(3);
|
|
return info->mHandle;
|
|
}
|
|
}
|
|
return (MtpObjectHandle)-1;
|
|
}
|
|
|
|
bool MtpDevice::sendObject(MtpObjectInfo* info, int srcFD) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
int remaining = info->mCompressedSize;
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, info->mHandle);
|
|
if (sendRequest(MTP_OPERATION_SEND_OBJECT)) {
|
|
// send data header
|
|
writeDataHeader(MTP_OPERATION_SEND_OBJECT, remaining);
|
|
|
|
char buffer[65536];
|
|
while (remaining > 0) {
|
|
int count = read(srcFD, buffer, sizeof(buffer));
|
|
if (count > 0) {
|
|
int written = mData.write(mEndpointOut, buffer, count);
|
|
// FIXME check error
|
|
remaining -= count;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
MtpResponseCode ret = readResponse();
|
|
return (remaining == 0 && ret == MTP_RESPONSE_OK);
|
|
}
|
|
|
|
bool MtpDevice::deleteObject(MtpObjectHandle handle) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, handle);
|
|
if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) {
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) {
|
|
MtpObjectInfo* info = getObjectInfo(handle);
|
|
if (info)
|
|
return info->mParent;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) {
|
|
MtpObjectInfo* info = getObjectInfo(handle);
|
|
if (info)
|
|
return info->mStorageID;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) {
|
|
Mutex::Autolock autoLock(mMutex);
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, code);
|
|
if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC))
|
|
return NULL;
|
|
if (!readData())
|
|
return NULL;
|
|
MtpResponseCode ret = readResponse();
|
|
if (ret == MTP_RESPONSE_OK) {
|
|
MtpProperty* property = new MtpProperty;
|
|
property->read(mData);
|
|
return property;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// reads the object's data and writes it to the specified file path
|
|
bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath) {
|
|
LOGD("readObject: %s", destPath);
|
|
int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC);
|
|
if (fd < 0) {
|
|
LOGE("open failed for %s", destPath);
|
|
return false;
|
|
}
|
|
|
|
Mutex::Autolock autoLock(mMutex);
|
|
bool result = false;
|
|
|
|
mRequest.reset();
|
|
mRequest.setParameter(1, handle);
|
|
if (sendRequest(MTP_OPERATION_GET_OBJECT)
|
|
&& mData.readDataHeader(mEndpointIn)) {
|
|
uint32_t length = mData.getContainerLength();
|
|
if (length < MTP_CONTAINER_HEADER_SIZE)
|
|
goto fail;
|
|
length -= MTP_CONTAINER_HEADER_SIZE;
|
|
uint32_t remaining = length;
|
|
|
|
int initialDataLength = 0;
|
|
void* initialData = mData.getData(initialDataLength);
|
|
if (initialData) {
|
|
if (initialDataLength > 0) {
|
|
if (write(fd, initialData, initialDataLength) != initialDataLength)
|
|
goto fail;
|
|
remaining -= initialDataLength;
|
|
}
|
|
free(initialData);
|
|
}
|
|
|
|
// USB reads greater than 16K don't work
|
|
char buffer1[16384], buffer2[16384];
|
|
char* readBuffer = buffer1;
|
|
char* writeBuffer = NULL;
|
|
int writeLength = 0;
|
|
|
|
while (remaining > 0 || writeBuffer) {
|
|
if (remaining > 0) {
|
|
// queue up a read request
|
|
int readSize = (remaining > sizeof(buffer1) ? sizeof(buffer1) : remaining);
|
|
if (mData.readDataAsync(mEndpointIn, readBuffer, readSize)) {
|
|
LOGE("readDataAsync failed");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
readBuffer = NULL;
|
|
}
|
|
|
|
if (writeBuffer) {
|
|
// write previous buffer
|
|
if (write(fd, writeBuffer, writeLength) != writeLength) {
|
|
LOGE("write failed");
|
|
// wait for pending read before failing
|
|
if (readBuffer)
|
|
mData.readDataWait(mEndpointIn);
|
|
goto fail;
|
|
}
|
|
writeBuffer = NULL;
|
|
}
|
|
|
|
// wait for read to complete
|
|
if (readBuffer) {
|
|
int read = mData.readDataWait(mEndpointIn);
|
|
if (read < 0)
|
|
goto fail;
|
|
|
|
writeBuffer = readBuffer;
|
|
writeLength = read;
|
|
remaining -= read;
|
|
readBuffer = (readBuffer == buffer1 ? buffer2 : buffer1);
|
|
}
|
|
}
|
|
|
|
MtpResponseCode response = readResponse();
|
|
if (response == MTP_RESPONSE_OK)
|
|
result = true;
|
|
}
|
|
|
|
fail:
|
|
::close(fd);
|
|
return result;
|
|
}
|
|
|
|
bool MtpDevice::sendRequest(MtpOperationCode operation) {
|
|
LOGV("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation));
|
|
mRequest.setOperationCode(operation);
|
|
if (mTransactionID > 0)
|
|
mRequest.setTransactionID(mTransactionID++);
|
|
int ret = mRequest.write(mEndpointOut);
|
|
mRequest.dump();
|
|
return (ret > 0);
|
|
}
|
|
|
|
bool MtpDevice::sendData() {
|
|
LOGV("sendData\n");
|
|
mData.setOperationCode(mRequest.getOperationCode());
|
|
mData.setTransactionID(mRequest.getTransactionID());
|
|
int ret = mData.write(mEndpointOut);
|
|
mData.dump();
|
|
return (ret > 0);
|
|
}
|
|
|
|
bool MtpDevice::readData() {
|
|
mData.reset();
|
|
int ret = mData.read(mEndpointIn);
|
|
LOGV("readData returned %d\n", ret);
|
|
if (ret >= MTP_CONTAINER_HEADER_SIZE) {
|
|
mData.dump();
|
|
return true;
|
|
}
|
|
else {
|
|
LOGV("readResponse failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MtpDevice::writeDataHeader(MtpOperationCode operation, int dataLength) {
|
|
mData.setOperationCode(operation);
|
|
mData.setTransactionID(mRequest.getTransactionID());
|
|
return (!mData.writeDataHeader(mEndpointOut, dataLength));
|
|
}
|
|
|
|
MtpResponseCode MtpDevice::readResponse() {
|
|
LOGV("readResponse\n");
|
|
int ret = mResponse.read(mEndpointIn);
|
|
if (ret >= MTP_CONTAINER_HEADER_SIZE) {
|
|
mResponse.dump();
|
|
return mResponse.getResponseCode();
|
|
}
|
|
else {
|
|
LOGD("readResponse failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
} // namespace android
|