MtpServer.cpp revision 21ef7d0e70c5ad599bc2602cb484f8cd647055ca
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 "MtpDatabase.h" 29#include "MtpProperty.h" 30#include "MtpServer.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 : mFD(fd), 118 mDatabasePath(databasePath), 119 mDatabase(NULL), 120 mSessionID(0), 121 mSessionOpen(false), 122 mSendObjectHandle(kInvalidObjectHandle), 123 mSendObjectFileSize(0) 124{ 125 mDatabase = new MtpDatabase(); 126 mDatabase->open(databasePath, true); 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 427 MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); 428 mData.putAUInt32(handles); 429 delete handles; 430 return MTP_RESPONSE_OK; 431} 432 433MtpResponseCode MtpServer::doGetObjectPropValue() { 434 MtpObjectHandle handle = mRequest.getParameter(1); 435 MtpObjectProperty property = mRequest.getParameter(2); 436 437 return mDatabase->getObjectProperty(handle, property, mData); 438} 439 440MtpResponseCode MtpServer::doGetObjectInfo() { 441 MtpObjectHandle handle = mRequest.getParameter(1); 442 return mDatabase->getObjectInfo(handle, mData); 443} 444 445MtpResponseCode MtpServer::doGetObject() { 446 MtpObjectHandle handle = mRequest.getParameter(1); 447 MtpString pathBuf; 448 int64_t fileLength; 449 if (!mDatabase->getObjectFilePath(handle, pathBuf, fileLength)) 450 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 451 const char* filePath = (const char *)pathBuf; 452 453 mtp_file_range mfr; 454 mfr.fd = open(filePath, O_RDONLY); 455 if (mfr.fd < 0) { 456 return MTP_RESPONSE_GENERAL_ERROR; 457 } 458 mfr.offset = 0; 459 mfr.length = fileLength; 460 461 // send data header 462 mData.setOperationCode(mRequest.getOperationCode()); 463 mData.setTransactionID(mRequest.getTransactionID()); 464 mData.writeDataHeader(mFD, fileLength); 465 466 // then transfer the file 467 int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr); 468 close(mfr.fd); 469 if (ret < 0) { 470 if (errno == ECANCELED) 471 return MTP_RESPONSE_TRANSACTION_CANCELLED; 472 else 473 return MTP_RESPONSE_GENERAL_ERROR; 474 } 475 return MTP_RESPONSE_OK; 476} 477 478MtpResponseCode MtpServer::doSendObjectInfo() { 479 MtpString path; 480 MtpStorageID storageID = mRequest.getParameter(1); 481 MtpStorage* storage = getStorage(storageID); 482 MtpObjectHandle parent = mRequest.getParameter(2); 483 if (!storage) 484 return MTP_RESPONSE_INVALID_STORAGE_ID; 485 486 // special case the root 487 if (parent == MTP_PARENT_ROOT) 488 path = storage->getPath(); 489 else { 490 int64_t dummy; 491 if (!mDatabase->getObjectFilePath(parent, path, dummy)) 492 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 493 } 494 495 // read only the fields we need 496 mData.getUInt32(); // storage ID 497 MtpObjectFormat format = mData.getUInt16(); 498 mData.getUInt16(); // protection status 499 mSendObjectFileSize = mData.getUInt32(); 500 mData.getUInt16(); // thumb format 501 mData.getUInt32(); // thumb compressed size 502 mData.getUInt32(); // thumb pix width 503 mData.getUInt32(); // thumb pix height 504 mData.getUInt32(); // image pix width 505 mData.getUInt32(); // image pix height 506 mData.getUInt32(); // image bit depth 507 mData.getUInt32(); // parent 508 uint16_t associationType = mData.getUInt16(); 509 uint32_t associationDesc = mData.getUInt32(); // association desc 510 mData.getUInt32(); // sequence number 511 MtpStringBuffer name, created, modified; 512 mData.getString(name); // file name 513 mData.getString(created); // date created 514 mData.getString(modified); // date modified 515 // keywords follow 516 517 time_t modifiedTime; 518 if (!parseDateTime(modified, modifiedTime)) 519 modifiedTime = 0; 520 521 if (path[path.size() - 1] != '/') 522 path += "/"; 523 path += (const char *)name; 524 525 mDatabase->beginTransaction(); 526 MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID, 527 mSendObjectFileSize, modifiedTime); 528 if (handle == kInvalidObjectHandle) { 529 mDatabase->rollbackTransaction(); 530 return MTP_RESPONSE_GENERAL_ERROR; 531 } 532 uint32_t table = MtpDatabase::getTableForFile(format); 533 if (table == kObjectHandleTableAudio) 534 handle = mDatabase->addAudioFile(handle); 535 mDatabase->commitTransaction(); 536 537 if (format == MTP_FORMAT_ASSOCIATION) { 538 mode_t mask = umask(0); 539 int ret = mkdir((const char *)path, S_IRWXU | S_IRWXG | S_IRWXO); 540 umask(mask); 541 if (ret && ret != -EEXIST) 542 return MTP_RESPONSE_GENERAL_ERROR; 543 } else { 544 mSendObjectFilePath = path; 545 // save the handle for the SendObject call, which should follow 546 mSendObjectHandle = handle; 547 } 548 549 mResponse.setParameter(1, storageID); 550 mResponse.setParameter(2, parent); 551 mResponse.setParameter(3, handle); 552 553 return MTP_RESPONSE_OK; 554} 555 556MtpResponseCode MtpServer::doSendObject() { 557 if (mSendObjectHandle == kInvalidObjectHandle) { 558 LOGE("Expected SendObjectInfo before SendObject"); 559 return MTP_RESPONSE_NO_VALID_OBJECT_INFO; 560 } 561 562 // read the header 563 int ret = mData.readDataHeader(mFD); 564 // FIXME - check for errors here. 565 566 // reset so we don't attempt to send this back 567 mData.reset(); 568 569 mtp_file_range mfr; 570 mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC); 571 if (mfr.fd < 0) { 572 return MTP_RESPONSE_GENERAL_ERROR; 573 } 574 mfr.offset = 0; 575 mfr.length = mSendObjectFileSize; 576 577 // transfer the file 578 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr); 579 close(mfr.fd); 580 // FIXME - we need to delete mSendObjectHandle from the database if this fails. 581 LOGV("MTP_RECEIVE_FILE returned %d", ret); 582 mSendObjectHandle = kInvalidObjectHandle; 583 584 if (ret < 0) { 585 unlink(mSendObjectFilePath); 586 if (errno == ECANCELED) 587 return MTP_RESPONSE_TRANSACTION_CANCELLED; 588 else 589 return MTP_RESPONSE_GENERAL_ERROR; 590 } 591 return MTP_RESPONSE_OK; 592} 593 594MtpResponseCode MtpServer::doDeleteObject() { 595 MtpObjectHandle handle = mRequest.getParameter(1); 596 MtpObjectFormat format = mRequest.getParameter(1); 597 // FIXME - support deleting all objects if handle is 0xFFFFFFFF 598 // FIXME - implement deleting objects by format 599 // FIXME - handle non-empty directories 600 601 MtpString filePath; 602 int64_t fileLength; 603 if (!mDatabase->getObjectFilePath(handle, filePath, fileLength)) 604 return MTP_RESPONSE_INVALID_OBJECT_HANDLE; 605 606 LOGV("deleting %s", (const char *)filePath); 607 // one of these should work 608 rmdir((const char *)filePath); 609 unlink((const char *)filePath); 610 611 mDatabase->deleteFile(handle); 612 613 return MTP_RESPONSE_OK; 614} 615 616MtpResponseCode MtpServer::doGetObjectPropDesc() { 617 MtpObjectProperty propCode = mRequest.getParameter(1); 618 MtpObjectFormat format = mRequest.getParameter(2); 619 MtpProperty* property = getObjectProperty(propCode); 620 if (!property) 621 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; 622 623 property->write(mData); 624 return MTP_RESPONSE_OK; 625} 626 627} // namespace android 628