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