blob: de6cbac9026d9dcaa13a78293f7c7afde7fefd50 [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 Lockwood21ef7d02010-06-30 17:00:35 -040033#include "MtpProperty.h"
Mike Lockwood16864ba2010-05-11 17:16:59 -040034#include "MtpServer.h"
35#include "MtpStorage.h"
36#include "MtpStringBuffer.h"
Mike Lockwood16864ba2010-05-11 17:16:59 -040037
Mike Lockwood8065e202010-07-15 13:36:52 -040038#include <linux/usb/f_mtp.h>
Mike Lockwood16864ba2010-05-11 17:16:59 -040039
Mike Lockwood7850ef92010-05-14 10:10:36 -040040namespace android {
41
Mike Lockwood16864ba2010-05-11 17:16:59 -040042static const MtpOperationCode kSupportedOperationCodes[] = {
43 MTP_OPERATION_GET_DEVICE_INFO,
44 MTP_OPERATION_OPEN_SESSION,
45 MTP_OPERATION_CLOSE_SESSION,
46 MTP_OPERATION_GET_STORAGE_IDS,
47 MTP_OPERATION_GET_STORAGE_INFO,
48 MTP_OPERATION_GET_NUM_OBJECTS,
49 MTP_OPERATION_GET_OBJECT_HANDLES,
50 MTP_OPERATION_GET_OBJECT_INFO,
51 MTP_OPERATION_GET_OBJECT,
52// MTP_OPERATION_GET_THUMB,
53 MTP_OPERATION_DELETE_OBJECT,
54 MTP_OPERATION_SEND_OBJECT_INFO,
55 MTP_OPERATION_SEND_OBJECT,
56// MTP_OPERATION_INITIATE_CAPTURE,
57// MTP_OPERATION_FORMAT_STORE,
58// MTP_OPERATION_RESET_DEVICE,
59// MTP_OPERATION_SELF_TEST,
60// MTP_OPERATION_SET_OBJECT_PROTECTION,
61// MTP_OPERATION_POWER_DOWN,
Mike Lockwoode3e76c42010-09-02 14:57:30 -040062 MTP_OPERATION_GET_DEVICE_PROP_DESC,
Mike Lockwood8277cec2010-08-10 15:20:35 -040063 MTP_OPERATION_GET_DEVICE_PROP_VALUE,
64 MTP_OPERATION_SET_DEVICE_PROP_VALUE,
65 MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
Mike Lockwood16864ba2010-05-11 17:16:59 -040066// MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
67// MTP_OPERATION_MOVE_OBJECT,
68// MTP_OPERATION_COPY_OBJECT,
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -050069 MTP_OPERATION_GET_PARTIAL_OBJECT,
Mike Lockwood16864ba2010-05-11 17:16:59 -040070// MTP_OPERATION_INITIATE_OPEN_CAPTURE,
71 MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
Mike Lockwood8277cec2010-08-10 15:20:35 -040072 MTP_OPERATION_GET_OBJECT_PROP_DESC,
Mike Lockwood677f5702010-09-23 23:04:28 -040073 MTP_OPERATION_GET_OBJECT_PROP_VALUE,
74 MTP_OPERATION_SET_OBJECT_PROP_VALUE,
Mike Lockwoodb6da06e2010-10-14 18:03:25 -040075 MTP_OPERATION_GET_OBJECT_PROP_LIST,
76// MTP_OPERATION_SET_OBJECT_PROP_LIST,
77// MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
78// MTP_OPERATION_SEND_OBJECT_PROP_LIST,
Mike Lockwood438344f2010-08-03 15:30:09 -040079 MTP_OPERATION_GET_OBJECT_REFERENCES,
80 MTP_OPERATION_SET_OBJECT_REFERENCES,
Mike Lockwood16864ba2010-05-11 17:16:59 -040081// MTP_OPERATION_SKIP,
82};
83
Mike Lockwood873871f2010-07-12 18:54:16 -040084static const MtpEventCode kSupportedEventCodes[] = {
85 MTP_EVENT_OBJECT_ADDED,
86 MTP_EVENT_OBJECT_REMOVED,
87};
88
Mike Lockwood1865a5d2010-07-03 00:44:05 -040089MtpServer::MtpServer(int fd, MtpDatabase* database,
Mike Lockwood8e2a2802010-07-02 15:15:07 -040090 int fileGroup, int filePerm, int directoryPerm)
Mike Lockwood16864ba2010-05-11 17:16:59 -040091 : mFD(fd),
Mike Lockwood1865a5d2010-07-03 00:44:05 -040092 mDatabase(database),
Mike Lockwood8e2a2802010-07-02 15:15:07 -040093 mFileGroup(fileGroup),
94 mFilePermission(filePerm),
95 mDirectoryPermission(directoryPerm),
Mike Lockwood16864ba2010-05-11 17:16:59 -040096 mSessionID(0),
97 mSessionOpen(false),
98 mSendObjectHandle(kInvalidObjectHandle),
Mike Lockwood4714b072010-07-12 08:49:01 -040099 mSendObjectFormat(0),
Mike Lockwood16864ba2010-05-11 17:16:59 -0400100 mSendObjectFileSize(0)
101{
Mike Lockwood16864ba2010-05-11 17:16:59 -0400102}
103
104MtpServer::~MtpServer() {
105}
106
107void MtpServer::addStorage(const char* filePath) {
108 int index = mStorages.size() + 1;
109 index |= index << 16; // set high and low part to our index
110 MtpStorage* storage = new MtpStorage(index, filePath, mDatabase);
111 addStorage(storage);
112}
113
114MtpStorage* MtpServer::getStorage(MtpStorageID id) {
115 for (int i = 0; i < mStorages.size(); i++) {
116 MtpStorage* storage = mStorages[i];
117 if (storage->getStorageID() == id)
118 return storage;
119 }
120 return NULL;
121}
122
Mike Lockwood16864ba2010-05-11 17:16:59 -0400123void MtpServer::run() {
124 int fd = mFD;
125
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400126 LOGV("MtpServer::run fd: %d\n", fd);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400127
128 while (1) {
129 int ret = mRequest.read(fd);
130 if (ret < 0) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400131 LOGE("request read returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400132 if (errno == ECANCELED) {
133 // return to top of loop and wait for next command
134 continue;
135 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400136 break;
137 }
138 MtpOperationCode operation = mRequest.getOperationCode();
139 MtpTransactionID transaction = mRequest.getTransactionID();
140
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400141 LOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400142 mRequest.dump();
143
144 // FIXME need to generalize this
Mike Lockwood438344f2010-08-03 15:30:09 -0400145 bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
Mike Lockwood8277cec2010-08-10 15:20:35 -0400146 || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
147 || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
148 || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400149 if (dataIn) {
150 int ret = mData.read(fd);
151 if (ret < 0) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400152 LOGE("data read returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400153 if (errno == ECANCELED) {
154 // return to top of loop and wait for next command
155 continue;
156 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400157 break;
158 }
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400159 LOGV("received data:");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400160 mData.dump();
161 } else {
162 mData.reset();
163 }
164
Mike Lockwood916076c2010-06-04 09:49:21 -0400165 if (handleRequest()) {
166 if (!dataIn && mData.hasData()) {
167 mData.setOperationCode(operation);
168 mData.setTransactionID(transaction);
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400169 LOGV("sending data:");
Mike Lockwood23d20712010-10-11 17:31:44 -0400170 mData.dump();
Mike Lockwood916076c2010-06-04 09:49:21 -0400171 ret = mData.write(fd);
172 if (ret < 0) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400173 LOGE("request write returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400174 if (errno == ECANCELED) {
175 // return to top of loop and wait for next command
176 continue;
177 }
178 break;
179 }
180 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400181
Mike Lockwood916076c2010-06-04 09:49:21 -0400182 mResponse.setTransactionID(transaction);
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400183 LOGV("sending response %04X", mResponse.getResponseCode());
Mike Lockwood916076c2010-06-04 09:49:21 -0400184 ret = mResponse.write(fd);
Mike Lockwood23d20712010-10-11 17:31:44 -0400185 mResponse.dump();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400186 if (ret < 0) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400187 LOGE("request write returned %d, errno: %d", ret, errno);
Mike Lockwood916076c2010-06-04 09:49:21 -0400188 if (errno == ECANCELED) {
189 // return to top of loop and wait for next command
190 continue;
191 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400192 break;
193 }
Mike Lockwood916076c2010-06-04 09:49:21 -0400194 } else {
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400195 LOGV("skipping response\n");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400196 }
197 }
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400198
199 if (mSessionOpen)
200 mDatabase->sessionEnded();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400201}
202
Mike Lockwood873871f2010-07-12 18:54:16 -0400203void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
Mike Lockwood73ecd232010-07-19 14:29:58 -0400204 if (mSessionOpen) {
205 LOGD("sendObjectAdded %d\n", handle);
206 mEvent.setEventCode(MTP_EVENT_OBJECT_ADDED);
207 mEvent.setTransactionID(mRequest.getTransactionID());
208 mEvent.setParameter(1, handle);
209 int ret = mEvent.write(mFD);
210 LOGD("mEvent.write returned %d\n", ret);
211 }
Mike Lockwood873871f2010-07-12 18:54:16 -0400212}
213
214void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
Mike Lockwood73ecd232010-07-19 14:29:58 -0400215 if (mSessionOpen) {
216 LOGD("sendObjectRemoved %d\n", handle);
217 mEvent.setEventCode(MTP_EVENT_OBJECT_REMOVED);
218 mEvent.setTransactionID(mRequest.getTransactionID());
219 mEvent.setParameter(1, handle);
220 int ret = mEvent.write(mFD);
221 LOGD("mEvent.write returned %d\n", ret);
222 }
Mike Lockwood873871f2010-07-12 18:54:16 -0400223}
224
Mike Lockwood916076c2010-06-04 09:49:21 -0400225bool MtpServer::handleRequest() {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400226 MtpOperationCode operation = mRequest.getOperationCode();
227 MtpResponseCode response;
228
229 mResponse.reset();
230
231 if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
232 // FIXME - need to delete mSendObjectHandle from the database
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400233 LOGE("expected SendObject after SendObjectInfo");
Mike Lockwood16864ba2010-05-11 17:16:59 -0400234 mSendObjectHandle = kInvalidObjectHandle;
235 }
236
237 switch (operation) {
238 case MTP_OPERATION_GET_DEVICE_INFO:
239 response = doGetDeviceInfo();
240 break;
241 case MTP_OPERATION_OPEN_SESSION:
242 response = doOpenSession();
243 break;
244 case MTP_OPERATION_CLOSE_SESSION:
245 response = doCloseSession();
246 break;
247 case MTP_OPERATION_GET_STORAGE_IDS:
248 response = doGetStorageIDs();
249 break;
250 case MTP_OPERATION_GET_STORAGE_INFO:
251 response = doGetStorageInfo();
252 break;
253 case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
254 response = doGetObjectPropsSupported();
255 break;
256 case MTP_OPERATION_GET_OBJECT_HANDLES:
257 response = doGetObjectHandles();
258 break;
Mike Lockwood343af4e2010-08-02 10:52:20 -0400259 case MTP_OPERATION_GET_NUM_OBJECTS:
260 response = doGetNumObjects();
261 break;
Mike Lockwood438344f2010-08-03 15:30:09 -0400262 case MTP_OPERATION_GET_OBJECT_REFERENCES:
263 response = doGetObjectReferences();
264 break;
265 case MTP_OPERATION_SET_OBJECT_REFERENCES:
266 response = doSetObjectReferences();
267 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400268 case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
269 response = doGetObjectPropValue();
270 break;
Mike Lockwood8277cec2010-08-10 15:20:35 -0400271 case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
272 response = doSetObjectPropValue();
273 break;
274 case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
275 response = doGetDevicePropValue();
276 break;
277 case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
278 response = doSetDevicePropValue();
279 break;
280 case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
281 response = doResetDevicePropValue();
282 break;
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400283 case MTP_OPERATION_GET_OBJECT_PROP_LIST:
284 response = doGetObjectPropList();
285 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400286 case MTP_OPERATION_GET_OBJECT_INFO:
287 response = doGetObjectInfo();
288 break;
289 case MTP_OPERATION_GET_OBJECT:
290 response = doGetObject();
291 break;
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500292 case MTP_OPERATION_GET_PARTIAL_OBJECT:
293 response = doGetPartialObject();
294 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400295 case MTP_OPERATION_SEND_OBJECT_INFO:
296 response = doSendObjectInfo();
297 break;
298 case MTP_OPERATION_SEND_OBJECT:
299 response = doSendObject();
300 break;
301 case MTP_OPERATION_DELETE_OBJECT:
302 response = doDeleteObject();
303 break;
304 case MTP_OPERATION_GET_OBJECT_PROP_DESC:
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400305 response = doGetObjectPropDesc();
306 break;
Mike Lockwoode3e76c42010-09-02 14:57:30 -0400307 case MTP_OPERATION_GET_DEVICE_PROP_DESC:
308 response = doGetDevicePropDesc();
309 break;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400310 default:
Mike Lockwooda881b442010-09-23 22:32:05 -0400311 LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400312 response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
313 break;
314 }
315
Mike Lockwood916076c2010-06-04 09:49:21 -0400316 if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
317 return false;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400318 mResponse.setResponseCode(response);
Mike Lockwood916076c2010-06-04 09:49:21 -0400319 return true;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400320}
321
322MtpResponseCode MtpServer::doGetDeviceInfo() {
323 MtpStringBuffer string;
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700324 char prop_value[PROPERTY_VALUE_MAX];
Mike Lockwood16864ba2010-05-11 17:16:59 -0400325
Mike Lockwood782aef12010-08-10 07:37:50 -0400326 MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
327 MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
328 MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
329
Mike Lockwood16864ba2010-05-11 17:16:59 -0400330 // fill in device info
331 mData.putUInt16(MTP_STANDARD_VERSION);
332 mData.putUInt32(6); // MTP Vendor Extension ID
333 mData.putUInt16(MTP_STANDARD_VERSION);
334 string.set("microsoft.com: 1.0;");
335 mData.putString(string); // MTP Extensions
336 mData.putUInt16(0); //Functional Mode
337 mData.putAUInt16(kSupportedOperationCodes,
338 sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
Mike Lockwood873871f2010-07-12 18:54:16 -0400339 mData.putAUInt16(kSupportedEventCodes,
340 sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
Mike Lockwood782aef12010-08-10 07:37:50 -0400341 mData.putAUInt16(deviceProperties); // Device Properties Supported
342 mData.putAUInt16(captureFormats); // Capture Formats
343 mData.putAUInt16(playbackFormats); // Playback Formats
Mike Lockwood16864ba2010-05-11 17:16:59 -0400344 // FIXME
345 string.set("Google, Inc.");
346 mData.putString(string); // Manufacturer
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700347
348 property_get("ro.product.model", prop_value, "MTP Device");
349 string.set(prop_value);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400350 mData.putString(string); // Model
351 string.set("1.0");
352 mData.putString(string); // Device Version
Mike Lockwoodc42aa122010-06-14 17:58:08 -0700353
354 property_get("ro.serialno", prop_value, "????????");
355 string.set(prop_value);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400356 mData.putString(string); // Serial Number
357
Mike Lockwood782aef12010-08-10 07:37:50 -0400358 delete playbackFormats;
359 delete captureFormats;
360 delete deviceProperties;
361
Mike Lockwood16864ba2010-05-11 17:16:59 -0400362 return MTP_RESPONSE_OK;
363}
364
365MtpResponseCode MtpServer::doOpenSession() {
366 if (mSessionOpen) {
367 mResponse.setParameter(1, mSessionID);
368 return MTP_RESPONSE_SESSION_ALREADY_OPEN;
369 }
370 mSessionID = mRequest.getParameter(1);
371 mSessionOpen = true;
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400372
373 mDatabase->sessionStarted();
374
Mike Lockwood16864ba2010-05-11 17:16:59 -0400375 return MTP_RESPONSE_OK;
376}
377
378MtpResponseCode MtpServer::doCloseSession() {
379 if (!mSessionOpen)
380 return MTP_RESPONSE_SESSION_NOT_OPEN;
381 mSessionID = 0;
382 mSessionOpen = false;
Mike Lockwood6b3a9d12010-08-31 16:25:12 -0400383 mDatabase->sessionEnded();
Mike Lockwood16864ba2010-05-11 17:16:59 -0400384 return MTP_RESPONSE_OK;
385}
386
387MtpResponseCode MtpServer::doGetStorageIDs() {
388 if (!mSessionOpen)
389 return MTP_RESPONSE_SESSION_NOT_OPEN;
390
391 int count = mStorages.size();
392 mData.putUInt32(count);
393 for (int i = 0; i < count; i++)
394 mData.putUInt32(mStorages[i]->getStorageID());
395
396 return MTP_RESPONSE_OK;
397}
398
399MtpResponseCode MtpServer::doGetStorageInfo() {
400 MtpStringBuffer string;
401
402 if (!mSessionOpen)
403 return MTP_RESPONSE_SESSION_NOT_OPEN;
404 MtpStorageID id = mRequest.getParameter(1);
405 MtpStorage* storage = getStorage(id);
406 if (!storage)
407 return MTP_RESPONSE_INVALID_STORAGE_ID;
408
409 mData.putUInt16(storage->getType());
410 mData.putUInt16(storage->getFileSystemType());
411 mData.putUInt16(storage->getAccessCapability());
412 mData.putUInt64(storage->getMaxCapacity());
413 mData.putUInt64(storage->getFreeSpace());
414 mData.putUInt32(1024*1024*1024); // Free Space in Objects
415 string.set(storage->getDescription());
416 mData.putString(string);
417 mData.putEmptyString(); // Volume Identifier
418
419 return MTP_RESPONSE_OK;
420}
421
422MtpResponseCode MtpServer::doGetObjectPropsSupported() {
423 if (!mSessionOpen)
424 return MTP_RESPONSE_SESSION_NOT_OPEN;
425 MtpObjectFormat format = mRequest.getParameter(1);
Mike Lockwood782aef12010-08-10 07:37:50 -0400426 MtpDevicePropertyList* properties = mDatabase->getSupportedObjectProperties(format);
427 mData.putAUInt16(properties);
Mike Lockwoodbf9b2052010-08-10 15:11:32 -0400428 delete properties;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400429 return MTP_RESPONSE_OK;
430}
431
432MtpResponseCode MtpServer::doGetObjectHandles() {
433 if (!mSessionOpen)
434 return MTP_RESPONSE_SESSION_NOT_OPEN;
435 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
Mike Lockwoode13401b2010-05-19 15:12:14 -0400436 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
Mike Lockwood16864ba2010-05-11 17:16:59 -0400437 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
438 // 0x00000000 for all objects?
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400439 if (parent == 0xFFFFFFFF)
440 parent = 0;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400441
442 MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
443 mData.putAUInt32(handles);
444 delete handles;
445 return MTP_RESPONSE_OK;
446}
447
Mike Lockwood343af4e2010-08-02 10:52:20 -0400448MtpResponseCode MtpServer::doGetNumObjects() {
449 if (!mSessionOpen)
450 return MTP_RESPONSE_SESSION_NOT_OPEN;
451 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
452 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
453 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
454 // 0x00000000 for all objects?
455 if (parent == 0xFFFFFFFF)
456 parent = 0;
457
458 int count = mDatabase->getNumObjects(storageID, format, parent);
459 if (count >= 0) {
460 mResponse.setParameter(1, count);
461 return MTP_RESPONSE_OK;
462 } else {
463 mResponse.setParameter(1, 0);
464 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
465 }
466}
467
Mike Lockwood438344f2010-08-03 15:30:09 -0400468MtpResponseCode MtpServer::doGetObjectReferences() {
469 if (!mSessionOpen)
470 return MTP_RESPONSE_SESSION_NOT_OPEN;
471 MtpStorageID handle = mRequest.getParameter(1);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400472
473 // FIXME - check for invalid object handle
Mike Lockwood438344f2010-08-03 15:30:09 -0400474 MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400475 if (handles) {
476 mData.putAUInt32(handles);
477 delete handles;
478 } else {
Mike Lockwood438344f2010-08-03 15:30:09 -0400479 mData.putEmptyArray();
Mike Lockwood438344f2010-08-03 15:30:09 -0400480 }
Mike Lockwood438344f2010-08-03 15:30:09 -0400481 return MTP_RESPONSE_OK;
482}
483
484MtpResponseCode MtpServer::doSetObjectReferences() {
485 if (!mSessionOpen)
486 return MTP_RESPONSE_SESSION_NOT_OPEN;
487 MtpStorageID handle = mRequest.getParameter(1);
488 MtpObjectHandleList* references = mData.getAUInt32();
489 MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
490 delete references;
491 return result;
492}
493
Mike Lockwood16864ba2010-05-11 17:16:59 -0400494MtpResponseCode MtpServer::doGetObjectPropValue() {
495 MtpObjectHandle handle = mRequest.getParameter(1);
496 MtpObjectProperty property = mRequest.getParameter(2);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400497 LOGD("GetObjectPropValue %d %s\n", handle,
498 MtpDebug::getObjectPropCodeName(property));
Mike Lockwood16864ba2010-05-11 17:16:59 -0400499
Mike Lockwood8277cec2010-08-10 15:20:35 -0400500 return mDatabase->getObjectPropertyValue(handle, property, mData);
501}
502
503MtpResponseCode MtpServer::doSetObjectPropValue() {
504 MtpObjectHandle handle = mRequest.getParameter(1);
505 MtpObjectProperty property = mRequest.getParameter(2);
506 LOGD("SetObjectPropValue %d %s\n", handle,
507 MtpDebug::getObjectPropCodeName(property));
508
509 return mDatabase->setObjectPropertyValue(handle, property, mData);
510}
511
512MtpResponseCode MtpServer::doGetDevicePropValue() {
513 MtpDeviceProperty property = mRequest.getParameter(1);
514 LOGD("GetDevicePropValue %s\n",
515 MtpDebug::getDevicePropCodeName(property));
516
517 return mDatabase->getDevicePropertyValue(property, mData);
518}
519
520MtpResponseCode MtpServer::doSetDevicePropValue() {
521 MtpDeviceProperty property = mRequest.getParameter(1);
522 LOGD("SetDevicePropValue %s\n",
523 MtpDebug::getDevicePropCodeName(property));
524
525 return mDatabase->setDevicePropertyValue(property, mData);
526}
527
528MtpResponseCode MtpServer::doResetDevicePropValue() {
529 MtpDeviceProperty property = mRequest.getParameter(1);
530 LOGD("ResetDevicePropValue %s\n",
531 MtpDebug::getDevicePropCodeName(property));
532
533 return mDatabase->resetDeviceProperty(property);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400534}
535
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400536MtpResponseCode MtpServer::doGetObjectPropList() {
537
538 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwood40ce1f22010-12-01 18:46:23 -0500539 // use uint32_t so we can support 0xFFFFFFFF
540 uint32_t format = mRequest.getParameter(2);
541 uint32_t property = mRequest.getParameter(3);
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400542 int groupCode = mRequest.getParameter(4);
Mike Lockwoodf05ff072010-11-23 18:45:25 -0500543 int depth = mRequest.getParameter(5);
Mike Lockwoodb6da06e2010-10-14 18:03:25 -0400544 LOGD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
545 handle, MtpDebug::getFormatCodeName(format),
546 MtpDebug::getObjectPropCodeName(property), groupCode, depth);
547
548 return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
549}
550
Mike Lockwood16864ba2010-05-11 17:16:59 -0400551MtpResponseCode MtpServer::doGetObjectInfo() {
552 MtpObjectHandle handle = mRequest.getParameter(1);
553 return mDatabase->getObjectInfo(handle, mData);
554}
555
556MtpResponseCode MtpServer::doGetObject() {
557 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400558 MtpString pathBuf;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400559 int64_t fileLength;
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400560 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength);
561 if (result != MTP_RESPONSE_OK)
562 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400563
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400564 const char* filePath = (const char *)pathBuf;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400565 mtp_file_range mfr;
Mike Lockwoodc6588762010-06-22 15:03:53 -0400566 mfr.fd = open(filePath, O_RDONLY);
567 if (mfr.fd < 0) {
568 return MTP_RESPONSE_GENERAL_ERROR;
569 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400570 mfr.offset = 0;
571 mfr.length = fileLength;
572
573 // send data header
574 mData.setOperationCode(mRequest.getOperationCode());
575 mData.setTransactionID(mRequest.getTransactionID());
Mike Lockwood23d20712010-10-11 17:31:44 -0400576 mData.writeDataHeader(mFD, fileLength + MTP_CONTAINER_HEADER_SIZE);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400577
578 // then transfer the file
579 int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400580 close(mfr.fd);
Mike Lockwood916076c2010-06-04 09:49:21 -0400581 if (ret < 0) {
582 if (errno == ECANCELED)
583 return MTP_RESPONSE_TRANSACTION_CANCELLED;
584 else
585 return MTP_RESPONSE_GENERAL_ERROR;
586 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400587 return MTP_RESPONSE_OK;
588}
589
Mike Lockwoodd81ce3c2010-11-23 09:08:01 -0500590MtpResponseCode MtpServer::doGetPartialObject() {
591 MtpObjectHandle handle = mRequest.getParameter(1);
592 uint32_t offset = mRequest.getParameter(2);
593 uint32_t length = mRequest.getParameter(3);
594 MtpString pathBuf;
595 int64_t fileLength;
596 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength);
597 if (result != MTP_RESPONSE_OK)
598 return result;
599 if (offset + length > fileLength)
600 length = fileLength - offset;
601
602 const char* filePath = (const char *)pathBuf;
603 mtp_file_range mfr;
604 mfr.fd = open(filePath, O_RDONLY);
605 if (mfr.fd < 0) {
606 return MTP_RESPONSE_GENERAL_ERROR;
607 }
608 mfr.offset = offset;
609 mfr.length = length;
610 mResponse.setParameter(1, length);
611
612 // send data header
613 mData.setOperationCode(mRequest.getOperationCode());
614 mData.setTransactionID(mRequest.getTransactionID());
615 mData.writeDataHeader(mFD, length + MTP_CONTAINER_HEADER_SIZE);
616
617 // then transfer the file
618 int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
619 close(mfr.fd);
620 if (ret < 0) {
621 if (errno == ECANCELED)
622 return MTP_RESPONSE_TRANSACTION_CANCELLED;
623 else
624 return MTP_RESPONSE_GENERAL_ERROR;
625 }
626 return MTP_RESPONSE_OK;
627}
628
Mike Lockwood16864ba2010-05-11 17:16:59 -0400629MtpResponseCode MtpServer::doSendObjectInfo() {
630 MtpString path;
631 MtpStorageID storageID = mRequest.getParameter(1);
632 MtpStorage* storage = getStorage(storageID);
633 MtpObjectHandle parent = mRequest.getParameter(2);
634 if (!storage)
635 return MTP_RESPONSE_INVALID_STORAGE_ID;
636
637 // special case the root
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400638 if (parent == MTP_PARENT_ROOT) {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400639 path = storage->getPath();
Mike Lockwood1865a5d2010-07-03 00:44:05 -0400640 parent = 0;
641 } else {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400642 int64_t dummy;
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400643 int result = mDatabase->getObjectFilePath(parent, path, dummy);
644 if (result != MTP_RESPONSE_OK)
645 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400646 }
647
648 // read only the fields we need
649 mData.getUInt32(); // storage ID
650 MtpObjectFormat format = mData.getUInt16();
651 mData.getUInt16(); // protection status
652 mSendObjectFileSize = mData.getUInt32();
653 mData.getUInt16(); // thumb format
654 mData.getUInt32(); // thumb compressed size
655 mData.getUInt32(); // thumb pix width
656 mData.getUInt32(); // thumb pix height
657 mData.getUInt32(); // image pix width
658 mData.getUInt32(); // image pix height
659 mData.getUInt32(); // image bit depth
660 mData.getUInt32(); // parent
661 uint16_t associationType = mData.getUInt16();
662 uint32_t associationDesc = mData.getUInt32(); // association desc
663 mData.getUInt32(); // sequence number
664 MtpStringBuffer name, created, modified;
665 mData.getString(name); // file name
666 mData.getString(created); // date created
667 mData.getString(modified); // date modified
668 // keywords follow
669
Mike Lockwoodf5b2ff22010-11-17 15:42:09 -0500670 LOGD("name: %s format: %04X\n", (const char *)name, format);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400671 time_t modifiedTime;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400672 if (!parseDateTime(modified, modifiedTime))
673 modifiedTime = 0;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400674
675 if (path[path.size() - 1] != '/')
676 path += "/";
677 path += (const char *)name;
678
Mike Lockwood4714b072010-07-12 08:49:01 -0400679 MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
680 format, parent, storageID, mSendObjectFileSize, modifiedTime);
Mike Lockwoodfceef462010-05-14 15:35:17 -0400681 if (handle == kInvalidObjectHandle) {
Mike Lockwood16864ba2010-05-11 17:16:59 -0400682 return MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwoodfceef462010-05-14 15:35:17 -0400683 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400684
685 if (format == MTP_FORMAT_ASSOCIATION) {
686 mode_t mask = umask(0);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400687 int ret = mkdir((const char *)path, mDirectoryPermission);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400688 umask(mask);
689 if (ret && ret != -EEXIST)
690 return MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400691 chown((const char *)path, getuid(), mFileGroup);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400692 } else {
693 mSendObjectFilePath = path;
694 // save the handle for the SendObject call, which should follow
695 mSendObjectHandle = handle;
Mike Lockwood4714b072010-07-12 08:49:01 -0400696 mSendObjectFormat = format;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400697 }
698
699 mResponse.setParameter(1, storageID);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400700 mResponse.setParameter(2, parent);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400701 mResponse.setParameter(3, handle);
702
703 return MTP_RESPONSE_OK;
704}
705
706MtpResponseCode MtpServer::doSendObject() {
Mike Lockwood4714b072010-07-12 08:49:01 -0400707 MtpResponseCode result = MTP_RESPONSE_OK;
708 mode_t mask;
709 int ret;
Mike Lockwoode1b8cf12010-11-16 17:38:43 -0500710 uint64_t actualSize = -1;
Mike Lockwood4714b072010-07-12 08:49:01 -0400711
Mike Lockwood16864ba2010-05-11 17:16:59 -0400712 if (mSendObjectHandle == kInvalidObjectHandle) {
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400713 LOGE("Expected SendObjectInfo before SendObject");
Mike Lockwood4714b072010-07-12 08:49:01 -0400714 result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
715 goto done;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400716 }
717
718 // read the header
Mike Lockwood4714b072010-07-12 08:49:01 -0400719 ret = mData.readDataHeader(mFD);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400720 // FIXME - check for errors here.
721
722 // reset so we don't attempt to send this back
723 mData.reset();
724
725 mtp_file_range mfr;
Mike Lockwoodc6588762010-06-22 15:03:53 -0400726 mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
727 if (mfr.fd < 0) {
Mike Lockwood4714b072010-07-12 08:49:01 -0400728 result = MTP_RESPONSE_GENERAL_ERROR;
729 goto done;
Mike Lockwoodc6588762010-06-22 15:03:53 -0400730 }
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400731 fchown(mfr.fd, getuid(), mFileGroup);
732 // set permissions
Mike Lockwood4714b072010-07-12 08:49:01 -0400733 mask = umask(0);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400734 fchmod(mfr.fd, mFilePermission);
735 umask(mask);
736
Mike Lockwood16864ba2010-05-11 17:16:59 -0400737 mfr.offset = 0;
738 mfr.length = mSendObjectFileSize;
739
Mike Lockwoodf5b2ff22010-11-17 15:42:09 -0500740 LOGD("receiving %s\n", (const char *)mSendObjectFilePath);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400741 // transfer the file
742 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
Mike Lockwoodc6588762010-06-22 15:03:53 -0400743 close(mfr.fd);
Mike Lockwood8e2a2802010-07-02 15:15:07 -0400744
Mike Lockwoodb14e5882010-06-29 18:11:52 -0400745 LOGV("MTP_RECEIVE_FILE returned %d", ret);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400746
Mike Lockwood916076c2010-06-04 09:49:21 -0400747 if (ret < 0) {
748 unlink(mSendObjectFilePath);
749 if (errno == ECANCELED)
Mike Lockwood4714b072010-07-12 08:49:01 -0400750 result = MTP_RESPONSE_TRANSACTION_CANCELLED;
Mike Lockwood916076c2010-06-04 09:49:21 -0400751 else
Mike Lockwood4714b072010-07-12 08:49:01 -0400752 result = MTP_RESPONSE_GENERAL_ERROR;
Mike Lockwoode1b8cf12010-11-16 17:38:43 -0500753 } else if (mSendObjectFileSize == 0xFFFFFFFF) {
754 // actual size is likely > 4 gig so stat the file to compute actual length
755 struct stat s;
756 if (lstat(mSendObjectFilePath, &s) == 0) {
757 actualSize = s.st_size;
758 LOGD("actualSize: %lld\n", actualSize);
759 }
Mike Lockwood916076c2010-06-04 09:49:21 -0400760 }
Mike Lockwood4714b072010-07-12 08:49:01 -0400761
762done:
763 mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
Mike Lockwoode1b8cf12010-11-16 17:38:43 -0500764 actualSize, result == MTP_RESPONSE_OK);
Mike Lockwood4714b072010-07-12 08:49:01 -0400765 mSendObjectHandle = kInvalidObjectHandle;
766 mSendObjectFormat = 0;
767 return result;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400768}
769
Mike Lockwoodd3211492010-09-13 17:15:58 -0400770static void deleteRecursive(const char* path) {
771 char pathbuf[PATH_MAX];
772 int pathLength = strlen(path);
773 if (pathLength >= sizeof(pathbuf) - 1) {
774 LOGE("path too long: %s\n", path);
775 }
776 strcpy(pathbuf, path);
777 if (pathbuf[pathLength - 1] != '/') {
778 pathbuf[pathLength++] = '/';
779 }
780 char* fileSpot = pathbuf + pathLength;
781 int pathRemaining = sizeof(pathbuf) - pathLength - 1;
782
783 DIR* dir = opendir(path);
784 if (!dir) {
785 LOGE("opendir %s failed: %s", path, strerror(errno));
786 return;
787 }
788
789 struct dirent* entry;
790 while ((entry = readdir(dir))) {
791 const char* name = entry->d_name;
792
793 // ignore "." and ".."
794 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
795 continue;
796 }
797
798 int nameLength = strlen(name);
799 if (nameLength > pathRemaining) {
800 LOGE("path %s/%s too long\n", path, name);
801 continue;
802 }
803 strcpy(fileSpot, name);
804
805 int type = entry->d_type;
806 if (entry->d_type == DT_DIR) {
807 deleteRecursive(pathbuf);
808 rmdir(pathbuf);
809 } else {
810 unlink(pathbuf);
811 }
812 }
Mike Lockwood7ce05cf2010-11-11 11:22:32 -0500813 closedir(dir);
Mike Lockwoodd3211492010-09-13 17:15:58 -0400814}
815
816static void deletePath(const char* path) {
817 struct stat statbuf;
818 if (stat(path, &statbuf) == 0) {
819 if (S_ISDIR(statbuf.st_mode)) {
820 deleteRecursive(path);
821 rmdir(path);
822 } else {
823 unlink(path);
824 }
825 } else {
826 LOGE("deletePath stat failed for %s: %s", path, strerror(errno));
827 }
828}
829
Mike Lockwood16864ba2010-05-11 17:16:59 -0400830MtpResponseCode MtpServer::doDeleteObject() {
831 MtpObjectHandle handle = mRequest.getParameter(1);
Mike Lockwoodd3211492010-09-13 17:15:58 -0400832 MtpObjectFormat format = mRequest.getParameter(2);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400833 // FIXME - support deleting all objects if handle is 0xFFFFFFFF
834 // FIXME - implement deleting objects by format
Mike Lockwood16864ba2010-05-11 17:16:59 -0400835
836 MtpString filePath;
837 int64_t fileLength;
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400838 int result = mDatabase->getObjectFilePath(handle, filePath, fileLength);
839 if (result == MTP_RESPONSE_OK) {
840 LOGV("deleting %s", (const char *)filePath);
Mike Lockwoodd3211492010-09-13 17:15:58 -0400841 deletePath((const char *)filePath);
Mike Lockwood9c04c4c2010-08-02 10:37:41 -0400842 return mDatabase->deleteFile(handle);
843 } else {
844 return result;
845 }
Mike Lockwood16864ba2010-05-11 17:16:59 -0400846}
847
848MtpResponseCode MtpServer::doGetObjectPropDesc() {
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400849 MtpObjectProperty propCode = mRequest.getParameter(1);
Mike Lockwood16864ba2010-05-11 17:16:59 -0400850 MtpObjectFormat format = mRequest.getParameter(2);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400851 LOGD("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
852 MtpDebug::getFormatCodeName(format));
853 MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400854 if (!property)
855 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400856 property->write(mData);
Mike Lockwood8277cec2010-08-10 15:20:35 -0400857 delete property;
858 return MTP_RESPONSE_OK;
859}
860
861MtpResponseCode MtpServer::doGetDevicePropDesc() {
862 MtpDeviceProperty propCode = mRequest.getParameter(1);
863 LOGD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
864 MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
865 if (!property)
866 return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
867 property->write(mData);
868 delete property;
Mike Lockwood21ef7d02010-06-30 17:00:35 -0400869 return MTP_RESPONSE_OK;
Mike Lockwood16864ba2010-05-11 17:16:59 -0400870}
Mike Lockwood7850ef92010-05-14 10:10:36 -0400871
872} // namespace android