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