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