blob: abc457ea07a6d4e00b9038782ccb573e86e90387 [file] [log] [blame]
Mike Lockwoodd21eac92010-07-03 00:44:05 -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
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040018
19import android.content.Context;
Mike Lockwoodd815f792010-07-12 08:49:01 -040020import android.content.ContentValues;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040021import android.content.IContentProvider;
Mike Lockwood2837eef2010-08-31 16:25:12 -040022import android.content.Intent;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040023import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040024import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050025import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040026import android.net.Uri;
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -040027import android.os.Environment;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040028import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050029import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040030import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040031import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040032import android.provider.MediaStore.Images;
33import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040034import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080035import android.view.Display;
36import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040037
Mike Lockwood5ebac832010-10-12 11:33:47 -040038import java.io.File;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050039import java.util.HashMap;
Mike Lockwood5ebac832010-10-12 11:33:47 -040040
Mike Lockwoodd21eac92010-07-03 00:44:05 -040041/**
42 * {@hide}
43 */
44public class MtpDatabase {
45
46 private static final String TAG = "MtpDatabase";
47
Mike Lockwood2837eef2010-08-31 16:25:12 -040048 private final Context mContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040049 private final IContentProvider mMediaProvider;
50 private final String mVolumeName;
51 private final Uri mObjectsUri;
Mike Lockwood01788562010-10-11 11:22:19 -040052 private final String mMediaStoragePath;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040053
Mike Lockwood7d7fb632010-12-01 18:46:23 -050054 // cached property groups for single properties
55 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
56 = new HashMap<Integer, MtpPropertyGroup>();
57
58 // cached property groups for all properties for a given format
59 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
60 = new HashMap<Integer, MtpPropertyGroup>();
61
Mike Lockwood2837eef2010-08-31 16:25:12 -040062 // true if the database has been modified in the current MTP session
63 private boolean mDatabaseModified;
64
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040065 // database for writable MTP device properties
66 private SQLiteDatabase mDevicePropDb;
67 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
68
Mike Lockwoodb2cada92010-07-14 15:44:06 -040069 // FIXME - this should be passed in via the constructor
70 private final int mStorageID = 0x00010001;
71
Mike Lockwoodd21eac92010-07-03 00:44:05 -040072 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040073 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040074 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040075 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040076 Files.FileColumns._ID, // 0
77 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040078 };
Mike Lockwood365e03e2010-12-08 16:08:01 -080079 private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040080 Files.FileColumns._ID, // 0
81 Files.FileColumns.DATA, // 1
82 Files.FileColumns.SIZE, // 2
Mike Lockwood365e03e2010-12-08 16:08:01 -080083 Files.FileColumns.FORMAT, // 3
Mike Lockwoodd21eac92010-07-03 00:44:05 -040084 };
85 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040086 Files.FileColumns._ID, // 0
87 Files.FileColumns.DATA, // 1
88 Files.FileColumns.FORMAT, // 2
89 Files.FileColumns.PARENT, // 3
90 Files.FileColumns.SIZE, // 4
91 Files.FileColumns.DATE_MODIFIED, // 5
Mike Lockwoodd21eac92010-07-03 00:44:05 -040092 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040093 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -080094 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040095 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -040096 private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040097 + Files.FileColumns.FORMAT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -040098
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040099 private static final String[] DEVICE_PROPERTY_PROJECTION = new String[] { "_id", "value" };
100 private static final String DEVICE_PROPERTY_WHERE = "code=?";
101
Mike Lockwoodd815f792010-07-12 08:49:01 -0400102 private final MediaScanner mMediaScanner;
103
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400104 static {
105 System.loadLibrary("media_jni");
106 }
107
Mike Lockwood01788562010-10-11 11:22:19 -0400108 public MtpDatabase(Context context, String volumeName, String storagePath) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400109 native_setup();
110
Mike Lockwood2837eef2010-08-31 16:25:12 -0400111 mContext = context;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400112 mMediaProvider = context.getContentResolver().acquireProvider("media");
113 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400114 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400115 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400116 mMediaScanner = new MediaScanner(context);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400117 openDevicePropertiesDatabase(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400118 }
119
120 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400121 protected void finalize() throws Throwable {
122 try {
123 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400124 } finally {
125 super.finalize();
126 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400127 }
128
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400129 private void openDevicePropertiesDatabase(Context context) {
130 mDevicePropDb = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
131 int version = mDevicePropDb.getVersion();
132
133 // initialize if necessary
134 if (version != DEVICE_PROPERTIES_DATABASE_VERSION) {
135 mDevicePropDb.execSQL("CREATE TABLE properties (" +
136 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
137 "code INTEGER UNIQUE ON CONFLICT REPLACE," +
138 "value TEXT" +
139 ");");
140 mDevicePropDb.execSQL("CREATE INDEX property_index ON properties (code);");
141 mDevicePropDb.setVersion(DEVICE_PROPERTIES_DATABASE_VERSION);
142 }
143 }
144
Mike Lockwoodd815f792010-07-12 08:49:01 -0400145 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400146 int storage, long size, long modified) {
Mike Lockwoodbafca212010-12-13 21:50:09 -0800147 // first make sure the object does not exist
148 if (path != null) {
149 Cursor c = null;
150 try {
151 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
152 new String[] { path }, null);
153 if (c != null && c.getCount() > 0) {
154 Log.w(TAG, "file already exists in beginSendObject: " + path);
155 return -1;
156 }
157 } catch (RemoteException e) {
158 Log.e(TAG, "RemoteException in beginSendObject", e);
159 } finally {
160 if (c != null) {
161 c.close();
162 }
163 }
164 }
165
Mike Lockwood2837eef2010-08-31 16:25:12 -0400166 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400167 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400168 values.put(Files.FileColumns.DATA, path);
169 values.put(Files.FileColumns.FORMAT, format);
170 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400171 // storage is ignored for now
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400172 values.put(Files.FileColumns.SIZE, size);
173 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400174
175 try {
176 Uri uri = mMediaProvider.insert(mObjectsUri, values);
177 if (uri != null) {
178 return Integer.parseInt(uri.getPathSegments().get(2));
179 } else {
180 return -1;
181 }
182 } catch (RemoteException e) {
183 Log.e(TAG, "RemoteException in beginSendObject", e);
184 return -1;
185 }
186 }
187
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800188 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400189 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400190 // handle abstract playlists separately
191 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400192 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400193 // extract name from path
194 String name = path;
195 int lastSlash = name.lastIndexOf('/');
196 if (lastSlash >= 0) {
197 name = name.substring(lastSlash + 1);
198 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800199 // strip trailing ".pla" from the name
200 if (name.endsWith(".pla")) {
201 name = name.substring(0, name.length() - 4);
202 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400203
204 ContentValues values = new ContentValues(1);
205 values.put(Audio.Playlists.DATA, path);
206 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500207 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800208 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400209 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
210 try {
211 Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values);
212 } catch (RemoteException e) {
213 Log.e(TAG, "RemoteException in endSendObject", e);
214 }
215 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400216 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400217 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400218 } else {
219 deleteFile(handle);
220 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400221 }
222
223 private int[] getObjectList(int storageID, int format, int parent) {
224 // we can ignore storageID until we support multiple storages
225 Log.d(TAG, "getObjectList parent: " + parent);
226 Cursor c = null;
227 try {
228 if (format != 0) {
229 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
230 PARENT_FORMAT_WHERE,
231 new String[] { Integer.toString(parent), Integer.toString(format) },
232 null);
233 } else {
234 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
235 PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
236 }
237 if (c == null) {
238 Log.d(TAG, "null cursor");
239 return null;
240 }
241 int count = c.getCount();
242 if (count > 0) {
243 int[] result = new int[count];
244 for (int i = 0; i < count; i++) {
245 c.moveToNext();
246 result[i] = c.getInt(0);
247 }
248 Log.d(TAG, "returning " + result);
249 return result;
250 }
251 } catch (RemoteException e) {
252 Log.e(TAG, "RemoteException in getObjectList", e);
253 } finally {
254 if (c != null) {
255 c.close();
256 }
257 }
258 return null;
259 }
260
Mike Lockwood7a047c82010-08-02 10:52:20 -0400261 private int getNumObjects(int storageID, int format, int parent) {
262 // we can ignore storageID until we support multiple storages
263 Log.d(TAG, "getObjectList parent: " + parent);
264 Cursor c = null;
265 try {
266 if (format != 0) {
267 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
268 PARENT_FORMAT_WHERE,
269 new String[] { Integer.toString(parent), Integer.toString(format) },
270 null);
271 } else {
272 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
273 PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
274 }
275 if (c != null) {
276 return c.getCount();
277 }
278 } catch (RemoteException e) {
279 Log.e(TAG, "RemoteException in getNumObjects", e);
280 } finally {
281 if (c != null) {
282 c.close();
283 }
284 }
285 return -1;
286 }
287
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400288 private int[] getSupportedPlaybackFormats() {
289 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400290 // allow transfering arbitrary files
291 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400292
Mike Lockwood792ec842010-09-09 15:30:10 -0400293 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400294 MtpConstants.FORMAT_TEXT,
295 MtpConstants.FORMAT_HTML,
296 MtpConstants.FORMAT_WAV,
297 MtpConstants.FORMAT_MP3,
298 MtpConstants.FORMAT_MPEG,
299 MtpConstants.FORMAT_EXIF_JPEG,
300 MtpConstants.FORMAT_TIFF_EP,
301 MtpConstants.FORMAT_GIF,
302 MtpConstants.FORMAT_JFIF,
303 MtpConstants.FORMAT_PNG,
304 MtpConstants.FORMAT_TIFF,
305 MtpConstants.FORMAT_WMA,
306 MtpConstants.FORMAT_OGG,
307 MtpConstants.FORMAT_AAC,
308 MtpConstants.FORMAT_MP4_CONTAINER,
309 MtpConstants.FORMAT_MP2,
310 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400311 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400312 MtpConstants.FORMAT_WPL_PLAYLIST,
313 MtpConstants.FORMAT_M3U_PLAYLIST,
314 MtpConstants.FORMAT_PLS_PLAYLIST,
315 MtpConstants.FORMAT_XML_DOCUMENT,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400316 };
317 }
318
319 private int[] getSupportedCaptureFormats() {
320 // no capture formats yet
321 return null;
322 }
323
Mike Lockwoodae078f72010-09-26 12:35:51 -0400324 static final int[] FILE_PROPERTIES = {
325 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
326 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400327 MtpConstants.PROPERTY_STORAGE_ID,
328 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400329 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400330 MtpConstants.PROPERTY_OBJECT_SIZE,
331 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400332 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400333 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400334 MtpConstants.PROPERTY_PERSISTENT_UID,
335 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400336 MtpConstants.PROPERTY_DATE_ADDED,
337 };
338
339 static final int[] AUDIO_PROPERTIES = {
340 // NOTE must match FILE_PROPERTIES above
341 MtpConstants.PROPERTY_STORAGE_ID,
342 MtpConstants.PROPERTY_OBJECT_FORMAT,
343 MtpConstants.PROPERTY_PROTECTION_STATUS,
344 MtpConstants.PROPERTY_OBJECT_SIZE,
345 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
346 MtpConstants.PROPERTY_DATE_MODIFIED,
347 MtpConstants.PROPERTY_PARENT_OBJECT,
348 MtpConstants.PROPERTY_PERSISTENT_UID,
349 MtpConstants.PROPERTY_NAME,
350 MtpConstants.PROPERTY_DISPLAY_NAME,
351 MtpConstants.PROPERTY_DATE_ADDED,
352
353 // audio specific properties
354 MtpConstants.PROPERTY_ARTIST,
355 MtpConstants.PROPERTY_ALBUM_NAME,
356 MtpConstants.PROPERTY_ALBUM_ARTIST,
357 MtpConstants.PROPERTY_TRACK,
358 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
359 MtpConstants.PROPERTY_DURATION,
360 MtpConstants.PROPERTY_GENRE,
361 MtpConstants.PROPERTY_COMPOSER,
362 };
363
364 static final int[] VIDEO_PROPERTIES = {
365 // NOTE must match FILE_PROPERTIES above
366 MtpConstants.PROPERTY_STORAGE_ID,
367 MtpConstants.PROPERTY_OBJECT_FORMAT,
368 MtpConstants.PROPERTY_PROTECTION_STATUS,
369 MtpConstants.PROPERTY_OBJECT_SIZE,
370 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
371 MtpConstants.PROPERTY_DATE_MODIFIED,
372 MtpConstants.PROPERTY_PARENT_OBJECT,
373 MtpConstants.PROPERTY_PERSISTENT_UID,
374 MtpConstants.PROPERTY_NAME,
375 MtpConstants.PROPERTY_DISPLAY_NAME,
376 MtpConstants.PROPERTY_DATE_ADDED,
377
378 // video specific properties
379 MtpConstants.PROPERTY_ARTIST,
380 MtpConstants.PROPERTY_ALBUM_NAME,
381 MtpConstants.PROPERTY_DURATION,
382 MtpConstants.PROPERTY_DESCRIPTION,
383 };
384
385 static final int[] IMAGE_PROPERTIES = {
386 // NOTE must match FILE_PROPERTIES above
387 MtpConstants.PROPERTY_STORAGE_ID,
388 MtpConstants.PROPERTY_OBJECT_FORMAT,
389 MtpConstants.PROPERTY_PROTECTION_STATUS,
390 MtpConstants.PROPERTY_OBJECT_SIZE,
391 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
392 MtpConstants.PROPERTY_DATE_MODIFIED,
393 MtpConstants.PROPERTY_PARENT_OBJECT,
394 MtpConstants.PROPERTY_PERSISTENT_UID,
395 MtpConstants.PROPERTY_NAME,
396 MtpConstants.PROPERTY_DISPLAY_NAME,
397 MtpConstants.PROPERTY_DATE_ADDED,
398
399 // image specific properties
400 MtpConstants.PROPERTY_DESCRIPTION,
401 };
402
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500403 static final int[] ALL_PROPERTIES = {
404 // NOTE must match FILE_PROPERTIES above
405 MtpConstants.PROPERTY_STORAGE_ID,
406 MtpConstants.PROPERTY_OBJECT_FORMAT,
407 MtpConstants.PROPERTY_PROTECTION_STATUS,
408 MtpConstants.PROPERTY_OBJECT_SIZE,
409 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
410 MtpConstants.PROPERTY_DATE_MODIFIED,
411 MtpConstants.PROPERTY_PARENT_OBJECT,
412 MtpConstants.PROPERTY_PERSISTENT_UID,
413 MtpConstants.PROPERTY_NAME,
414 MtpConstants.PROPERTY_DISPLAY_NAME,
415 MtpConstants.PROPERTY_DATE_ADDED,
416
417 // image specific properties
418 MtpConstants.PROPERTY_DESCRIPTION,
419
420 // audio specific properties
421 MtpConstants.PROPERTY_ARTIST,
422 MtpConstants.PROPERTY_ALBUM_NAME,
423 MtpConstants.PROPERTY_ALBUM_ARTIST,
424 MtpConstants.PROPERTY_TRACK,
425 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
426 MtpConstants.PROPERTY_DURATION,
427 MtpConstants.PROPERTY_GENRE,
428 MtpConstants.PROPERTY_COMPOSER,
429
430 // video specific properties
431 MtpConstants.PROPERTY_ARTIST,
432 MtpConstants.PROPERTY_ALBUM_NAME,
433 MtpConstants.PROPERTY_DURATION,
434 MtpConstants.PROPERTY_DESCRIPTION,
435
436 // image specific properties
437 MtpConstants.PROPERTY_DESCRIPTION,
438 };
439
Mike Lockwoodae078f72010-09-26 12:35:51 -0400440 private int[] getSupportedObjectProperties(int format) {
441 switch (format) {
442 case MtpConstants.FORMAT_MP3:
443 case MtpConstants.FORMAT_WAV:
444 case MtpConstants.FORMAT_WMA:
445 case MtpConstants.FORMAT_OGG:
446 case MtpConstants.FORMAT_AAC:
447 return AUDIO_PROPERTIES;
448 case MtpConstants.FORMAT_MPEG:
449 case MtpConstants.FORMAT_3GP_CONTAINER:
450 case MtpConstants.FORMAT_WMV:
451 return VIDEO_PROPERTIES;
452 case MtpConstants.FORMAT_EXIF_JPEG:
453 case MtpConstants.FORMAT_GIF:
454 case MtpConstants.FORMAT_PNG:
455 case MtpConstants.FORMAT_BMP:
456 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500457 case 0:
458 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400459 default:
460 return FILE_PROPERTIES;
461 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400462 }
463
464 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400465 return new int[] {
466 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
467 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800468 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400469 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400470 }
471
Mike Lockwoodae078f72010-09-26 12:35:51 -0400472
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500473 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400474 int groupCode, int depth) {
475 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400476 if (groupCode != 0) {
477 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
478 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400479
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500480 MtpPropertyGroup propertyGroup;
481 if (property == 0xFFFFFFFFL) {
482 propertyGroup = mPropertyGroupsByFormat.get(format);
483 if (propertyGroup == null) {
484 int[] propertyList = getSupportedObjectProperties(format);
485 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
486 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400487 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500488 } else {
489 propertyGroup = mPropertyGroupsByProperty.get(property);
490 if (propertyGroup == null) {
491 int[] propertyList = new int[] { (int)property };
492 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
493 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400494 }
495 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500496
497 return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400498 }
499
Mike Lockwood5ebac832010-10-12 11:33:47 -0400500 private int renameFile(int handle, String newName) {
501 Cursor c = null;
502
503 // first compute current path
504 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400505 String[] whereArgs = new String[] { Integer.toString(handle) };
506 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400507 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400508 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500509 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400510 }
511 } catch (RemoteException e) {
512 Log.e(TAG, "RemoteException in getObjectFilePath", e);
513 return MtpConstants.RESPONSE_GENERAL_ERROR;
514 } finally {
515 if (c != null) {
516 c.close();
517 }
518 }
519 if (path == null) {
520 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
521 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400522
523 // now rename the file. make sure this succeeds before updating database
524 File oldFile = new File(path);
525 int lastSlash = path.lastIndexOf('/');
526 if (lastSlash <= 1) {
527 return MtpConstants.RESPONSE_GENERAL_ERROR;
528 }
529 String newPath = path.substring(0, lastSlash + 1) + newName;
530 File newFile = new File(newPath);
531 boolean success = oldFile.renameTo(newFile);
532 Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed"));
533 if (!success) {
534 return MtpConstants.RESPONSE_GENERAL_ERROR;
535 }
536
537 // finally update database
538 ContentValues values = new ContentValues();
539 values.put(Files.FileColumns.DATA, newPath);
540 int updated = 0;
541 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400542 // note - we are relying on a special case in MediaProvider.update() to update
543 // the paths for all children in the case where this is a directory.
Mike Lockwood5ebac832010-10-12 11:33:47 -0400544 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
545 } catch (RemoteException e) {
546 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
547 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400548 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400549 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
550 // this shouldn't happen, but if it does we need to rename the file to its original name
551 newFile.renameTo(oldFile);
552 return MtpConstants.RESPONSE_GENERAL_ERROR;
553 }
554
555 return MtpConstants.RESPONSE_OK;
556 }
557
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400558 private int setObjectProperty(int handle, int property,
559 long intValue, String stringValue) {
560 Log.d(TAG, "setObjectProperty: " + property);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400561
562 switch (property) {
563 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
564 return renameFile(handle, stringValue);
565
566 default:
567 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
568 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400569 }
570
571 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
572 Log.d(TAG, "getDeviceProperty: " + property);
573
574 switch (property) {
575 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
576 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
577 // writable string properties kept in our device property database
578 Cursor c = null;
579 try {
580 c = mDevicePropDb.query("properties", DEVICE_PROPERTY_PROJECTION,
581 DEVICE_PROPERTY_WHERE, new String[] { Integer.toString(property) },
582 null, null, null);
583
584 if (c != null && c.moveToNext()) {
585 String value = c.getString(1);
586 int length = value.length();
587 if (length > 255) {
588 length = 255;
589 }
590 value.getChars(0, length, outStringValue, 0);
591 outStringValue[length] = 0;
592 } else {
593 outStringValue[0] = 0;
594 }
595 return MtpConstants.RESPONSE_OK;
596 } finally {
597 if (c != null) {
598 c.close();
599 }
600 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400601
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800602 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
603 // use screen size as max image size
604 Display display = ((WindowManager)mContext.getSystemService(
605 Context.WINDOW_SERVICE)).getDefaultDisplay();
606 int width = display.getWidth();
607 int height = display.getHeight();
608 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
609 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
610 outStringValue[imageSize.length()] = 0;
611 return MtpConstants.RESPONSE_OK;
612
613 default:
614 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
615 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400616 }
617
618 private int setDeviceProperty(int property, long intValue, String stringValue) {
619 Log.d(TAG, "setDeviceProperty: " + property + " : " + stringValue);
620
621 switch (property) {
622 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
623 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
624 // writable string properties kept in our device property database
625 try {
626 ContentValues values = new ContentValues();
627 values.put("code", property);
628 values.put("value", stringValue);
629 mDevicePropDb.insert("properties", "code", values);
630 return MtpConstants.RESPONSE_OK;
631 } catch (Exception e) {
632 return MtpConstants.RESPONSE_GENERAL_ERROR;
633 }
634 }
635
636 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
637 }
638
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400639 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
640 char[] outName, long[] outSizeModified) {
641 Log.d(TAG, "getObjectInfo: " + handle);
642 Cursor c = null;
643 try {
644 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
645 ID_WHERE, new String[] { Integer.toString(handle) }, null);
646 if (c != null && c.moveToNext()) {
Mike Lockwoodb2cada92010-07-14 15:44:06 -0400647 outStorageFormatParent[0] = mStorageID;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400648 outStorageFormatParent[1] = c.getInt(2);
649 outStorageFormatParent[2] = c.getInt(3);
650
651 // extract name from path
652 String path = c.getString(1);
653 int lastSlash = path.lastIndexOf('/');
654 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
655 int end = path.length();
656 if (end - start > 255) {
657 end = start + 255;
658 }
659 path.getChars(start, end, outName, 0);
660 outName[end - start] = 0;
661
662 outSizeModified[0] = c.getLong(4);
663 outSizeModified[1] = c.getLong(5);
664 return true;
665 }
666 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400667 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400668 } finally {
669 if (c != null) {
670 c.close();
671 }
672 }
673 return false;
674 }
675
Mike Lockwood365e03e2010-12-08 16:08:01 -0800676 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400677 Log.d(TAG, "getObjectFilePath: " + handle);
Mike Lockwood01788562010-10-11 11:22:19 -0400678 if (handle == 0) {
679 // special case root directory
680 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
681 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800682 outFileLengthFormat[0] = 0;
683 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400684 return MtpConstants.RESPONSE_OK;
685 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400686 Cursor c = null;
687 try {
Mike Lockwood365e03e2010-12-08 16:08:01 -0800688 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION,
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400689 ID_WHERE, new String[] { Integer.toString(handle) }, null);
690 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500691 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400692 path.getChars(0, path.length(), outFilePath, 0);
693 outFilePath[path.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800694 outFileLengthFormat[0] = c.getLong(2);
695 outFileLengthFormat[1] = c.getLong(3);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400696 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400697 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400698 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400699 }
700 } catch (RemoteException e) {
701 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400702 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400703 } finally {
704 if (c != null) {
705 c.close();
706 }
707 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400708 }
709
Mike Lockwood59c777a2010-08-02 10:37:41 -0400710 private int deleteFile(int handle) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400711 Log.d(TAG, "deleteFile: " + handle);
Mike Lockwood2837eef2010-08-31 16:25:12 -0400712 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800713 String path = null;
714 int format = 0;
715
716 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400717 try {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800718 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION,
719 ID_WHERE, new String[] { Integer.toString(handle) }, null);
720 if (c != null && c.moveToNext()) {
721 // don't convert to media path here, since we will be matching
722 // against paths in the database matching /data/media
723 path = c.getString(1);
724 format = c.getInt(3);
725 } else {
726 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
727 }
728
729 if (path == null || format == 0) {
730 return MtpConstants.RESPONSE_GENERAL_ERROR;
731 }
732
733 if (format == MtpConstants.FORMAT_ASSOCIATION) {
734 // recursive case - delete all children first
735 Uri uri = Files.getMtpObjectsUri(mVolumeName);
736 int count = mMediaProvider.delete(uri, "_data LIKE ?",
737 new String[] { path + "/%"});
738 }
739
740 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
741 if (mMediaProvider.delete(uri, null, null) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400742 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400743 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400744 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400745 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400746 } catch (RemoteException e) {
747 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400748 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800749 } finally {
750 if (c != null) {
751 c.close();
752 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400753 }
754 }
755
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400756 private int[] getObjectReferences(int handle) {
757 Log.d(TAG, "getObjectReferences for: " + handle);
Mike Lockwood8490e662010-09-09 14:16:22 -0400758 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400759 Cursor c = null;
760 try {
761 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null);
762 if (c == null) {
763 return null;
764 }
765 int count = c.getCount();
766 if (count > 0) {
767 int[] result = new int[count];
768 for (int i = 0; i < count; i++) {
769 c.moveToNext();
770 result[i] = c.getInt(0);
771 }
772 return result;
773 }
774 } catch (RemoteException e) {
775 Log.e(TAG, "RemoteException in getObjectList", e);
776 } finally {
777 if (c != null) {
778 c.close();
779 }
780 }
781 return null;
782 }
783
784 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400785 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -0400786 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400787 int count = references.length;
788 ContentValues[] valuesList = new ContentValues[count];
789 for (int i = 0; i < count; i++) {
790 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400791 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400792 valuesList[i] = values;
793 }
794 try {
Mike Lockwood7adfd182010-11-30 12:18:28 -0500795 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400796 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400797 }
798 } catch (RemoteException e) {
799 Log.e(TAG, "RemoteException in setObjectReferences", e);
800 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400801 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400802 }
803
Mike Lockwood2837eef2010-08-31 16:25:12 -0400804 private void sessionStarted() {
805 Log.d(TAG, "sessionStarted");
806 mDatabaseModified = false;
807 }
808
809 private void sessionEnded() {
810 Log.d(TAG, "sessionEnded");
811 if (mDatabaseModified) {
812 Log.d(TAG, "sending ACTION_MTP_SESSION_END");
Mike Lockwooda3156052010-11-20 12:28:27 -0500813 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -0400814 mDatabaseModified = false;
815 }
816 }
817
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400818 // used by the JNI code
819 private int mNativeContext;
820
821 private native final void native_setup();
822 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400823}