blob: b900671c22fc052b1910f48bd250b1892306af29 [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 Lockwood775de952011-03-05 17:34:11 -050023import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040024import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040025import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050026import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040027import android.net.Uri;
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -040028import android.os.Environment;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040029import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050030import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040031import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040032import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040033import android.provider.MediaStore.Images;
34import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040035import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080036import android.view.Display;
37import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040038
Mike Lockwood5ebac832010-10-12 11:33:47 -040039import java.io.File;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050040import java.util.HashMap;
Mike Lockwood5ebac832010-10-12 11:33:47 -040041
Mike Lockwoodd21eac92010-07-03 00:44:05 -040042/**
43 * {@hide}
44 */
45public class MtpDatabase {
46
47 private static final String TAG = "MtpDatabase";
48
Mike Lockwood2837eef2010-08-31 16:25:12 -040049 private final Context mContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040050 private final IContentProvider mMediaProvider;
51 private final String mVolumeName;
52 private final Uri mObjectsUri;
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -040053 private final String mMediaStoragePath; // path to primary storage
54 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040055
Mike Lockwood7d7fb632010-12-01 18:46:23 -050056 // cached property groups for single properties
57 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
58 = new HashMap<Integer, MtpPropertyGroup>();
59
60 // cached property groups for all properties for a given format
61 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
62 = new HashMap<Integer, MtpPropertyGroup>();
63
Mike Lockwood2837eef2010-08-31 16:25:12 -040064 // true if the database has been modified in the current MTP session
65 private boolean mDatabaseModified;
66
Mike Lockwood775de952011-03-05 17:34:11 -050067 // SharedPreferences for writable MTP device properties
68 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040069 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
70
Mike Lockwoodd21eac92010-07-03 00:44:05 -040071 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040072 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040073 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040074 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040075 Files.FileColumns._ID, // 0
76 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040077 };
Mike Lockwood365e03e2010-12-08 16:08:01 -080078 private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040079 Files.FileColumns._ID, // 0
80 Files.FileColumns.DATA, // 1
81 Files.FileColumns.SIZE, // 2
Mike Lockwood365e03e2010-12-08 16:08:01 -080082 Files.FileColumns.FORMAT, // 3
Mike Lockwoodd21eac92010-07-03 00:44:05 -040083 };
84 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040085 Files.FileColumns._ID, // 0
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -040086 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040087 Files.FileColumns.FORMAT, // 2
88 Files.FileColumns.PARENT, // 3
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -040089 Files.FileColumns.DATA, // 4
90 Files.FileColumns.SIZE, // 5
91 Files.FileColumns.DATE_MODIFIED, // 6
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 Lockwoodd3e4290c2011-04-05 10:21:27 -040098 private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND "
99 + Files.FileColumns.STORAGE_ID + "=?";
100 private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND "
101 + Files.FileColumns.FORMAT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400102
Mike Lockwoodd815f792010-07-12 08:49:01 -0400103 private final MediaScanner mMediaScanner;
104
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400105 static {
106 System.loadLibrary("media_jni");
107 }
108
Mike Lockwood01788562010-10-11 11:22:19 -0400109 public MtpDatabase(Context context, String volumeName, String storagePath) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400110 native_setup();
111
Mike Lockwood2837eef2010-08-31 16:25:12 -0400112 mContext = context;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400113 mMediaProvider = context.getContentResolver().acquireProvider("media");
114 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400115 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400116 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400117 mMediaScanner = new MediaScanner(context);
Mike Lockwood775de952011-03-05 17:34:11 -0500118 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400119 }
120
121 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400122 protected void finalize() throws Throwable {
123 try {
124 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400125 } finally {
126 super.finalize();
127 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400128 }
129
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400130 public void addStorage(MtpStorage storage) {
131 mStorageMap.put(storage.getPath(), storage);
132 }
133
134 public void removeStorage(MtpStorage storage) {
135 mStorageMap.remove(storage.getPath());
136 }
137
Mike Lockwood775de952011-03-05 17:34:11 -0500138 private void initDeviceProperties(Context context) {
139 final String devicePropertiesName = "device-properties";
140 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
141 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400142
Mike Lockwood775de952011-03-05 17:34:11 -0500143 if (databaseFile.exists()) {
144 // for backward compatibility - read device properties from sqlite database
145 // and migrate them to shared prefs
146 SQLiteDatabase db = null;
147 Cursor c = null;
148 try {
149 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
150 if (db != null) {
151 c = db.query("properties", new String[] { "_id", "code", "value" },
152 null, null, null, null, null);
153 if (c != null) {
154 SharedPreferences.Editor e = mDeviceProperties.edit();
155 while (c.moveToNext()) {
156 String name = c.getString(1);
157 String value = c.getString(2);
158 e.putString(name, value);
159 }
160 e.commit();
161 }
162 }
163 } catch (Exception e) {
164 Log.e(TAG, "failed to migrate device properties", e);
165 } finally {
166 if (c != null) c.close();
167 if (db != null) db.close();
168 }
169 databaseFile.delete();
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400170 }
171 }
172
Mike Lockwoodd815f792010-07-12 08:49:01 -0400173 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400174 int storageId, long size, long modified) {
Mike Lockwoodbafca212010-12-13 21:50:09 -0800175 // first make sure the object does not exist
176 if (path != null) {
177 Cursor c = null;
178 try {
179 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
180 new String[] { path }, null);
181 if (c != null && c.getCount() > 0) {
182 Log.w(TAG, "file already exists in beginSendObject: " + path);
183 return -1;
184 }
185 } catch (RemoteException e) {
186 Log.e(TAG, "RemoteException in beginSendObject", e);
187 } finally {
188 if (c != null) {
189 c.close();
190 }
191 }
192 }
193
Mike Lockwood2837eef2010-08-31 16:25:12 -0400194 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400195 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400196 values.put(Files.FileColumns.DATA, path);
197 values.put(Files.FileColumns.FORMAT, format);
198 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400199 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400200 values.put(Files.FileColumns.SIZE, size);
201 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400202
203 try {
204 Uri uri = mMediaProvider.insert(mObjectsUri, values);
205 if (uri != null) {
206 return Integer.parseInt(uri.getPathSegments().get(2));
207 } else {
208 return -1;
209 }
210 } catch (RemoteException e) {
211 Log.e(TAG, "RemoteException in beginSendObject", e);
212 return -1;
213 }
214 }
215
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800216 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400217 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400218 // handle abstract playlists separately
219 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400220 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400221 // extract name from path
222 String name = path;
223 int lastSlash = name.lastIndexOf('/');
224 if (lastSlash >= 0) {
225 name = name.substring(lastSlash + 1);
226 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800227 // strip trailing ".pla" from the name
228 if (name.endsWith(".pla")) {
229 name = name.substring(0, name.length() - 4);
230 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400231
232 ContentValues values = new ContentValues(1);
233 values.put(Audio.Playlists.DATA, path);
234 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500235 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800236 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400237 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
238 try {
239 Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values);
240 } catch (RemoteException e) {
241 Log.e(TAG, "RemoteException in endSendObject", e);
242 }
243 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400244 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400245 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400246 } else {
247 deleteFile(handle);
248 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400249 }
250
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400251 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
252 if (storageID != 0) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400253 if (format != 0) {
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400254 return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
255 PARENT_STORAGE_FORMAT_WHERE,
256 new String[] { Integer.toString(parent), Integer.toString(storageID),
257 Integer.toString(format) }, null);
258 } else {
259 return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
260 PARENT_STORAGE_WHERE, new String[]
261 { Integer.toString(parent), Integer.toString(storageID) }, null);
262 }
263 } else {
264 if (format != 0) {
265 return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400266 PARENT_FORMAT_WHERE,
267 new String[] { Integer.toString(parent), Integer.toString(format) },
268 null);
269 } else {
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400270 return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400271 PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
272 }
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400273 }
274 }
275
276 private int[] getObjectList(int storageID, int format, int parent) {
277 Cursor c = null;
278 try {
279 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400280 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400281 return null;
282 }
283 int count = c.getCount();
284 if (count > 0) {
285 int[] result = new int[count];
286 for (int i = 0; i < count; i++) {
287 c.moveToNext();
288 result[i] = c.getInt(0);
289 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400290 return result;
291 }
292 } catch (RemoteException e) {
293 Log.e(TAG, "RemoteException in getObjectList", e);
294 } finally {
295 if (c != null) {
296 c.close();
297 }
298 }
299 return null;
300 }
301
Mike Lockwood7a047c82010-08-02 10:52:20 -0400302 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400303 Cursor c = null;
304 try {
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400305 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400306 if (c != null) {
307 return c.getCount();
308 }
309 } catch (RemoteException e) {
310 Log.e(TAG, "RemoteException in getNumObjects", e);
311 } finally {
312 if (c != null) {
313 c.close();
314 }
315 }
316 return -1;
317 }
318
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400319 private int[] getSupportedPlaybackFormats() {
320 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400321 // allow transfering arbitrary files
322 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400323
Mike Lockwood792ec842010-09-09 15:30:10 -0400324 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400325 MtpConstants.FORMAT_TEXT,
326 MtpConstants.FORMAT_HTML,
327 MtpConstants.FORMAT_WAV,
328 MtpConstants.FORMAT_MP3,
329 MtpConstants.FORMAT_MPEG,
330 MtpConstants.FORMAT_EXIF_JPEG,
331 MtpConstants.FORMAT_TIFF_EP,
332 MtpConstants.FORMAT_GIF,
333 MtpConstants.FORMAT_JFIF,
334 MtpConstants.FORMAT_PNG,
335 MtpConstants.FORMAT_TIFF,
336 MtpConstants.FORMAT_WMA,
337 MtpConstants.FORMAT_OGG,
338 MtpConstants.FORMAT_AAC,
339 MtpConstants.FORMAT_MP4_CONTAINER,
340 MtpConstants.FORMAT_MP2,
341 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400342 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400343 MtpConstants.FORMAT_WPL_PLAYLIST,
344 MtpConstants.FORMAT_M3U_PLAYLIST,
345 MtpConstants.FORMAT_PLS_PLAYLIST,
346 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800347 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400348 };
349 }
350
351 private int[] getSupportedCaptureFormats() {
352 // no capture formats yet
353 return null;
354 }
355
Mike Lockwoodae078f72010-09-26 12:35:51 -0400356 static final int[] FILE_PROPERTIES = {
357 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
358 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400359 MtpConstants.PROPERTY_STORAGE_ID,
360 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400361 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400362 MtpConstants.PROPERTY_OBJECT_SIZE,
363 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400364 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400365 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400366 MtpConstants.PROPERTY_PERSISTENT_UID,
367 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400368 MtpConstants.PROPERTY_DATE_ADDED,
369 };
370
371 static final int[] AUDIO_PROPERTIES = {
372 // NOTE must match FILE_PROPERTIES above
373 MtpConstants.PROPERTY_STORAGE_ID,
374 MtpConstants.PROPERTY_OBJECT_FORMAT,
375 MtpConstants.PROPERTY_PROTECTION_STATUS,
376 MtpConstants.PROPERTY_OBJECT_SIZE,
377 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
378 MtpConstants.PROPERTY_DATE_MODIFIED,
379 MtpConstants.PROPERTY_PARENT_OBJECT,
380 MtpConstants.PROPERTY_PERSISTENT_UID,
381 MtpConstants.PROPERTY_NAME,
382 MtpConstants.PROPERTY_DISPLAY_NAME,
383 MtpConstants.PROPERTY_DATE_ADDED,
384
385 // audio specific properties
386 MtpConstants.PROPERTY_ARTIST,
387 MtpConstants.PROPERTY_ALBUM_NAME,
388 MtpConstants.PROPERTY_ALBUM_ARTIST,
389 MtpConstants.PROPERTY_TRACK,
390 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
391 MtpConstants.PROPERTY_DURATION,
392 MtpConstants.PROPERTY_GENRE,
393 MtpConstants.PROPERTY_COMPOSER,
394 };
395
396 static final int[] VIDEO_PROPERTIES = {
397 // NOTE must match FILE_PROPERTIES above
398 MtpConstants.PROPERTY_STORAGE_ID,
399 MtpConstants.PROPERTY_OBJECT_FORMAT,
400 MtpConstants.PROPERTY_PROTECTION_STATUS,
401 MtpConstants.PROPERTY_OBJECT_SIZE,
402 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
403 MtpConstants.PROPERTY_DATE_MODIFIED,
404 MtpConstants.PROPERTY_PARENT_OBJECT,
405 MtpConstants.PROPERTY_PERSISTENT_UID,
406 MtpConstants.PROPERTY_NAME,
407 MtpConstants.PROPERTY_DISPLAY_NAME,
408 MtpConstants.PROPERTY_DATE_ADDED,
409
410 // video specific properties
411 MtpConstants.PROPERTY_ARTIST,
412 MtpConstants.PROPERTY_ALBUM_NAME,
413 MtpConstants.PROPERTY_DURATION,
414 MtpConstants.PROPERTY_DESCRIPTION,
415 };
416
417 static final int[] IMAGE_PROPERTIES = {
418 // NOTE must match FILE_PROPERTIES above
419 MtpConstants.PROPERTY_STORAGE_ID,
420 MtpConstants.PROPERTY_OBJECT_FORMAT,
421 MtpConstants.PROPERTY_PROTECTION_STATUS,
422 MtpConstants.PROPERTY_OBJECT_SIZE,
423 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
424 MtpConstants.PROPERTY_DATE_MODIFIED,
425 MtpConstants.PROPERTY_PARENT_OBJECT,
426 MtpConstants.PROPERTY_PERSISTENT_UID,
427 MtpConstants.PROPERTY_NAME,
428 MtpConstants.PROPERTY_DISPLAY_NAME,
429 MtpConstants.PROPERTY_DATE_ADDED,
430
431 // image specific properties
432 MtpConstants.PROPERTY_DESCRIPTION,
433 };
434
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500435 static final int[] ALL_PROPERTIES = {
436 // NOTE must match FILE_PROPERTIES above
437 MtpConstants.PROPERTY_STORAGE_ID,
438 MtpConstants.PROPERTY_OBJECT_FORMAT,
439 MtpConstants.PROPERTY_PROTECTION_STATUS,
440 MtpConstants.PROPERTY_OBJECT_SIZE,
441 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
442 MtpConstants.PROPERTY_DATE_MODIFIED,
443 MtpConstants.PROPERTY_PARENT_OBJECT,
444 MtpConstants.PROPERTY_PERSISTENT_UID,
445 MtpConstants.PROPERTY_NAME,
446 MtpConstants.PROPERTY_DISPLAY_NAME,
447 MtpConstants.PROPERTY_DATE_ADDED,
448
449 // image specific properties
450 MtpConstants.PROPERTY_DESCRIPTION,
451
452 // audio specific properties
453 MtpConstants.PROPERTY_ARTIST,
454 MtpConstants.PROPERTY_ALBUM_NAME,
455 MtpConstants.PROPERTY_ALBUM_ARTIST,
456 MtpConstants.PROPERTY_TRACK,
457 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
458 MtpConstants.PROPERTY_DURATION,
459 MtpConstants.PROPERTY_GENRE,
460 MtpConstants.PROPERTY_COMPOSER,
461
462 // video specific properties
463 MtpConstants.PROPERTY_ARTIST,
464 MtpConstants.PROPERTY_ALBUM_NAME,
465 MtpConstants.PROPERTY_DURATION,
466 MtpConstants.PROPERTY_DESCRIPTION,
467
468 // image specific properties
469 MtpConstants.PROPERTY_DESCRIPTION,
470 };
471
Mike Lockwoodae078f72010-09-26 12:35:51 -0400472 private int[] getSupportedObjectProperties(int format) {
473 switch (format) {
474 case MtpConstants.FORMAT_MP3:
475 case MtpConstants.FORMAT_WAV:
476 case MtpConstants.FORMAT_WMA:
477 case MtpConstants.FORMAT_OGG:
478 case MtpConstants.FORMAT_AAC:
479 return AUDIO_PROPERTIES;
480 case MtpConstants.FORMAT_MPEG:
481 case MtpConstants.FORMAT_3GP_CONTAINER:
482 case MtpConstants.FORMAT_WMV:
483 return VIDEO_PROPERTIES;
484 case MtpConstants.FORMAT_EXIF_JPEG:
485 case MtpConstants.FORMAT_GIF:
486 case MtpConstants.FORMAT_PNG:
487 case MtpConstants.FORMAT_BMP:
488 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500489 case 0:
490 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400491 default:
492 return FILE_PROPERTIES;
493 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400494 }
495
496 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400497 return new int[] {
498 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
499 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800500 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400501 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400502 }
503
Mike Lockwoodae078f72010-09-26 12:35:51 -0400504
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500505 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400506 int groupCode, int depth) {
507 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400508 if (groupCode != 0) {
509 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
510 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400511
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500512 MtpPropertyGroup propertyGroup;
513 if (property == 0xFFFFFFFFL) {
514 propertyGroup = mPropertyGroupsByFormat.get(format);
515 if (propertyGroup == null) {
516 int[] propertyList = getSupportedObjectProperties(format);
517 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
518 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400519 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500520 } else {
521 propertyGroup = mPropertyGroupsByProperty.get(property);
522 if (propertyGroup == null) {
523 int[] propertyList = new int[] { (int)property };
524 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
525 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400526 }
527 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500528
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400529 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400530 }
531
Mike Lockwood5ebac832010-10-12 11:33:47 -0400532 private int renameFile(int handle, String newName) {
533 Cursor c = null;
534
535 // first compute current path
536 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400537 String[] whereArgs = new String[] { Integer.toString(handle) };
538 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400539 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400540 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500541 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400542 }
543 } catch (RemoteException e) {
544 Log.e(TAG, "RemoteException in getObjectFilePath", e);
545 return MtpConstants.RESPONSE_GENERAL_ERROR;
546 } finally {
547 if (c != null) {
548 c.close();
549 }
550 }
551 if (path == null) {
552 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
553 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400554
555 // now rename the file. make sure this succeeds before updating database
556 File oldFile = new File(path);
557 int lastSlash = path.lastIndexOf('/');
558 if (lastSlash <= 1) {
559 return MtpConstants.RESPONSE_GENERAL_ERROR;
560 }
561 String newPath = path.substring(0, lastSlash + 1) + newName;
562 File newFile = new File(newPath);
563 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400564 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800565 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400566 return MtpConstants.RESPONSE_GENERAL_ERROR;
567 }
568
569 // finally update database
570 ContentValues values = new ContentValues();
571 values.put(Files.FileColumns.DATA, newPath);
572 int updated = 0;
573 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400574 // note - we are relying on a special case in MediaProvider.update() to update
575 // the paths for all children in the case where this is a directory.
Mike Lockwood5ebac832010-10-12 11:33:47 -0400576 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
577 } catch (RemoteException e) {
578 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
579 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400580 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400581 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
582 // this shouldn't happen, but if it does we need to rename the file to its original name
583 newFile.renameTo(oldFile);
584 return MtpConstants.RESPONSE_GENERAL_ERROR;
585 }
586
587 return MtpConstants.RESPONSE_OK;
588 }
589
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400590 private int setObjectProperty(int handle, int property,
591 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400592 switch (property) {
593 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
594 return renameFile(handle, stringValue);
595
596 default:
597 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
598 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400599 }
600
601 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400602 switch (property) {
603 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
604 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500605 // writable string properties kept in shared preferences
606 String value = mDeviceProperties.getString(Integer.toString(property), "");
607 int length = value.length();
608 if (length > 255) {
609 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400610 }
Mike Lockwood775de952011-03-05 17:34:11 -0500611 value.getChars(0, length, outStringValue, 0);
612 outStringValue[length] = 0;
613 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400614
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800615 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
616 // use screen size as max image size
617 Display display = ((WindowManager)mContext.getSystemService(
618 Context.WINDOW_SERVICE)).getDefaultDisplay();
619 int width = display.getWidth();
620 int height = display.getHeight();
621 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
622 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
623 outStringValue[imageSize.length()] = 0;
624 return MtpConstants.RESPONSE_OK;
625
626 default:
627 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
628 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400629 }
630
631 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400632 switch (property) {
633 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
634 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500635 // writable string properties kept in shared prefs
636 SharedPreferences.Editor e = mDeviceProperties.edit();
637 e.putString(Integer.toString(property), stringValue);
638 return (e.commit() ? MtpConstants.RESPONSE_OK
639 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400640 }
641
642 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
643 }
644
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400645 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
646 char[] outName, long[] outSizeModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400647 Cursor c = null;
648 try {
649 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
650 ID_WHERE, new String[] { Integer.toString(handle) }, null);
651 if (c != null && c.moveToNext()) {
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400652 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400653 outStorageFormatParent[1] = c.getInt(2);
654 outStorageFormatParent[2] = c.getInt(3);
655
656 // extract name from path
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400657 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400658 int lastSlash = path.lastIndexOf('/');
659 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
660 int end = path.length();
661 if (end - start > 255) {
662 end = start + 255;
663 }
664 path.getChars(start, end, outName, 0);
665 outName[end - start] = 0;
666
Mike Lockwoodd3e4290c2011-04-05 10:21:27 -0400667 outSizeModified[0] = c.getLong(5);
668 outSizeModified[1] = c.getLong(6);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400669 return true;
670 }
671 } catch (RemoteException e) {
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -0400672 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400673 } finally {
674 if (c != null) {
675 c.close();
676 }
677 }
678 return false;
679 }
680
Mike Lockwood365e03e2010-12-08 16:08:01 -0800681 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400682 if (handle == 0) {
683 // special case root directory
684 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
685 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800686 outFileLengthFormat[0] = 0;
687 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400688 return MtpConstants.RESPONSE_OK;
689 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400690 Cursor c = null;
691 try {
Mike Lockwood365e03e2010-12-08 16:08:01 -0800692 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION,
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400693 ID_WHERE, new String[] { Integer.toString(handle) }, null);
694 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500695 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400696 path.getChars(0, path.length(), outFilePath, 0);
697 outFilePath[path.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800698 outFileLengthFormat[0] = c.getLong(2);
699 outFileLengthFormat[1] = c.getLong(3);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400700 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400701 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400702 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400703 }
704 } catch (RemoteException e) {
705 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400706 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400707 } finally {
708 if (c != null) {
709 c.close();
710 }
711 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400712 }
713
Mike Lockwood59c777a2010-08-02 10:37:41 -0400714 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400715 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800716 String path = null;
717 int format = 0;
718
719 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400720 try {
Mike Lockwood55f808c2010-12-14 13:14:29 -0800721 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION,
722 ID_WHERE, new String[] { Integer.toString(handle) }, null);
723 if (c != null && c.moveToNext()) {
724 // don't convert to media path here, since we will be matching
725 // against paths in the database matching /data/media
726 path = c.getString(1);
727 format = c.getInt(3);
728 } else {
729 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
730 }
731
732 if (path == null || format == 0) {
733 return MtpConstants.RESPONSE_GENERAL_ERROR;
734 }
735
736 if (format == MtpConstants.FORMAT_ASSOCIATION) {
737 // recursive case - delete all children first
738 Uri uri = Files.getMtpObjectsUri(mVolumeName);
739 int count = mMediaProvider.delete(uri, "_data LIKE ?",
740 new String[] { path + "/%"});
741 }
742
743 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
744 if (mMediaProvider.delete(uri, null, null) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400745 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400746 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400747 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400748 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400749 } catch (RemoteException e) {
750 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400751 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800752 } finally {
753 if (c != null) {
754 c.close();
755 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400756 }
757 }
758
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400759 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -0400760 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400761 Cursor c = null;
762 try {
763 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null);
764 if (c == null) {
765 return null;
766 }
767 int count = c.getCount();
768 if (count > 0) {
769 int[] result = new int[count];
770 for (int i = 0; i < count; i++) {
771 c.moveToNext();
772 result[i] = c.getInt(0);
773 }
774 return result;
775 }
776 } catch (RemoteException e) {
777 Log.e(TAG, "RemoteException in getObjectList", e);
778 } finally {
779 if (c != null) {
780 c.close();
781 }
782 }
783 return null;
784 }
785
786 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400787 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -0400788 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400789 int count = references.length;
790 ContentValues[] valuesList = new ContentValues[count];
791 for (int i = 0; i < count; i++) {
792 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400793 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400794 valuesList[i] = values;
795 }
796 try {
Mike Lockwood7adfd182010-11-30 12:18:28 -0500797 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400798 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400799 }
800 } catch (RemoteException e) {
801 Log.e(TAG, "RemoteException in setObjectReferences", e);
802 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400803 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400804 }
805
Mike Lockwood2837eef2010-08-31 16:25:12 -0400806 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400807 mDatabaseModified = false;
808 }
809
810 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400811 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -0500812 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -0400813 mDatabaseModified = false;
814 }
815 }
816
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400817 // used by the JNI code
818 private int mNativeContext;
819
820 private native final void native_setup();
821 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400822}