MtpServer.cpp revision dda7e2b7378755637f188cca7c5ae854427a28f7
1/* 2 * Copyright (C) 2010 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 <stdio.h> 18#include <stdlib.h> 19#include <sys/types.h> 20#include <sys/ioctl.h> 21#include <sys/stat.h> 22#include <fcntl.h> 23#include <errno.h> 24 25#include <cutils/properties.h> 26 27#include "MtpDebug.h" 28#include "MtpProperty.h" 29#include "MtpServer.h" 30#include "MtpSqliteDatabase.h" 31#include "MtpStorage.h" 32#include "MtpStringBuffer.h" 33 34#include "f_mtp.h" 35 36namespace android { 37 38static const MtpOperationCode kSupportedOperationCodes[] = { 39 MTP_OPERATION_GET_DEVICE_INFO, 40 MTP_OPERATION_OPEN_SESSION, 41 MTP_OPERATION_CLOSE_SESSION, 42 MTP_OPERATION_GET_STORAGE_IDS, 43 MTP_OPERATION_GET_STORAGE_INFO, 44 MTP_OPERATION_GET_NUM_OBJECTS, 45 MTP_OPERATION_GET_OBJECT_HANDLES, 46 MTP_OPERATION_GET_OBJECT_INFO, 47 MTP_OPERATION_GET_OBJECT, 48// MTP_OPERATION_GET_THUMB, 49 MTP_OPERATION_DELETE_OBJECT, 50 MTP_OPERATION_SEND_OBJECT_INFO, 51 MTP_OPERATION_SEND_OBJECT, 52// MTP_OPERATION_INITIATE_CAPTURE, 53// MTP_OPERATION_FORMAT_STORE, 54// MTP_OPERATION_RESET_DEVICE, 55// MTP_OPERATION_SELF_TEST, 56// MTP_OPERATION_SET_OBJECT_PROTECTION, 57// MTP_OPERATION_POWER_DOWN, 58 MTP_OPERATION_GET_DEVICE_PROP_DESC, 59 MTP_OPERATION_GET_DEVICE_PROP_VALUE, 60 MTP_OPERATION_SET_DEVICE_PROP_VALUE, 61 MTP_OPERATION_RESET_DEVICE_PROP_VALUE, 62// MTP_OPERATION_TERMINATE_OPEN_CAPTURE, 63// MTP_OPERATION_MOVE_OBJECT, 64// MTP_OPERATION_COPY_OBJECT, 65// MTP_OPERATION_GET_PARTIAL_OBJECT, 66// MTP_OPERATION_INITIATE_OPEN_CAPTURE, 67 MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED, 68// MTP_OPERATION_GET_OBJECT_PROP_DESC, 69 MTP_OPERATION_GET_OBJECT_PROP_VALUE, 70 MTP_OPERATION_SET_OBJECT_PROP_VALUE, 71// MTP_OPERATION_GET_OBJECT_REFERENCES, 72// MTP_OPERATION_SET_OBJECT_REFERENCES, 73// MTP_OPERATION_SKIP, 74}; 75 76static const MtpObjectProperty kSupportedObjectProperties[] = { 77 MTP_PROPERTY_STORAGE_ID, 78 MTP_PROPERTY_OBJECT_FORMAT, 79 MTP_PROPERTY_OBJECT_SIZE, 80 MTP_PROPERTY_OBJECT_FILE_NAME, 81 MTP_PROPERTY_PARENT_OBJECT, 82}; 83 84static const MtpObjectFormat kSupportedPlaybackFormats[] = { 85 // MTP_FORMAT_UNDEFINED, 86 MTP_FORMAT_ASSOCIATION, 87 // MTP_FORMAT_TEXT, 88 // MTP_FORMAT_HTML, 89 MTP_FORMAT_MP3, 90 //MTP_FORMAT_AVI, 91 MTP_FORMAT_MPEG, 92 // MTP_FORMAT_ASF, 93 MTP_FORMAT_EXIF_JPEG, 94 MTP_FORMAT_TIFF_EP, 95 // MTP_FORMAT_BMP, 96 MTP_FORMAT_GIF, 97 MTP_FORMAT_JFIF, 98 MTP_FORMAT_PNG, 99 MTP_FORMAT_TIFF, 100 MTP_FORMAT_WMA, 101 MTP_FORMAT_OGG, 102 MTP_FORMAT_AAC, 103 // MTP_FORMAT_FLAC, 104 // MTP_FORMAT_WMV, 105 MTP_FORMAT_MP4_CONTAINER, 106 MTP_FORMAT_MP2, 107 MTP_FORMAT_3GP_CONTAINER, 108 // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM, 109 // MTP_FORMAT_ABSTRACT_AV_PLAYLIST, 110 // MTP_FORMAT_WPL_PLAYLIST, 111 // MTP_FORMAT_M3U_PLAYLIST, 112 // MTP_FORMAT_MPL_PLAYLIST, 113 // MTP_FORMAT_PLS_PLAYLIST, 114}; 115 116MtpServer::MtpServer(int fd, const char* databasePath, 117 int fileGroup, int filePerm, int directoryPerm) 118 : mFD(fd), 119 mDatabasePath(databasePath), 120 mDatabase(NULL), 121 mFileGroup(fileGroup), 122 mFilePermission(filePerm), 123 mDirectoryPermission(directoryPerm), 124 mSessionID(0), 125 mSessionOpen(false), 126 mSendObjectHandle(kInvalidObjectHandle), 127 mSendObjectFileSize(0) 128{ 129 mDatabase = new MtpSqliteDatabase(); 130 mDatabase->open(databasePath, true); 131 132 initObjectProperties(); 133} 134 135MtpServer::~MtpServer() { 136} 137 138void MtpServer::addStorage(const char* filePath) { 139 int index = mStorages.size() + 1; 140 index |= index << 16; // set high and low part to our index 141 MtpStorage* storage = new MtpStorage(index, filePath, mDatabase); 142 addStorage(storage); 143} 144 145MtpStorage* MtpServer::getStorage(MtpStorageID id) { 146 for (int i = 0; i < mStorages.size(); i++) { 147 MtpStorage* storage = mStorages[i]; 148 if (storage->getStorageID() == id) 149 return storage; 150 } 151 return NULL; 152} 153 154void MtpServer::scanStorage() { 155 for (int i = 0; i < mStorages.size(); i++) { 156 MtpStorage* storage = mStorages[i]; 157 storage->scanFiles(); 158 } 159} 160 161void MtpServer::run() { 162 int fd = mFD; 163 164 LOGV("MtpServer::run fd: %d\n", fd); 165 166 while (1) { 167 int ret = mRequest.read(fd); 168 if (ret < 0) { 169 LOGE("request read returned %d, errno: %d", ret, errno); 170 if (errno == ECANCELED) { 171 // return to top of loop and wait for next command 172 continue; 173 } 174 break; 175 } 176 MtpOperationCode operation = mRequest.getOperationCode(); 177 MtpTransactionID transaction = mRequest.getTransactionID(); 178 179 LOGV("operation: %s", MtpDebug::getOperationCodeName(operation)); 180 mRequest.dump(); 181 182 // FIXME need to generalize this 183 bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO); 184 if (dataIn) { 185 int ret = mData.read(fd); 186 if (ret < 0) { 187 LOGE("data read returned %d, errno: %d", ret, errno); 188 if (errno == ECANCELED) { 189 // return to top of loop and wait for next command 190 continue; 191 } 192 break; 193 } 194 LOGV("received data:"); 195 mData.dump(); 196 } else { 197 mData.reset(); 198 } 199 200 if (handleRequest()) { 201 if (!dataIn && mData.hasData()) { 202 mData.setOperationCode(operation); 203 mData.setTransactionID(transaction); 204 LOGV("sending data:"); 205 mData.dump(); 206 ret = mData.write(fd); 207 if (ret < 0) { 208 LOGE("request write returned %d, errno: %d", ret, errno); 209 if (errno == ECANCELED) { 210 // return to top of loop and wait for next command 211 continue; 212 } 213 break; 214 } 215 } 216 217 mResponse.setTransactionID(transaction); 218 LOGV("sending response %04X", mResponse.getResponseCode()); 219 ret = mResponse.write(fd); 220 if (ret < 0) { 221 LOGE("request write returned %d, errno: %d", ret, errno); 222 if (errno == ECANCELED) { 223 // return to top of loop and wait for next command 224 continue; 225 } 226 break; 227 } 228 } else { 229 LOGV("skipping response\n"); 230 } 231 } 232} 233 234MtpProperty* MtpServer::getObjectProperty(MtpPropertyCode propCode) { 235 for (int i = 0; i < mObjectProperties.size(); i++) { 236 MtpProperty* property = mObjectProperties[i]; 237 if (property->getPropertyCode() == propCode) 238 return property; 239 } 240 return NULL; 241} 242 243MtpProperty* MtpServer::getDeviceProperty(MtpPropertyCode propCode) { 244 for (int i = 0; i < mDeviceProperties.size(); i++) { 245 MtpProperty* property = mDeviceProperties[i]; 246 if (property->getPropertyCode() == propCode) 247 return property; 248 } 249 return NULL; 250} 251 252void MtpServer::initObjectProperties() { 253 mObjectProperties.push(new MtpProperty(MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT16)); 254 mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16)); 255 mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64)); 256 mObjectProperties.push(new MtpProperty(MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR)); 257 mObjectProperties.push(new MtpProperty(MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32)); 258} 259 260bool MtpServer::handleRequest() { 261 MtpOperationCode operation = mRequest.getOperationCode(); 262 MtpResponseCode response; 263 264 mResponse.reset(); 265 266 if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) { 267 // FIXME - need to delete mSendObjectHandle from the database 268 LOGE("expected SendObject after SendObjectInfo"); 269 mSendObjectHandle = kInvalidObjectHandle; 270 } 271 272 switch (operation) { 273 case MTP_OPERATION_GET_DEVICE_INFO: 274 response = doGetDeviceInfo(); 275 break; 276 case MTP_OPERATION_OPEN_SESSION: 277 response = doOpenSession(); 278 break; 279 case MTP_OPERATION_CLOSE_SESSION: 280 response = doCloseSession(); 281 break; 282 case MTP_OPERATION_GET_STORAGE_IDS: 283 response = doGetStorageIDs(); 284 break; 285 case MTP_OPERATION_GET_STORAGE_INFO: 286 response = doGetStorageInfo(); 287 break; 288 case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED: 289 response = doGetObjectPropsSupported(); 290 break; 291 case MTP_OPERATION_GET_OBJECT_HANDLES: 292 response = doGetObjectHandles(); 293 break; 294 case MTP_OPERATION_GET_OBJECT_PROP_VALUE: 295 response = doGetObjectPropValue(); 296 break; 297 case MTP_OPERATION_GET_OBJECT_INFO: 298 response = doGetObjectInfo(); 299 break; 300 case MTP_OPERATION_GET_OBJECT: 301 response = doGetObject(); 302 break; 303 case MTP_OPERATION_SEND_OBJECT_INFO: 304 response = doSendObjectInfo(); 305 break; 306 case MTP_OPERATION_SEND_OBJECT: 307 response = doSendObject(); 308 break; 309 case MTP_OPERATION_DELETE_OBJECT: 310 response = doDeleteObject(); 311 break; 312 case MTP_OPERATION_GET_OBJECT_PROP_DESC: 313 response = doGetObjectPropDesc(); 314 break; 315 default: 316 response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; 317 break; 318 } 319 320 if (response == MTP_RESPONSE_TRANSACTION_CANCELLED) 321 return false; 322 mResponse.setResponseCode(response); 323 return true; 324} 325 326MtpResponseCode MtpServer::doGetDeviceInfo() { 327 MtpStringBuffer string; 328 char prop_value[PROPERTY_VALUE_MAX]; 329 330 // fill in device info 331 mData.putUInt16(MTP_STANDARD_VERSION); 332 mData.putUInt32(6); // MTP Vendor Extension ID 333 mData.putUInt16(MTP_STANDARD_VERSION); 334 string.set("microsoft.com: 1.0;"); 335 mData.putString(string); // MTP Extensions 336 mData.putUInt16(0); //Functional Mode 337 mData.putAUInt16(kSupportedOperationCodes, 338 sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported 339 mData.putEmptyArray(); // Events Supported 340 mData.putEmptyArray(); // Device Properties Supported 341 mData.putEmptyArray(); // Capture Formats 342 mData.putAUInt16(kSupportedPlaybackFormats, 343 sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats 344 // FIXME 345 string.set("Google, Inc."); 346 mData.putString(string); // Manufacturer 347 348 property_get("ro.product.model", prop_value, "MTP Device"); 349 string.set(prop_value); 350 mData.putString(string); // Model 351 string.set("1.0"); 352 mData.putString(string); // Device Version 353 354 property_get("ro.serialno", prop_value, "????????"); 355 string.set(prop_value); 356 mData.putString(string); // Serial Number 357 358 return MTP_RESPONSE_OK; 359} 360 361MtpResponseCode MtpServer::doOpenSession() { 362 if (mSessionOpen) { 363 mResponse.setParameter(1, mSessionID); 364 return MTP_RESPONSE_SESSION_ALREADY_OPEN; 365 } 366 mSessionID = mRequest.getParameter(1); 367 mSessionOpen = true; 368 return MTP_RESPONSE_OK; 369} 370 371MtpResponseCode MtpServer::doCloseSession() { 372 if (!mSessionOpen) 373 return MTP_RESPONSE_SESSION_NOT_OPEN; 374 mSessionID = 0; 375 mSessionOpen = false; 376 return MTP_RESPONSE_OK; 377} 378 379MtpResponseCode MtpServer::doGetStorageIDs() { 380 if (!mSessionOpen) 381 return MTP_RESPONSE_SESSION_NOT_OPEN; 382 383 int count = mStorages.size(); 384 mData.putUInt32(count); 385 for (int i = 0; i < count; i++) 386 mData.putUInt32(mStorages[i]->getStorageID()); 387 388 return MTP_RESPONSE_OK; 389} 390 391MtpResponseCode MtpServer::doGetStorageInfo() { 392 MtpStringBuffer string; 393 394 if (!mSessionOpen) 395 return MTP_RESPONSE_SESSION_NOT_OPEN; 396 MtpStorageID id = mRequest.getParameter(1); 397 MtpStorage* storage = getStorage(id); 398 if (!storage) 399 return MTP_RESPONSE_INVALID_STORAGE_ID; 400 401 mData.putUInt16(storage->getType()); 402 mData.putUInt16(storage->getFileSystemType()); 403 mData.putUInt16(storage->getAccessCapability()); 404 mData.putUInt64(storage->getMaxCapacity()); 405 mData.putUInt64(storage->getFreeSpace()); 406 mData.putUInt32(1024*1024*1024); // Free Space in Objects 407 string.set(storage->getDescription()); 408 mData.putString(string); 409 mData.putEmptyString(); // Volume Identifier 410 411 return MTP_RESPONSE_OK; 412} 413 414MtpResponseCode MtpServer::doGetObjectPropsSupported() { 415 if (!mSessionOpen) 416 return MTP_RESPONSE_SESSION_NOT_OPEN; 417 MtpObjectFormat format = mRequest.getParameter(1); 418 mData.putAUInt16(kSupportedObjectProperties, 419 sizeof(kSupportedObjectProperties) / sizeof(uint16_t)); 420 return MTP_RESPONSE_OK; 421} 422 423MtpResponseCode MtpServer::doGetObjectHandles() { 424 if (!mSessionOpen) 425 return MTP_RESPONSE_SESSION_NOT_OPEN; 426 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage 427 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats 428 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent 429 // 0x00000000 for all objects? 430 431 MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); 432 mData.putAUInt32(handles); 433 delete handles; 434 return MTP_RESPONSE_OK; 435} 436 437MtpResponseCode MtpServer::doGetObjectPropValue() { 438 MtpObjectHandle handle = mRequest.getParameter(1); 439 MtpObjectProperty property = mRequest.getParameter(2); 440 441 return mDatabase->getObjectProperty(handle, property, mData); 442} 443 444MtpResponseCode MtpServer::doGetObjectInfo() { 445 MtpObjectHandle handle = mRequest.getParameter(1); 446 return mDatabase->getObjectInfo(handle, mData); 447} 448 449MtpResponseCode MtpServer::doGetObject() { 450 MtpObjectHandle handle = mRequest.getParameter(1); 451 MtpString pathBuf; 452 int64_t fileLength; 453 if (!mDatabase->getObjectFilePath(handle, pathBuf, fileLength)) 454 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 455 const char* filePath = (const char *)pathBuf; 456 457 mtp_file_range mfr; 458 mfr.fd = open(filePath, O_RDONLY); 459 if (mfr.fd < 0) { 460 return MTP_RESPONSE_GENERAL_ERROR; 461 } 462 mfr.offset = 0; 463 mfr.length = fileLength; 464 465 // send data header 466 mData.setOperationCode(mRequest.getOperationCode()); 467 mData.setTransactionID(mRequest.getTransactionID()); 468 mData.writeDataHeader(mFD, fileLength); 469 470 // then transfer the file 471 int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr); 472 close(mfr.fd); 473 if (ret < 0) { 474 if (errno == ECANCELED) 475 return MTP_RESPONSE_TRANSACTION_CANCELLED; 476 else 477 return MTP_RESPONSE_GENERAL_ERROR; 478 } 479 return MTP_RESPONSE_OK; 480} 481 482MtpResponseCode MtpServer::doSendObjectInfo() { 483 MtpString path; 484 MtpStorageID storageID = mRequest.getParameter(1); 485 MtpStorage* storage = getStorage(storageID); 486 MtpObjectHandle parent = mRequest.getParameter(2); 487 if (!storage) 488 return MTP_RESPONSE_INVALID_STORAGE_ID; 489 490 // special case the root 491 if (parent == MTP_PARENT_ROOT) 492 path = storage->getPath(); 493 else { 494 int64_t dummy; 495 if (!mDatabase->getObjectFilePath(parent, path, dummy)) 496 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 497 } 498 499 // read only the fields we need 500 mData.getUInt32(); // storage ID 501 MtpObjectFormat format = mData.getUInt16(); 502 mData.getUInt16(); // protection status 503 mSendObjectFileSize = mData.getUInt32(); 504 mData.getUInt16(); // thumb format 505 mData.getUInt32(); // thumb compressed size 506 mData.getUInt32(); // thumb pix width 507 mData.getUInt32(); // thumb pix height 508 mData.getUInt32(); // image pix width 509 mData.getUInt32(); // image pix height 510 mData.getUInt32(); // image bit depth 511 mData.getUInt32(); // parent 512 uint16_t associationType = mData.getUInt16(); 513 uint32_t associationDesc = mData.getUInt32(); // association desc 514 mData.getUInt32(); // sequence number 515 MtpStringBuffer name, created, modified; 516 mData.getString(name); // file name 517 mData.getString(created); // date created 518 mData.getString(modified); // date modified 519 // keywords follow 520 521 time_t modifiedTime; 522 if (!parseDateTime(modified, modifiedTime)) 523 modifiedTime = 0; 524 525 if (path[path.size() - 1] != '/') 526 path += "/"; 527 path += (const char *)name; 528 529 mDatabase->beginTransaction(); 530 MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID, 531 mSendObjectFileSize, modifiedTime); 532 if (handle == kInvalidObjectHandle) { 533 mDatabase->rollbackTransaction(); 534 return MTP_RESPONSE_GENERAL_ERROR; 535 } 536 mDatabase->commitTransaction(); 537 538 if (format == MTP_FORMAT_ASSOCIATION) { 539 mode_t mask = umask(0); 540 int ret = mkdir((const char *)path, mDirectoryPermission); 541 umask(mask); 542 if (ret && ret != -EEXIST) 543 return MTP_RESPONSE_GENERAL_ERROR; 544 chown((const char *)path, getuid(), mFileGroup); 545 } else { 546 mSendObjectFilePath = path; 547 // save the handle for the SendObject call, which should follow 548 mSendObjectHandle = handle; 549 } 550 551 mResponse.setParameter(1, storageID); 552 mResponse.setParameter(2, parent); 553 mResponse.setParameter(3, handle); 554 555 return MTP_RESPONSE_OK; 556} 557 558MtpResponseCode MtpServer::doSendObject() { 559 if (mSendObjectHandle == kInvalidObjectHandle) { 560 LOGE("Expected SendObjectInfo before SendObject"); 561 return MTP_RESPONSE_NO_VALID_OBJECT_INFO; 562 } 563 564 // read the header 565 int ret = mData.readDataHeader(mFD); 566 // FIXME - check for errors here. 567 568 // reset so we don't attempt to send this back 569 mData.reset(); 570 571 mtp_file_range mfr; 572 mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC); 573 if (mfr.fd < 0) { 574 return MTP_RESPONSE_GENERAL_ERROR; 575 } 576 fchown(mfr.fd, getuid(), mFileGroup); 577 // set permissions 578 mode_t mask = umask(0); 579 fchmod(mfr.fd, mFilePermission); 580 umask(mask); 581 582 mfr.offset = 0; 583 mfr.length = mSendObjectFileSize; 584 585 // transfer the file 586 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr); 587 close(mfr.fd); 588 589 // FIXME - we need to delete mSendObjectHandle from the database if this fails. 590 LOGV("MTP_RECEIVE_FILE returned %d", ret); 591 mSendObjectHandle = kInvalidObjectHandle; 592 593 if (ret < 0) { 594 unlink(mSendObjectFilePath); 595 if (errno == ECANCELED) 596 return MTP_RESPONSE_TRANSACTION_CANCELLED; 597 else 598 return MTP_RESPONSE_GENERAL_ERROR; 599 } 600 return MTP_RESPONSE_OK; 601} 602 603MtpResponseCode MtpServer::doDeleteObject() { 604 MtpObjectHandle handle = mRequest.getParameter(1); 605 MtpObjectFormat format = mRequest.getParameter(1); 606 // FIXME - support deleting all objects if handle is 0xFFFFFFFF 607 // FIXME - implement deleting objects by format 608 // FIXME - handle non-empty directories 609 610 MtpString filePath; 611 int64_t fileLength; 612 if (!mDatabase->getObjectFilePath(handle, filePath, fileLength)) 613 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 614 615 LOGV("deleting %s", (const char *)filePath); 616 // one of these should work 617 rmdir((const char *)filePath); 618 unlink((const char *)filePath); 619 620 mDatabase->deleteFile(handle); 621 622 return MTP_RESPONSE_OK; 623} 624 625MtpResponseCode MtpServer::doGetObjectPropDesc() { 626 MtpObjectProperty propCode = mRequest.getParameter(1); 627 MtpObjectFormat format = mRequest.getParameter(2); 628 MtpProperty* property = getObjectProperty(propCode); 629 if (!property) 630 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; 631 632 property->write(mData); 633 return MTP_RESPONSE_OK; 634} 635 636} // namespace android 637