blob: df87db4fe6b47cfd546ac35d02939bfe15f3c44c [file] [log] [blame]
Mike Lockwood16864ba2010-05-11 17:16:59 -04001/*
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>
Mike Lockwoodd3211492010-09-13 17:15:58 -040024#include <sys/stat.h>
25#include <dirent.h>
Mike Lockwood16864ba2010-05-11 17:16:59 -040026
Mike Lockwoodc42aa122010-06-14 17:58:08 -070027#include <cutils/properties.h>
28
Mike Lockwooda881b442010-09-23 22:32:05 -040029#define LOG_TAG "MtpServer"
30
Mike Lockwood16864ba2010-05-11 17:16:59 -040031#include "MtpDebug.h"
Mike Lockwood7f53a192010-07-09 10:45:22 -040032#include "MtpDatabase.h"
Mike Lockwood7d77dcf2011-04-21 17:05:55 -070033#include "MtpObjectInfo.h"
Mike Lockwood21ef7d02010-06-30 17:00:35 -040034#include "MtpProperty.h"
Mike Lockwood16864ba2010-05-11 17:16:59 -040035#include "MtpServer.h"
36#include "MtpStorage.h"
37#include "MtpStringBuffer.h"
Mike Lockwood16864ba2010-05-11 17:16:59 -040038
Mike Lockwood8065e202010-07-15 13:36:52 -040039#include <linux/usb/f_mtp.h>
Mike Lockwood16864ba2010-05-11 17:16:59 -040040
Mike Lockwood7850ef92010-05-14 10:10:36 -040041namespace android {
42
Mike Lockwood16864ba2010-05-11 17:16:59 -040043static const MtpOperationCode kSupportedOperationCodes[] = {
44 MTP_OPERATION_GET_DEVICE_INFO,
45 MTP_OPERATION_OPEN_SESSION,
46 MTP_OPERATION_CLOSE_SESSION,
47 MTP_OPERATION_GET_STORAGE_IDS,
48 MTP_OPERATION_GET_STORAGE_INFO,
49 MTP_OPERATION_GET_NUM_OBJECTS,
50 MTP_OPERATION_GET_OBJECT_HANDLES,
51 MTP_OPERATION_GET_OBJECT_INFO,
52 MTP_OPERATION_GET_OBJECT,
Mike Lockwood64000782011-04-24 18:40:17 -070053 MTP_OPERATION_GET_THUMB,
Mike Lockwood16864ba2010-05-11 17:16:59 -040054 MTP_OPERATION_DELETE_OBJECT,
55 MTP_OPERATION_SEND_OBJECT_INFO,
56 MTP_OPERATION_SEND_OBJECT,
57// MTP_OPERATION_INITIATE_CAPTURE,
58// MTP_OPERATION_FORMAT_STORE,
59// MTP_OPERATION_RESET_DEVICE,
60// MTP_OPERATION_SELF_TEST,
61// MTP_OPERATION_SET_OBJECT_PROTECTION,
62// MTP_OPERATION_POWER_DOWN,
Mike Lockwoode3e76c42010-09-02 14:57:30 -040063 MTP_OPERATION_GET_DEVICE_PROP_DESC,
Mike Lockwood8277cec2010-08-10 15:20:35 -040064 MTP_OPERATION_GET_DEVICE_PROP_VALUE,
65 MTP_OPERATION_SET_DEVICE_PROP_VALUE,
66 MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
Mike Lockwood16864ba2010-05-11 17:16:59 -040067// MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
68// MTP_OPERATION_MOVE_OBJECT,
69// MTP_OPERATION_COPY_OBJECT,
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -050070 MTP_OPERATION_GET_PARTIAL_OBJECT,
Mike Lockwood16864ba2010-05-11 17:16:59 -040071// MTP_OPERATION_INITIATE_OPEN_CAPTURE,
72 MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
Mike Lockwood8277cec2010-08-10 15:20:35 -040073 MTP_OPERATION_GET_OBJECT_PROP_DESC,
Mike Lockwood677f5702010-09-23 23:04:28 -040074 MTP_OPERATION_GET_OBJECT_PROP_VALUE,
75 MTP_OPERATION_SET_OBJECT_PROP_VALUE,
Mike Lockwoodb6da06e2010-10-14 18:03:25 -040076 MTP_OPERATION_GET_OBJECT_PROP_LIST,
77// MTP_OPERATION_SET_OBJECT_PROP_LIST,
78// MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
79// MTP_OPERATION_SEND_OBJECT_PROP_LIST,
Mike Lockwood438344f2010-08-03 15:30:09 -040080 MTP_OPERATION_GET_OBJECT_REFERENCES,
81 MTP_OPERATION_SET_OBJECT_REFERENCES,
Mike Lockwood16864ba2010-05-11 17:16:59 -040082// MTP_OPERATION_SKIP,
Mike Lockwood7d77dcf2011-04-21 17:05:55 -070083 // Android extension for direct file IO
84 MTP_OPERATION_GET_PARTIAL_OBJECT_64,
85 MTP_OPERATION_SEND_PARTIAL_OBJECT,
86 MTP_OPERATION_TRUNCATE_OBJECT,
87 MTP_OPERATION_BEGIN_EDIT_OBJECT,
88 MTP_OPERATION_END_EDIT_OBJECT,
Mike Lockwood16864ba2010-05-11 17:16:59 -040089};
90
Mike Lockwood873871f2010-07-12 18:54:16 -040091static const MtpEventCode kSupportedEventCodes[] = {
92 MTP_EVENT_OBJECT_ADDED,
93 MTP_EVENT_OBJECT_REMOVED,
Mike Lockwooda8494402011-02-18 09:07:14 -050094 MTP_EVENT_STORE_ADDED,
95 MTP_EVENT_STORE_REMOVED,
Mike Lockwood873871f2010-07-12 18:54:16 -040096};
97
Mike Lockwood3d1d7762011-06-21 08:27:06 -040098MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
Mike Lockwood8e2a2802010-07-02 15:15:07 -040099 int fileGroup, int filePerm, int directoryPerm)
Mike Lockwood16864ba2010-05-11 17:16:59 -0400100 : mFD(fd),
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400101 mDatabase(database),
Mike Lockwood3d1d7762011-06-21 08:27:06 -0400102 mPtp(ptp),
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400103 mFileGroup(fileGroup),
104 mFilePermission(filePerm),
105 mDirectoryPermission(directoryPerm),
Mike Lockwood16864ba2010-05-11 17:16:59 -0400106 mSessionID(0),
107 mSessionOpen(false),
108 mSendObjectHandle(kInvalidObjectHandle),
Mike Lockwood4714b072010-07-12 08:49:01 -0400109 mSendObjectFormat(0),
Mike Lockwood16864ba2010-05-11 17:16:59 -0400110 mSendObjectFileSize(0)
111{
Mike Lockwood16864ba2010-05-11 17:16:59 -0400112}
113
114MtpServer::~MtpServer() {
115}
116
Mike Lockwooda8494402011-02-18 09:07:14 -0500117void MtpServer::addStorage(MtpStorage* storage) {
118 Mutex::Autolock autoLock(mMutex);
119
120 mStorages.push(storage);
121 sendStoreAdded(storage->getStorageID());
122}
123
124void MtpServer::removeStorage(MtpStorage* storage) {
125 Mutex::Autolock autoLock(mMutex);
126
127 for (int i = 0; i < mStorages.size(); i++) {
128 if (mStorages[i] == storage) {
129 mStorages.removeAt(i);
130 sendStoreRemoved(storage->getStorageID());
131 break;
132 }
133 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400134}
135
136MtpStorage* MtpServer::getStorage(MtpStorageID id) {
Mike Lockwoodfd346262010-12-08 16:08:01 -0800137 if (id == 0)
138 return mStorages[0];
Mike Lockwood16864ba2010-05-11 17:16:59 -0400139 for (int i = 0; i < mStorages.size(); i++) {
Mike Lockwoodfd346262010-12-08 16:08:01 -0800140 MtpStorage* storage = mStorages[i];
Mike Lockwood16864ba2010-05-11 17:16:59 -0400141 if (storage->getStorageID() == id)
142 return storage;
143 }
144 return NULL;
145}
146
Mike Lockwooda8494402011-02-18 09:07:14 -0500147bool MtpServer::hasStorage(MtpStorageID id) {
148 if (id == 0 || id == 0xFFFFFFFF)
149 return mStorages.size() > 0;
150 return (getStorage(id) != NULL);
151}
152
Mike Lockwood16864ba2010-05-11 17:16:59 -0400153void MtpServer::run() {
154 int fd = mFD;
155
Steve Block3856b092011-10-20 11:56:00 +0100156 ALOGV("MtpServer::run fd: %d\n", fd);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400157
158 while (1) {
159 int ret = mRequest.read(fd);
160 if (ret < 0) {
Steve Block3856b092011-10-20 11:56:00 +0100161 ALOGV("request read returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400162 if (errno == ECANCELED) {
163 // return to top of loop and wait for next command
164 continue;
165 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400166 break;
167 }
168 MtpOperationCode operation = mRequest.getOperationCode();
169 MtpTransactionID transaction = mRequest.getTransactionID();
170
Steve Block3856b092011-10-20 11:56:00 +0100171 ALOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400172 mRequest.dump();
173
174 // FIXME need to generalize this
Mike Lockwood438344f2010-08-03 15:30:09 -0400175 bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
Mike Lockwood8277cec2010-08-10 15:20:35 -0400176 || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
177 || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
178 || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400179 if (dataIn) {
180 int ret = mData.read(fd);
181 if (ret < 0) {
Steve Block29357bc2012-01-06 19:20:56 +0000182 ALOGE("data read returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400183 if (errno == ECANCELED) {
184 // return to top of loop and wait for next command
185 continue;
186 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400187 break;
188 }
Steve Block3856b092011-10-20 11:56:00 +0100189 ALOGV("received data:");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400190 mData.dump();
191 } else {
192 mData.reset();
193 }
194
Mike Lockwood916076c2010-06-04 09:49:21 -0400195 if (handleRequest()) {
196 if (!dataIn && mData.hasData()) {
197 mData.setOperationCode(operation);
198 mData.setTransactionID(transaction);
Steve Block3856b092011-10-20 11:56:00 +0100199 ALOGV("sending data:");
Mike Lockwood23d20712010-10-11 17:31:44 -0400200 mData.dump();
Mike Lockwood916076c2010-06-04 09:49:21 -0400201 ret = mData.write(fd);
202 if (ret < 0) {
Steve Block29357bc2012-01-06 19:20:56 +0000203 ALOGE("request write returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400204 if (errno == ECANCELED) {
205 // return to top of loop and wait for next command
206 continue;
207 }
208 break;
209 }
210 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400211
Mike Lockwood916076c2010-06-04 09:49:21 -0400212 mResponse.setTransactionID(transaction);
Steve Block3856b092011-10-20 11:56:00 +0100213 ALOGV("sending response %04X", mResponse.getResponseCode());
Mike Lockwood916076c2010-06-04 09:49:21 -0400214 ret = mResponse.write(fd);
Mike Lockwood23d20712010-10-11 17:31:44 -0400215 mResponse.dump();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400216 if (ret < 0) {
Steve Block29357bc2012-01-06 19:20:56 +0000217 ALOGE("request write returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400218 if (errno == ECANCELED) {
219 // return to top of loop and wait for next command
220 continue;
221 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400222 break;
223 }
Mike Lockwood916076c2010-06-04 09:49:21 -0400224 } else {
Steve Block3856b092011-10-20 11:56:00 +0100225 ALOGV("skipping response\n");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400226 }
227 }
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400228
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700229 // commit any open edits
230 int count = mObjectEditList.size();
231 for (int i = 0; i < count; i++) {
232 ObjectEdit* edit = mObjectEditList[i];
233 commitEdit(edit);
234 delete edit;
235 }
236 mObjectEditList.clear();
237
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400238 if (mSessionOpen)
239 mDatabase->sessionEnded();
Mike Lockwooddec73882011-07-11 15:04:38 -0400240 close(fd);
241 mFD = -1;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400242}
243
Mike Lockwood873871f2010-07-12 18:54:16 -0400244void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
Steve Block3856b092011-10-20 11:56:00 +0100245 ALOGV("sendObjectAdded %d\n", handle);
Mike Lockwooda8494402011-02-18 09:07:14 -0500246 sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
Mike Lockwood873871f2010-07-12 18:54:16 -0400247}
248
249void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
Steve Block3856b092011-10-20 11:56:00 +0100250 ALOGV("sendObjectRemoved %d\n", handle);
Mike Lockwooda8494402011-02-18 09:07:14 -0500251 sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
252}
253
254void MtpServer::sendStoreAdded(MtpStorageID id) {
Steve Block3856b092011-10-20 11:56:00 +0100255 ALOGV("sendStoreAdded %08X\n", id);
Mike Lockwooda8494402011-02-18 09:07:14 -0500256 sendEvent(MTP_EVENT_STORE_ADDED, id);
257}
258
259void MtpServer::sendStoreRemoved(MtpStorageID id) {
Steve Block3856b092011-10-20 11:56:00 +0100260 ALOGV("sendStoreRemoved %08X\n", id);
Mike Lockwooda8494402011-02-18 09:07:14 -0500261 sendEvent(MTP_EVENT_STORE_REMOVED, id);
262}
263
264void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
Mike Lockwood73ecd232010-07-19 14:29:58 -0400265 if (mSessionOpen) {
Mike Lockwooda8494402011-02-18 09:07:14 -0500266 mEvent.setEventCode(code);
Mike Lockwood73ecd232010-07-19 14:29:58 -0400267 mEvent.setTransactionID(mRequest.getTransactionID());
Mike Lockwooda8494402011-02-18 09:07:14 -0500268 mEvent.setParameter(1, param1);
Mike Lockwood73ecd232010-07-19 14:29:58 -0400269 int ret = mEvent.write(mFD);
Steve Block3856b092011-10-20 11:56:00 +0100270 ALOGV("mEvent.write returned %d\n", ret);
Mike Lockwood73ecd232010-07-19 14:29:58 -0400271 }
Mike Lockwood873871f2010-07-12 18:54:16 -0400272}
273
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700274void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
275 uint64_t size, MtpObjectFormat format, int fd) {
Mike Lockwoodc3f16e52011-04-25 12:56:21 -0700276 ObjectEdit* edit = new ObjectEdit(handle, path, size, format, fd);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700277 mObjectEditList.add(edit);
278}
279
280MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
281 int count = mObjectEditList.size();
282 for (int i = 0; i < count; i++) {
283 ObjectEdit* edit = mObjectEditList[i];
Mike Lockwoodc3f16e52011-04-25 12:56:21 -0700284 if (edit->mHandle == handle) return edit;
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700285 }
286 return NULL;
287}
288
289void MtpServer::removeEditObject(MtpObjectHandle handle) {
290 int count = mObjectEditList.size();
291 for (int i = 0; i < count; i++) {
292 ObjectEdit* edit = mObjectEditList[i];
Mike Lockwoodc3f16e52011-04-25 12:56:21 -0700293 if (edit->mHandle == handle) {
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700294 delete edit;
295 mObjectEditList.removeAt(i);
296 return;
297 }
298 }
Steve Block29357bc2012-01-06 19:20:56 +0000299 ALOGE("ObjectEdit not found in removeEditObject");
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700300}
301
302void MtpServer::commitEdit(ObjectEdit* edit) {
Mike Lockwoodc3f16e52011-04-25 12:56:21 -0700303 mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700304}
305
306
Mike Lockwood916076c2010-06-04 09:49:21 -0400307bool MtpServer::handleRequest() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500308 Mutex::Autolock autoLock(mMutex);
309
Mike Lockwood16864ba2010-05-11 17:16:59 -0400310 MtpOperationCode operation = mRequest.getOperationCode();
311 MtpResponseCode response;
312
313 mResponse.reset();
314
315 if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
316 // FIXME - need to delete mSendObjectHandle from the database
Steve Block29357bc2012-01-06 19:20:56 +0000317 ALOGE("expected SendObject after SendObjectInfo");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400318 mSendObjectHandle = kInvalidObjectHandle;
319 }
320
321 switch (operation) {
322 case MTP_OPERATION_GET_DEVICE_INFO:
323 response = doGetDeviceInfo();
324 break;
325 case MTP_OPERATION_OPEN_SESSION:
326 response = doOpenSession();
327 break;
328 case MTP_OPERATION_CLOSE_SESSION:
329 response = doCloseSession();
330 break;
331 case MTP_OPERATION_GET_STORAGE_IDS:
332 response = doGetStorageIDs();
333 break;
334 case MTP_OPERATION_GET_STORAGE_INFO:
335 response = doGetStorageInfo();
336 break;
337 case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
338 response = doGetObjectPropsSupported();
339 break;
340 case MTP_OPERATION_GET_OBJECT_HANDLES:
341 response = doGetObjectHandles();
342 break;
Mike Lockwood343af4e2010-08-02 10:52:20 -0400343 case MTP_OPERATION_GET_NUM_OBJECTS:
344 response = doGetNumObjects();
345 break;
Mike Lockwood438344f2010-08-03 15:30:09 -0400346 case MTP_OPERATION_GET_OBJECT_REFERENCES:
347 response = doGetObjectReferences();
348 break;
349 case MTP_OPERATION_SET_OBJECT_REFERENCES:
350 response = doSetObjectReferences();
351 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400352 case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
353 response = doGetObjectPropValue();
354 break;
Mike Lockwood8277cec2010-08-10 15:20:35 -0400355 case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
356 response = doSetObjectPropValue();
357 break;
358 case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
359 response = doGetDevicePropValue();
360 break;
361 case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
362 response = doSetDevicePropValue();
363 break;
364 case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
365 response = doResetDevicePropValue();
366 break;
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400367 case MTP_OPERATION_GET_OBJECT_PROP_LIST:
368 response = doGetObjectPropList();
369 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400370 case MTP_OPERATION_GET_OBJECT_INFO:
371 response = doGetObjectInfo();
372 break;
373 case MTP_OPERATION_GET_OBJECT:
374 response = doGetObject();
375 break;
Mike Lockwood64000782011-04-24 18:40:17 -0700376 case MTP_OPERATION_GET_THUMB:
377 response = doGetThumb();
378 break;
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500379 case MTP_OPERATION_GET_PARTIAL_OBJECT:
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700380 case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
381 response = doGetPartialObject(operation);
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500382 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400383 case MTP_OPERATION_SEND_OBJECT_INFO:
384 response = doSendObjectInfo();
385 break;
386 case MTP_OPERATION_SEND_OBJECT:
387 response = doSendObject();
388 break;
389 case MTP_OPERATION_DELETE_OBJECT:
390 response = doDeleteObject();
391 break;
392 case MTP_OPERATION_GET_OBJECT_PROP_DESC:
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400393 response = doGetObjectPropDesc();
394 break;
Mike Lockwoode3e76c42010-09-02 14:57:30 -0400395 case MTP_OPERATION_GET_DEVICE_PROP_DESC:
396 response = doGetDevicePropDesc();
397 break;
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700398 case MTP_OPERATION_SEND_PARTIAL_OBJECT:
399 response = doSendPartialObject();
400 break;
401 case MTP_OPERATION_TRUNCATE_OBJECT:
402 response = doTruncateObject();
403 break;
404 case MTP_OPERATION_BEGIN_EDIT_OBJECT:
405 response = doBeginEditObject();
406 break;
407 case MTP_OPERATION_END_EDIT_OBJECT:
408 response = doEndEditObject();
409 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400410 default:
Steve Block29357bc2012-01-06 19:20:56 +0000411 ALOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400412 response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
413 break;
414 }
415
Mike Lockwood916076c2010-06-04 09:49:21 -0400416 if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
417 return false;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400418 mResponse.setResponseCode(response);
Mike Lockwood916076c2010-06-04 09:49:21 -0400419 return true;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400420}
421
422MtpResponseCode MtpServer::doGetDeviceInfo() {
423 MtpStringBuffer string;
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700424 char prop_value[PROPERTY_VALUE_MAX];
Mike Lockwood16864ba2010-05-11 17:16:59 -0400425
Mike Lockwood782aef12010-08-10 07:37:50 -0400426 MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
427 MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
428 MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
429
Mike Lockwood16864ba2010-05-11 17:16:59 -0400430 // fill in device info
431 mData.putUInt16(MTP_STANDARD_VERSION);
Mike Lockwood3d1d7762011-06-21 08:27:06 -0400432 if (mPtp) {
433 mData.putUInt32(0);
434 } else {
435 // MTP Vendor Extension ID
436 mData.putUInt32(6);
437 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400438 mData.putUInt16(MTP_STANDARD_VERSION);
Mike Lockwood3d1d7762011-06-21 08:27:06 -0400439 if (mPtp) {
440 // no extensions
441 string.set("");
442 } else {
443 // MTP extensions
444 string.set("microsoft.com: 1.0; android.com: 1.0;");
445 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400446 mData.putString(string); // MTP Extensions
447 mData.putUInt16(0); //Functional Mode
448 mData.putAUInt16(kSupportedOperationCodes,
449 sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
Mike Lockwood873871f2010-07-12 18:54:16 -0400450 mData.putAUInt16(kSupportedEventCodes,
451 sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
Mike Lockwood782aef12010-08-10 07:37:50 -0400452 mData.putAUInt16(deviceProperties); // Device Properties Supported
453 mData.putAUInt16(captureFormats); // Capture Formats
454 mData.putAUInt16(playbackFormats); // Playback Formats
Mike Lockwood8d08c5a2011-01-31 16:44:44 -0500455
456 property_get("ro.product.manufacturer", prop_value, "unknown manufacturer");
457 string.set(prop_value);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400458 mData.putString(string); // Manufacturer
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700459
460 property_get("ro.product.model", prop_value, "MTP Device");
461 string.set(prop_value);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400462 mData.putString(string); // Model
463 string.set("1.0");
464 mData.putString(string); // Device Version
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700465
466 property_get("ro.serialno", prop_value, "????????");
467 string.set(prop_value);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400468 mData.putString(string); // Serial Number
469
Mike Lockwood782aef12010-08-10 07:37:50 -0400470 delete playbackFormats;
471 delete captureFormats;
472 delete deviceProperties;
473
Mike Lockwood16864ba2010-05-11 17:16:59 -0400474 return MTP_RESPONSE_OK;
475}
476
477MtpResponseCode MtpServer::doOpenSession() {
478 if (mSessionOpen) {
479 mResponse.setParameter(1, mSessionID);
480 return MTP_RESPONSE_SESSION_ALREADY_OPEN;
481 }
482 mSessionID = mRequest.getParameter(1);
483 mSessionOpen = true;
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400484
485 mDatabase->sessionStarted();
486
Mike Lockwood16864ba2010-05-11 17:16:59 -0400487 return MTP_RESPONSE_OK;
488}
489
490MtpResponseCode MtpServer::doCloseSession() {
491 if (!mSessionOpen)
492 return MTP_RESPONSE_SESSION_NOT_OPEN;
493 mSessionID = 0;
494 mSessionOpen = false;
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400495 mDatabase->sessionEnded();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400496 return MTP_RESPONSE_OK;
497}
498
499MtpResponseCode MtpServer::doGetStorageIDs() {
500 if (!mSessionOpen)
501 return MTP_RESPONSE_SESSION_NOT_OPEN;
502
503 int count = mStorages.size();
504 mData.putUInt32(count);
505 for (int i = 0; i < count; i++)
506 mData.putUInt32(mStorages[i]->getStorageID());
507
508 return MTP_RESPONSE_OK;
509}
510
511MtpResponseCode MtpServer::doGetStorageInfo() {
512 MtpStringBuffer string;
513
514 if (!mSessionOpen)
515 return MTP_RESPONSE_SESSION_NOT_OPEN;
516 MtpStorageID id = mRequest.getParameter(1);
517 MtpStorage* storage = getStorage(id);
518 if (!storage)
519 return MTP_RESPONSE_INVALID_STORAGE_ID;
520
521 mData.putUInt16(storage->getType());
522 mData.putUInt16(storage->getFileSystemType());
523 mData.putUInt16(storage->getAccessCapability());
524 mData.putUInt64(storage->getMaxCapacity());
525 mData.putUInt64(storage->getFreeSpace());
526 mData.putUInt32(1024*1024*1024); // Free Space in Objects
527 string.set(storage->getDescription());
528 mData.putString(string);
529 mData.putEmptyString(); // Volume Identifier
530
531 return MTP_RESPONSE_OK;
532}
533
534MtpResponseCode MtpServer::doGetObjectPropsSupported() {
535 if (!mSessionOpen)
536 return MTP_RESPONSE_SESSION_NOT_OPEN;
537 MtpObjectFormat format = mRequest.getParameter(1);
Mike Lockwood2e09e282010-12-07 10:51:20 -0800538 MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
Mike Lockwood782aef12010-08-10 07:37:50 -0400539 mData.putAUInt16(properties);
Mike Lockwoodbf9b2052010-08-10 15:11:32 -0400540 delete properties;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400541 return MTP_RESPONSE_OK;
542}
543
544MtpResponseCode MtpServer::doGetObjectHandles() {
545 if (!mSessionOpen)
546 return MTP_RESPONSE_SESSION_NOT_OPEN;
547 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
Mike Lockwoode13401b2010-05-19 15:12:14 -0400548 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
Mike Lockwood16864ba2010-05-11 17:16:59 -0400549 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
Mike Lockwooddc3185e2011-06-17 13:44:24 -0400550 // 0x00000000 for all objects
Mike Lockwooda8494402011-02-18 09:07:14 -0500551
552 if (!hasStorage(storageID))
553 return MTP_RESPONSE_INVALID_STORAGE_ID;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400554
555 MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
556 mData.putAUInt32(handles);
557 delete handles;
558 return MTP_RESPONSE_OK;
559}
560
Mike Lockwood343af4e2010-08-02 10:52:20 -0400561MtpResponseCode MtpServer::doGetNumObjects() {
562 if (!mSessionOpen)
563 return MTP_RESPONSE_SESSION_NOT_OPEN;
564 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
565 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
566 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
Mike Lockwooddc3185e2011-06-17 13:44:24 -0400567 // 0x00000000 for all objects
Mike Lockwooda8494402011-02-18 09:07:14 -0500568 if (!hasStorage(storageID))
569 return MTP_RESPONSE_INVALID_STORAGE_ID;
Mike Lockwood343af4e2010-08-02 10:52:20 -0400570
571 int count = mDatabase->getNumObjects(storageID, format, parent);
572 if (count >= 0) {
573 mResponse.setParameter(1, count);
574 return MTP_RESPONSE_OK;
575 } else {
576 mResponse.setParameter(1, 0);
577 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
578 }
579}
580
Mike Lockwood438344f2010-08-03 15:30:09 -0400581MtpResponseCode MtpServer::doGetObjectReferences() {
582 if (!mSessionOpen)
583 return MTP_RESPONSE_SESSION_NOT_OPEN;
Mike Lockwooda8494402011-02-18 09:07:14 -0500584 if (!hasStorage())
585 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
586 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400587
588 // FIXME - check for invalid object handle
Mike Lockwood438344f2010-08-03 15:30:09 -0400589 MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400590 if (handles) {
591 mData.putAUInt32(handles);
592 delete handles;
593 } else {
Mike Lockwood438344f2010-08-03 15:30:09 -0400594 mData.putEmptyArray();
Mike Lockwood438344f2010-08-03 15:30:09 -0400595 }
Mike Lockwood438344f2010-08-03 15:30:09 -0400596 return MTP_RESPONSE_OK;
597}
598
599MtpResponseCode MtpServer::doSetObjectReferences() {
600 if (!mSessionOpen)
601 return MTP_RESPONSE_SESSION_NOT_OPEN;
Mike Lockwooda8494402011-02-18 09:07:14 -0500602 if (!hasStorage())
603 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood438344f2010-08-03 15:30:09 -0400604 MtpStorageID handle = mRequest.getParameter(1);
Mike Lockwooda8494402011-02-18 09:07:14 -0500605
Mike Lockwood438344f2010-08-03 15:30:09 -0400606 MtpObjectHandleList* references = mData.getAUInt32();
607 MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
608 delete references;
609 return result;
610}
611
Mike Lockwood16864ba2010-05-11 17:16:59 -0400612MtpResponseCode MtpServer::doGetObjectPropValue() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500613 if (!hasStorage())
614 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400615 MtpObjectHandle handle = mRequest.getParameter(1);
616 MtpObjectProperty property = mRequest.getParameter(2);
Steve Block3856b092011-10-20 11:56:00 +0100617 ALOGV("GetObjectPropValue %d %s\n", handle,
Mike Lockwood8277cec2010-08-10 15:20:35 -0400618 MtpDebug::getObjectPropCodeName(property));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400619
Mike Lockwood8277cec2010-08-10 15:20:35 -0400620 return mDatabase->getObjectPropertyValue(handle, property, mData);
621}
622
623MtpResponseCode MtpServer::doSetObjectPropValue() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500624 if (!hasStorage())
625 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood8277cec2010-08-10 15:20:35 -0400626 MtpObjectHandle handle = mRequest.getParameter(1);
627 MtpObjectProperty property = mRequest.getParameter(2);
Steve Block3856b092011-10-20 11:56:00 +0100628 ALOGV("SetObjectPropValue %d %s\n", handle,
Mike Lockwood8277cec2010-08-10 15:20:35 -0400629 MtpDebug::getObjectPropCodeName(property));
630
631 return mDatabase->setObjectPropertyValue(handle, property, mData);
632}
633
634MtpResponseCode MtpServer::doGetDevicePropValue() {
635 MtpDeviceProperty property = mRequest.getParameter(1);
Steve Block3856b092011-10-20 11:56:00 +0100636 ALOGV("GetDevicePropValue %s\n",
Mike Lockwood8277cec2010-08-10 15:20:35 -0400637 MtpDebug::getDevicePropCodeName(property));
638
639 return mDatabase->getDevicePropertyValue(property, mData);
640}
641
642MtpResponseCode MtpServer::doSetDevicePropValue() {
643 MtpDeviceProperty property = mRequest.getParameter(1);
Steve Block3856b092011-10-20 11:56:00 +0100644 ALOGV("SetDevicePropValue %s\n",
Mike Lockwood8277cec2010-08-10 15:20:35 -0400645 MtpDebug::getDevicePropCodeName(property));
646
647 return mDatabase->setDevicePropertyValue(property, mData);
648}
649
650MtpResponseCode MtpServer::doResetDevicePropValue() {
651 MtpDeviceProperty property = mRequest.getParameter(1);
Steve Block3856b092011-10-20 11:56:00 +0100652 ALOGV("ResetDevicePropValue %s\n",
Mike Lockwood8277cec2010-08-10 15:20:35 -0400653 MtpDebug::getDevicePropCodeName(property));
654
655 return mDatabase->resetDeviceProperty(property);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400656}
657
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400658MtpResponseCode MtpServer::doGetObjectPropList() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500659 if (!hasStorage())
660 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400661
662 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwood40ce1f22010-12-01 18:46:23 -0500663 // use uint32_t so we can support 0xFFFFFFFF
664 uint32_t format = mRequest.getParameter(2);
665 uint32_t property = mRequest.getParameter(3);
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400666 int groupCode = mRequest.getParameter(4);
Mike Lockwoodf05ff072010-11-23 18:45:25 -0500667 int depth = mRequest.getParameter(5);
Steve Block3856b092011-10-20 11:56:00 +0100668 ALOGV("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400669 handle, MtpDebug::getFormatCodeName(format),
670 MtpDebug::getObjectPropCodeName(property), groupCode, depth);
671
672 return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
673}
674
Mike Lockwood16864ba2010-05-11 17:16:59 -0400675MtpResponseCode MtpServer::doGetObjectInfo() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500676 if (!hasStorage())
677 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400678 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700679 MtpObjectInfo info(handle);
680 MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
681 if (result == MTP_RESPONSE_OK) {
682 char date[20];
683
684 mData.putUInt32(info.mStorageID);
685 mData.putUInt16(info.mFormat);
686 mData.putUInt16(info.mProtectionStatus);
687
688 // if object is being edited the database size may be out of date
689 uint32_t size = info.mCompressedSize;
690 ObjectEdit* edit = getEditObject(handle);
691 if (edit)
Mike Lockwoodc3f16e52011-04-25 12:56:21 -0700692 size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700693 mData.putUInt32(size);
694
695 mData.putUInt16(info.mThumbFormat);
696 mData.putUInt32(info.mThumbCompressedSize);
697 mData.putUInt32(info.mThumbPixWidth);
698 mData.putUInt32(info.mThumbPixHeight);
699 mData.putUInt32(info.mImagePixWidth);
700 mData.putUInt32(info.mImagePixHeight);
701 mData.putUInt32(info.mImagePixDepth);
702 mData.putUInt32(info.mParent);
703 mData.putUInt16(info.mAssociationType);
704 mData.putUInt32(info.mAssociationDesc);
705 mData.putUInt32(info.mSequenceNumber);
706 mData.putString(info.mName);
Mike Lockwoodec24fa42013-04-01 10:51:35 -0700707 formatDateTime(info.mDateCreated, date, sizeof(date));
708 mData.putString(date); // date created
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700709 formatDateTime(info.mDateModified, date, sizeof(date));
710 mData.putString(date); // date modified
711 mData.putEmptyString(); // keywords
712 }
713 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400714}
715
716MtpResponseCode MtpServer::doGetObject() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500717 if (!hasStorage())
718 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400719 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400720 MtpString pathBuf;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400721 int64_t fileLength;
Mike Lockwoodfd346262010-12-08 16:08:01 -0800722 MtpObjectFormat format;
723 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400724 if (result != MTP_RESPONSE_OK)
725 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400726
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400727 const char* filePath = (const char *)pathBuf;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400728 mtp_file_range mfr;
Mike Lockwoodc6588762010-06-22 15:03:53 -0400729 mfr.fd = open(filePath, O_RDONLY);
730 if (mfr.fd < 0) {
731 return MTP_RESPONSE_GENERAL_ERROR;
732 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400733 mfr.offset = 0;
734 mfr.length = fileLength;
Mike Lockwoodef441d92011-07-14 21:00:02 -0400735 mfr.command = mRequest.getOperationCode();
736 mfr.transaction_id = mRequest.getTransactionID();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400737
738 // then transfer the file
Mike Lockwoodef441d92011-07-14 21:00:02 -0400739 int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
Steve Block3856b092011-10-20 11:56:00 +0100740 ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400741 close(mfr.fd);
Mike Lockwood916076c2010-06-04 09:49:21 -0400742 if (ret < 0) {
743 if (errno == ECANCELED)
744 return MTP_RESPONSE_TRANSACTION_CANCELLED;
745 else
746 return MTP_RESPONSE_GENERAL_ERROR;
747 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400748 return MTP_RESPONSE_OK;
749}
750
Mike Lockwood64000782011-04-24 18:40:17 -0700751MtpResponseCode MtpServer::doGetThumb() {
752 MtpObjectHandle handle = mRequest.getParameter(1);
753 size_t thumbSize;
754 void* thumb = mDatabase->getThumbnail(handle, thumbSize);
755 if (thumb) {
756 // send data
757 mData.setOperationCode(mRequest.getOperationCode());
758 mData.setTransactionID(mRequest.getTransactionID());
759 mData.writeData(mFD, thumb, thumbSize);
760 free(thumb);
761 return MTP_RESPONSE_OK;
762 } else {
763 return MTP_RESPONSE_GENERAL_ERROR;
764 }
765}
766
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700767MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
Mike Lockwooda8494402011-02-18 09:07:14 -0500768 if (!hasStorage())
769 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500770 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -0700771 uint64_t offset;
772 uint32_t length;
773 offset = mRequest.getParameter(2);
774 if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
775 // android extension with 64 bit offset
776 uint64_t offset2 = mRequest.getParameter(3);
777 offset = offset | (offset2 << 32);
778 length = mRequest.getParameter(4);
779 } else {
780 // standard GetPartialObject
781 length = mRequest.getParameter(3);
782 }
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500783 MtpString pathBuf;
784 int64_t fileLength;
Mike Lockwoodfd346262010-12-08 16:08:01 -0800785 MtpObjectFormat format;
786 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500787 if (result != MTP_RESPONSE_OK)
788 return result;
789 if (offset + length > fileLength)
790 length = fileLength - offset;
791
792 const char* filePath = (const char *)pathBuf;
793 mtp_file_range mfr;
794 mfr.fd = open(filePath, O_RDONLY);
795 if (mfr.fd < 0) {
796 return MTP_RESPONSE_GENERAL_ERROR;
797 }
798 mfr.offset = offset;
799 mfr.length = length;
Mike Lockwoodef441d92011-07-14 21:00:02 -0400800 mfr.command = mRequest.getOperationCode();
801 mfr.transaction_id = mRequest.getTransactionID();
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500802 mResponse.setParameter(1, length);
803
Mike Lockwoodef441d92011-07-14 21:00:02 -0400804 // transfer the file
805 int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
Steve Block3856b092011-10-20 11:56:00 +0100806 ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500807 close(mfr.fd);
808 if (ret < 0) {
809 if (errno == ECANCELED)
810 return MTP_RESPONSE_TRANSACTION_CANCELLED;
811 else
812 return MTP_RESPONSE_GENERAL_ERROR;
813 }
814 return MTP_RESPONSE_OK;
815}
816
Mike Lockwood16864ba2010-05-11 17:16:59 -0400817MtpResponseCode MtpServer::doSendObjectInfo() {
818 MtpString path;
819 MtpStorageID storageID = mRequest.getParameter(1);
820 MtpStorage* storage = getStorage(storageID);
821 MtpObjectHandle parent = mRequest.getParameter(2);
822 if (!storage)
823 return MTP_RESPONSE_INVALID_STORAGE_ID;
824
825 // special case the root
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400826 if (parent == MTP_PARENT_ROOT) {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400827 path = storage->getPath();
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400828 parent = 0;
829 } else {
Mike Lockwoodfd346262010-12-08 16:08:01 -0800830 int64_t length;
831 MtpObjectFormat format;
832 int result = mDatabase->getObjectFilePath(parent, path, length, format);
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400833 if (result != MTP_RESPONSE_OK)
834 return result;
Mike Lockwoodfd346262010-12-08 16:08:01 -0800835 if (format != MTP_FORMAT_ASSOCIATION)
836 return MTP_RESPONSE_INVALID_PARENT_OBJECT;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400837 }
838
839 // read only the fields we need
840 mData.getUInt32(); // storage ID
841 MtpObjectFormat format = mData.getUInt16();
842 mData.getUInt16(); // protection status
843 mSendObjectFileSize = mData.getUInt32();
844 mData.getUInt16(); // thumb format
845 mData.getUInt32(); // thumb compressed size
846 mData.getUInt32(); // thumb pix width
847 mData.getUInt32(); // thumb pix height
848 mData.getUInt32(); // image pix width
849 mData.getUInt32(); // image pix height
850 mData.getUInt32(); // image bit depth
851 mData.getUInt32(); // parent
852 uint16_t associationType = mData.getUInt16();
853 uint32_t associationDesc = mData.getUInt32(); // association desc
854 mData.getUInt32(); // sequence number
855 MtpStringBuffer name, created, modified;
856 mData.getString(name); // file name
857 mData.getString(created); // date created
858 mData.getString(modified); // date modified
859 // keywords follow
860
Steve Block3856b092011-10-20 11:56:00 +0100861 ALOGV("name: %s format: %04X\n", (const char *)name, format);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400862 time_t modifiedTime;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400863 if (!parseDateTime(modified, modifiedTime))
864 modifiedTime = 0;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400865
866 if (path[path.size() - 1] != '/')
867 path += "/";
868 path += (const char *)name;
869
Mike Lockwood20c3be02010-12-12 12:17:43 -0800870 // check space first
871 if (mSendObjectFileSize > storage->getFreeSpace())
872 return MTP_RESPONSE_STORAGE_FULL;
Mike Lockwood9b88b722011-07-11 09:18:03 -0400873 uint64_t maxFileSize = storage->getMaxFileSize();
874 // check storage max file size
875 if (maxFileSize != 0) {
876 // if mSendObjectFileSize is 0xFFFFFFFF, then all we know is the file size
877 // is >= 0xFFFFFFFF
878 if (mSendObjectFileSize > maxFileSize || mSendObjectFileSize == 0xFFFFFFFF)
879 return MTP_RESPONSE_OBJECT_TOO_LARGE;
880 }
Mike Lockwood20c3be02010-12-12 12:17:43 -0800881
Steve Blockb8a80522011-12-20 16:23:08 +0000882 ALOGD("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID);
Mike Lockwood4714b072010-07-12 08:49:01 -0400883 MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
884 format, parent, storageID, mSendObjectFileSize, modifiedTime);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400885 if (handle == kInvalidObjectHandle) {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400886 return MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwoodfceef462010-05-14 15:35:17 -0400887 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400888
889 if (format == MTP_FORMAT_ASSOCIATION) {
890 mode_t mask = umask(0);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400891 int ret = mkdir((const char *)path, mDirectoryPermission);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400892 umask(mask);
893 if (ret && ret != -EEXIST)
894 return MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400895 chown((const char *)path, getuid(), mFileGroup);
Mike Lockwoodaa952402011-01-18 11:06:19 -0800896
897 // SendObject does not get sent for directories, so call endSendObject here instead
898 mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400899 } else {
900 mSendObjectFilePath = path;
901 // save the handle for the SendObject call, which should follow
902 mSendObjectHandle = handle;
Mike Lockwood4714b072010-07-12 08:49:01 -0400903 mSendObjectFormat = format;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400904 }
905
906 mResponse.setParameter(1, storageID);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400907 mResponse.setParameter(2, parent);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400908 mResponse.setParameter(3, handle);
909
910 return MTP_RESPONSE_OK;
911}
912
913MtpResponseCode MtpServer::doSendObject() {
Mike Lockwooda8494402011-02-18 09:07:14 -0500914 if (!hasStorage())
915 return MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwood4714b072010-07-12 08:49:01 -0400916 MtpResponseCode result = MTP_RESPONSE_OK;
917 mode_t mask;
Mike Lockwoodef441d92011-07-14 21:00:02 -0400918 int ret, initialData;
Mike Lockwood4714b072010-07-12 08:49:01 -0400919
Mike Lockwood16864ba2010-05-11 17:16:59 -0400920 if (mSendObjectHandle == kInvalidObjectHandle) {
Steve Block29357bc2012-01-06 19:20:56 +0000921 ALOGE("Expected SendObjectInfo before SendObject");
Mike Lockwood4714b072010-07-12 08:49:01 -0400922 result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
923 goto done;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400924 }
925
Mike Lockwoodef441d92011-07-14 21:00:02 -0400926 // read the header, and possibly some data
927 ret = mData.read(mFD);
928 if (ret < MTP_CONTAINER_HEADER_SIZE) {
929 result = MTP_RESPONSE_GENERAL_ERROR;
930 goto done;
931 }
932 initialData = ret - MTP_CONTAINER_HEADER_SIZE;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400933
934 mtp_file_range mfr;
Nick Kralevichaf8e8aa2012-06-26 13:32:23 -0700935 mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400936 if (mfr.fd < 0) {
Mike Lockwood4714b072010-07-12 08:49:01 -0400937 result = MTP_RESPONSE_GENERAL_ERROR;
938 goto done;
Mike Lockwoodc6588762010-06-22 15:03:53 -0400939 }
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400940 fchown(mfr.fd, getuid(), mFileGroup);
941 // set permissions
Mike Lockwood4714b072010-07-12 08:49:01 -0400942 mask = umask(0);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400943 fchmod(mfr.fd, mFilePermission);
944 umask(mask);
945
Mike Lockwoodef441d92011-07-14 21:00:02 -0400946 if (initialData > 0)
947 ret = write(mfr.fd, mData.getData(), initialData);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400948
Mike Lockwoodef441d92011-07-14 21:00:02 -0400949 if (mSendObjectFileSize - initialData > 0) {
950 mfr.offset = initialData;
Mike Lockwood0cc79c62011-10-13 11:38:20 -0400951 if (mSendObjectFileSize == 0xFFFFFFFF) {
952 // tell driver to read until it receives a short packet
953 mfr.length = 0xFFFFFFFF;
954 } else {
955 mfr.length = mSendObjectFileSize - initialData;
956 }
Mike Lockwoodef441d92011-07-14 21:00:02 -0400957
Steve Block3856b092011-10-20 11:56:00 +0100958 ALOGV("receiving %s\n", (const char *)mSendObjectFilePath);
Mike Lockwoodef441d92011-07-14 21:00:02 -0400959 // transfer the file
960 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
Steve Block3856b092011-10-20 11:56:00 +0100961 ALOGV("MTP_RECEIVE_FILE returned %d\n", ret);
Mike Lockwoodef441d92011-07-14 21:00:02 -0400962 }
Mike Lockwoodc6588762010-06-22 15:03:53 -0400963 close(mfr.fd);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400964
Mike Lockwood916076c2010-06-04 09:49:21 -0400965 if (ret < 0) {
966 unlink(mSendObjectFilePath);
967 if (errno == ECANCELED)
Mike Lockwood4714b072010-07-12 08:49:01 -0400968 result = MTP_RESPONSE_TRANSACTION_CANCELLED;
Mike Lockwood916076c2010-06-04 09:49:21 -0400969 else
Mike Lockwood4714b072010-07-12 08:49:01 -0400970 result = MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwood916076c2010-06-04 09:49:21 -0400971 }
Mike Lockwood4714b072010-07-12 08:49:01 -0400972
973done:
Mike Lockwoodef441d92011-07-14 21:00:02 -0400974 // reset so we don't attempt to send the data back
975 mData.reset();
976
Mike Lockwood4714b072010-07-12 08:49:01 -0400977 mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
Mike Lockwoodaa952402011-01-18 11:06:19 -0800978 result == MTP_RESPONSE_OK);
Mike Lockwood4714b072010-07-12 08:49:01 -0400979 mSendObjectHandle = kInvalidObjectHandle;
980 mSendObjectFormat = 0;
981 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400982}
983
Mike Lockwoodd3211492010-09-13 17:15:58 -0400984static void deleteRecursive(const char* path) {
985 char pathbuf[PATH_MAX];
986 int pathLength = strlen(path);
987 if (pathLength >= sizeof(pathbuf) - 1) {
Steve Block29357bc2012-01-06 19:20:56 +0000988 ALOGE("path too long: %s\n", path);
Mike Lockwoodd3211492010-09-13 17:15:58 -0400989 }
990 strcpy(pathbuf, path);
991 if (pathbuf[pathLength - 1] != '/') {
992 pathbuf[pathLength++] = '/';
993 }
994 char* fileSpot = pathbuf + pathLength;
995 int pathRemaining = sizeof(pathbuf) - pathLength - 1;
996
997 DIR* dir = opendir(path);
998 if (!dir) {
Steve Block29357bc2012-01-06 19:20:56 +0000999 ALOGE("opendir %s failed: %s", path, strerror(errno));
Mike Lockwoodd3211492010-09-13 17:15:58 -04001000 return;
1001 }
1002
1003 struct dirent* entry;
1004 while ((entry = readdir(dir))) {
1005 const char* name = entry->d_name;
1006
1007 // ignore "." and ".."
1008 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
1009 continue;
1010 }
1011
1012 int nameLength = strlen(name);
1013 if (nameLength > pathRemaining) {
Steve Block29357bc2012-01-06 19:20:56 +00001014 ALOGE("path %s/%s too long\n", path, name);
Mike Lockwoodd3211492010-09-13 17:15:58 -04001015 continue;
1016 }
1017 strcpy(fileSpot, name);
1018
1019 int type = entry->d_type;
1020 if (entry->d_type == DT_DIR) {
1021 deleteRecursive(pathbuf);
1022 rmdir(pathbuf);
1023 } else {
1024 unlink(pathbuf);
1025 }
1026 }
Mike Lockwood7ce05cf2010-11-11 11:22:32 -05001027 closedir(dir);
Mike Lockwoodd3211492010-09-13 17:15:58 -04001028}
1029
1030static void deletePath(const char* path) {
1031 struct stat statbuf;
1032 if (stat(path, &statbuf) == 0) {
1033 if (S_ISDIR(statbuf.st_mode)) {
1034 deleteRecursive(path);
1035 rmdir(path);
1036 } else {
1037 unlink(path);
1038 }
1039 } else {
Steve Block29357bc2012-01-06 19:20:56 +00001040 ALOGE("deletePath stat failed for %s: %s", path, strerror(errno));
Mike Lockwoodd3211492010-09-13 17:15:58 -04001041 }
1042}
1043
Mike Lockwood16864ba2010-05-11 17:16:59 -04001044MtpResponseCode MtpServer::doDeleteObject() {
Mike Lockwooda8494402011-02-18 09:07:14 -05001045 if (!hasStorage())
1046 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood16864ba2010-05-11 17:16:59 -04001047 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwoodd3211492010-09-13 17:15:58 -04001048 MtpObjectFormat format = mRequest.getParameter(2);
Mike Lockwood16864ba2010-05-11 17:16:59 -04001049 // FIXME - support deleting all objects if handle is 0xFFFFFFFF
1050 // FIXME - implement deleting objects by format
Mike Lockwood16864ba2010-05-11 17:16:59 -04001051
1052 MtpString filePath;
1053 int64_t fileLength;
Mike Lockwoodfd346262010-12-08 16:08:01 -08001054 int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
Mike Lockwood9c04c4c2010-08-02 10:37:41 -04001055 if (result == MTP_RESPONSE_OK) {
Steve Block3856b092011-10-20 11:56:00 +01001056 ALOGV("deleting %s", (const char *)filePath);
Mike Lockwooda9a46c12011-12-01 16:58:41 -05001057 result = mDatabase->deleteFile(handle);
1058 // Don't delete the actual files unless the database deletion is allowed
1059 if (result == MTP_RESPONSE_OK) {
1060 deletePath((const char *)filePath);
1061 }
Mike Lockwood9c04c4c2010-08-02 10:37:41 -04001062 }
Mike Lockwooda9a46c12011-12-01 16:58:41 -05001063
1064 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -04001065}
1066
1067MtpResponseCode MtpServer::doGetObjectPropDesc() {
Mike Lockwood21ef7d02010-06-30 17:00:35 -04001068 MtpObjectProperty propCode = mRequest.getParameter(1);
Mike Lockwood16864ba2010-05-11 17:16:59 -04001069 MtpObjectFormat format = mRequest.getParameter(2);
Steve Block3856b092011-10-20 11:56:00 +01001070 ALOGV("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
Mike Lockwood8277cec2010-08-10 15:20:35 -04001071 MtpDebug::getFormatCodeName(format));
1072 MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
Mike Lockwood21ef7d02010-06-30 17:00:35 -04001073 if (!property)
1074 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
Mike Lockwood21ef7d02010-06-30 17:00:35 -04001075 property->write(mData);
Mike Lockwood8277cec2010-08-10 15:20:35 -04001076 delete property;
1077 return MTP_RESPONSE_OK;
1078}
1079
1080MtpResponseCode MtpServer::doGetDevicePropDesc() {
1081 MtpDeviceProperty propCode = mRequest.getParameter(1);
Steve Block3856b092011-10-20 11:56:00 +01001082 ALOGV("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
Mike Lockwood8277cec2010-08-10 15:20:35 -04001083 MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
1084 if (!property)
1085 return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
1086 property->write(mData);
1087 delete property;
Mike Lockwood21ef7d02010-06-30 17:00:35 -04001088 return MTP_RESPONSE_OK;
Mike Lockwood16864ba2010-05-11 17:16:59 -04001089}
Mike Lockwood7850ef92010-05-14 10:10:36 -04001090
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001091MtpResponseCode MtpServer::doSendPartialObject() {
1092 if (!hasStorage())
1093 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1094 MtpObjectHandle handle = mRequest.getParameter(1);
1095 uint64_t offset = mRequest.getParameter(2);
1096 uint64_t offset2 = mRequest.getParameter(3);
1097 offset = offset | (offset2 << 32);
1098 uint32_t length = mRequest.getParameter(4);
1099
1100 ObjectEdit* edit = getEditObject(handle);
1101 if (!edit) {
Steve Block29357bc2012-01-06 19:20:56 +00001102 ALOGE("object not open for edit in doSendPartialObject");
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001103 return MTP_RESPONSE_GENERAL_ERROR;
1104 }
1105
1106 // can't start writing past the end of the file
Mike Lockwoodc3f16e52011-04-25 12:56:21 -07001107 if (offset > edit->mSize) {
Steve Blockb8a80522011-12-20 16:23:08 +00001108 ALOGD("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001109 return MTP_RESPONSE_GENERAL_ERROR;
1110 }
1111
Mike Lockwoodc3f16e52011-04-25 12:56:21 -07001112 const char* filePath = (const char *)edit->mPath;
Steve Block3856b092011-10-20 11:56:00 +01001113 ALOGV("receiving partial %s %lld %lld\n", filePath, offset, length);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001114
Mike Lockwoodef441d92011-07-14 21:00:02 -04001115 // read the header, and possibly some data
1116 int ret = mData.read(mFD);
1117 if (ret < MTP_CONTAINER_HEADER_SIZE)
1118 return MTP_RESPONSE_GENERAL_ERROR;
1119 int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
1120
1121 if (initialData > 0) {
Mike Lockwoood0a694952013-02-08 13:25:01 -08001122 ret = pwrite(edit->mFD, mData.getData(), initialData, offset);
Mike Lockwoodef441d92011-07-14 21:00:02 -04001123 offset += initialData;
1124 length -= initialData;
1125 }
1126
1127 if (length > 0) {
1128 mtp_file_range mfr;
1129 mfr.fd = edit->mFD;
1130 mfr.offset = offset;
1131 mfr.length = length;
1132
1133 // transfer the file
1134 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
Steve Block3856b092011-10-20 11:56:00 +01001135 ALOGV("MTP_RECEIVE_FILE returned %d", ret);
Mike Lockwoodef441d92011-07-14 21:00:02 -04001136 }
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001137 if (ret < 0) {
1138 mResponse.setParameter(1, 0);
1139 if (errno == ECANCELED)
1140 return MTP_RESPONSE_TRANSACTION_CANCELLED;
1141 else
1142 return MTP_RESPONSE_GENERAL_ERROR;
1143 }
Mike Lockwoodef441d92011-07-14 21:00:02 -04001144
1145 // reset so we don't attempt to send this back
1146 mData.reset();
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001147 mResponse.setParameter(1, length);
1148 uint64_t end = offset + length;
Mike Lockwoodc3f16e52011-04-25 12:56:21 -07001149 if (end > edit->mSize) {
1150 edit->mSize = end;
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001151 }
1152 return MTP_RESPONSE_OK;
1153}
1154
1155MtpResponseCode MtpServer::doTruncateObject() {
1156 MtpObjectHandle handle = mRequest.getParameter(1);
1157 ObjectEdit* edit = getEditObject(handle);
1158 if (!edit) {
Steve Block29357bc2012-01-06 19:20:56 +00001159 ALOGE("object not open for edit in doTruncateObject");
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001160 return MTP_RESPONSE_GENERAL_ERROR;
1161 }
1162
1163 uint64_t offset = mRequest.getParameter(2);
1164 uint64_t offset2 = mRequest.getParameter(3);
1165 offset |= (offset2 << 32);
Mike Lockwoodc3f16e52011-04-25 12:56:21 -07001166 if (ftruncate(edit->mFD, offset) != 0) {
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001167 return MTP_RESPONSE_GENERAL_ERROR;
1168 } else {
Mike Lockwoodc3f16e52011-04-25 12:56:21 -07001169 edit->mSize = offset;
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001170 return MTP_RESPONSE_OK;
1171 }
1172}
1173
1174MtpResponseCode MtpServer::doBeginEditObject() {
1175 MtpObjectHandle handle = mRequest.getParameter(1);
1176 if (getEditObject(handle)) {
Steve Block29357bc2012-01-06 19:20:56 +00001177 ALOGE("object already open for edit in doBeginEditObject");
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001178 return MTP_RESPONSE_GENERAL_ERROR;
1179 }
1180
1181 MtpString path;
1182 int64_t fileLength;
1183 MtpObjectFormat format;
1184 int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
1185 if (result != MTP_RESPONSE_OK)
1186 return result;
1187
1188 int fd = open((const char *)path, O_RDWR | O_EXCL);
1189 if (fd < 0) {
Steve Block29357bc2012-01-06 19:20:56 +00001190 ALOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001191 return MTP_RESPONSE_GENERAL_ERROR;
1192 }
1193
1194 addEditObject(handle, path, fileLength, format, fd);
1195 return MTP_RESPONSE_OK;
1196}
1197
1198MtpResponseCode MtpServer::doEndEditObject() {
1199 MtpObjectHandle handle = mRequest.getParameter(1);
1200 ObjectEdit* edit = getEditObject(handle);
1201 if (!edit) {
Steve Block29357bc2012-01-06 19:20:56 +00001202 ALOGE("object not open for edit in doEndEditObject");
Mike Lockwood7d77dcf2011-04-21 17:05:55 -07001203 return MTP_RESPONSE_GENERAL_ERROR;
1204 }
1205
1206 commitEdit(edit);
1207 removeEditObject(handle);
1208 return MTP_RESPONSE_OK;
1209}
1210
Mike Lockwood7850ef92010-05-14 10:10:36 -04001211} // namespace android