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