blob: 487585e96c46e48b43429cb2303dd16b56e7012d [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 Lockwood2b5f9ad12010-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;
dujin.chafe464a72011-11-22 12:13:33 +090041import java.util.Locale;
Mike Lockwood5ebac832010-10-12 11:33:47 -040042
Mike Lockwoodd21eac92010-07-03 00:44:05 -040043/**
44 * {@hide}
45 */
46public class MtpDatabase {
47
48 private static final String TAG = "MtpDatabase";
49
Mike Lockwood2837eef2010-08-31 16:25:12 -040050 private final Context mContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040051 private final IContentProvider mMediaProvider;
52 private final String mVolumeName;
53 private final Uri mObjectsUri;
Mike Lockwood73e56d92011-12-01 16:58:41 -050054 // path to primary storage
55 private final String mMediaStoragePath;
56 // if not null, restrict all queries to these subdirectories
57 private final String[] mSubDirectories;
58 // where clause for restricting queries to files in mSubDirectories
59 private String mSubDirectoriesWhere;
60 // where arguments for restricting queries to files in mSubDirectories
61 private String[] mSubDirectoriesWhereArgs;
62
Mike Lockwoodb239b6832011-04-05 10:21:27 -040063 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040064
Mike Lockwood7d7fb632010-12-01 18:46:23 -050065 // cached property groups for single properties
66 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
67 = new HashMap<Integer, MtpPropertyGroup>();
68
69 // cached property groups for all properties for a given format
70 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
71 = new HashMap<Integer, MtpPropertyGroup>();
72
Mike Lockwood2837eef2010-08-31 16:25:12 -040073 // true if the database has been modified in the current MTP session
74 private boolean mDatabaseModified;
75
Mike Lockwood775de952011-03-05 17:34:11 -050076 // SharedPreferences for writable MTP device properties
77 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040078 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
79
Mike Lockwoodd21eac92010-07-03 00:44:05 -040080 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040081 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040082 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040083 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040084 Files.FileColumns._ID, // 0
85 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040086 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070087 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040088 Files.FileColumns._ID, // 0
89 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070090 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040091 };
92 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040093 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -040094 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040095 Files.FileColumns.FORMAT, // 2
96 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -040097 Files.FileColumns.DATA, // 4
Mike Lockwoodf6f16612012-09-12 15:50:59 -070098 Files.FileColumns.DATE_MODIFIED, // 5
Mike Lockwoodd21eac92010-07-03 00:44:05 -040099 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400100 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800101 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400102
103 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700104 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
105 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400106 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400107 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400108 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
109 + Files.FileColumns.PARENT + "=?";
110 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
111 + Files.FileColumns.PARENT + "=?";
112 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
113 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400114
Mike Lockwoodd815f792010-07-12 08:49:01 -0400115 private final MediaScanner mMediaScanner;
116
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400117 static {
118 System.loadLibrary("media_jni");
119 }
120
Mike Lockwood73e56d92011-12-01 16:58:41 -0500121 public MtpDatabase(Context context, String volumeName, String storagePath,
122 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400123 native_setup();
124
Mike Lockwood2837eef2010-08-31 16:25:12 -0400125 mContext = context;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400126 mMediaProvider = context.getContentResolver().acquireProvider("media");
127 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400128 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400129 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400130 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900131
Mike Lockwood73e56d92011-12-01 16:58:41 -0500132 mSubDirectories = subDirectories;
133 if (subDirectories != null) {
134 // Compute "where" string for restricting queries to subdirectories
135 StringBuilder builder = new StringBuilder();
136 builder.append("(");
137 int count = subDirectories.length;
138 for (int i = 0; i < count; i++) {
139 builder.append(Files.FileColumns.DATA + "=? OR "
140 + Files.FileColumns.DATA + " LIKE ?");
141 if (i != count - 1) {
142 builder.append(" OR ");
143 }
144 }
145 builder.append(")");
146 mSubDirectoriesWhere = builder.toString();
147
148 // Compute "where" arguments for restricting queries to subdirectories
149 mSubDirectoriesWhereArgs = new String[count * 2];
150 for (int i = 0, j = 0; i < count; i++) {
151 String path = subDirectories[i];
152 mSubDirectoriesWhereArgs[j++] = path;
153 mSubDirectoriesWhereArgs[j++] = path + "/%";
154 }
155 }
156
dujin.chafe464a72011-11-22 12:13:33 +0900157 // Set locale to MediaScanner.
158 Locale locale = context.getResources().getConfiguration().locale;
159 if (locale != null) {
160 String language = locale.getLanguage();
161 String country = locale.getCountry();
162 if (language != null) {
163 if (country != null) {
164 mMediaScanner.setLocale(language + "_" + country);
165 } else {
166 mMediaScanner.setLocale(language);
167 }
168 }
169 }
Mike Lockwood775de952011-03-05 17:34:11 -0500170 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400171 }
172
173 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400174 protected void finalize() throws Throwable {
175 try {
176 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400177 } finally {
178 super.finalize();
179 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400180 }
181
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400182 public void addStorage(MtpStorage storage) {
183 mStorageMap.put(storage.getPath(), storage);
184 }
185
186 public void removeStorage(MtpStorage storage) {
187 mStorageMap.remove(storage.getPath());
188 }
189
Mike Lockwood775de952011-03-05 17:34:11 -0500190 private void initDeviceProperties(Context context) {
191 final String devicePropertiesName = "device-properties";
192 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
193 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400194
Mike Lockwood775de952011-03-05 17:34:11 -0500195 if (databaseFile.exists()) {
196 // for backward compatibility - read device properties from sqlite database
197 // and migrate them to shared prefs
198 SQLiteDatabase db = null;
199 Cursor c = null;
200 try {
201 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
202 if (db != null) {
203 c = db.query("properties", new String[] { "_id", "code", "value" },
204 null, null, null, null, null);
205 if (c != null) {
206 SharedPreferences.Editor e = mDeviceProperties.edit();
207 while (c.moveToNext()) {
208 String name = c.getString(1);
209 String value = c.getString(2);
210 e.putString(name, value);
211 }
212 e.commit();
213 }
214 }
215 } catch (Exception e) {
216 Log.e(TAG, "failed to migrate device properties", e);
217 } finally {
218 if (c != null) c.close();
219 if (db != null) db.close();
220 }
221 databaseFile.delete();
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400222 }
223 }
224
Mike Lockwood73e56d92011-12-01 16:58:41 -0500225 // check to see if the path is contained in one of our storage subdirectories
226 // returns true if we have no special subdirectories
227 private boolean inStorageSubDirectory(String path) {
228 if (mSubDirectories == null) return true;
229 if (path == null) return false;
230
231 boolean allowed = false;
232 int pathLength = path.length();
233 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
234 String subdir = mSubDirectories[i];
235 int subdirLength = subdir.length();
236 if (subdirLength < pathLength &&
237 path.charAt(subdirLength) == '/' &&
238 path.startsWith(subdir)) {
239 allowed = true;
240 }
241 }
242 return allowed;
243 }
244
245 // check to see if the path matches one of our storage subdirectories
246 // returns true if we have no special subdirectories
247 private boolean isStorageSubDirectory(String path) {
248 if (mSubDirectories == null) return false;
249 for (int i = 0; i < mSubDirectories.length; i++) {
250 if (path.equals(mSubDirectories[i])) {
251 return true;
252 }
253 }
254 return false;
255 }
256
Mike Lockwoodd815f792010-07-12 08:49:01 -0400257 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400258 int storageId, long size, long modified) {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500259 // if mSubDirectories is not null, do not allow copying files to any other locations
260 if (!inStorageSubDirectory(path)) return -1;
261
262 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800263 if (path != null) {
264 Cursor c = null;
265 try {
266 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800267 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800268 if (c != null && c.getCount() > 0) {
269 Log.w(TAG, "file already exists in beginSendObject: " + path);
270 return -1;
271 }
272 } catch (RemoteException e) {
273 Log.e(TAG, "RemoteException in beginSendObject", e);
274 } finally {
275 if (c != null) {
276 c.close();
277 }
278 }
279 }
280
Mike Lockwood2837eef2010-08-31 16:25:12 -0400281 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400282 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400283 values.put(Files.FileColumns.DATA, path);
284 values.put(Files.FileColumns.FORMAT, format);
285 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400286 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400287 values.put(Files.FileColumns.SIZE, size);
288 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400289
290 try {
291 Uri uri = mMediaProvider.insert(mObjectsUri, values);
292 if (uri != null) {
293 return Integer.parseInt(uri.getPathSegments().get(2));
294 } else {
295 return -1;
296 }
297 } catch (RemoteException e) {
298 Log.e(TAG, "RemoteException in beginSendObject", e);
299 return -1;
300 }
301 }
302
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800303 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400304 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400305 // handle abstract playlists separately
306 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400307 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400308 // extract name from path
309 String name = path;
310 int lastSlash = name.lastIndexOf('/');
311 if (lastSlash >= 0) {
312 name = name.substring(lastSlash + 1);
313 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800314 // strip trailing ".pla" from the name
315 if (name.endsWith(".pla")) {
316 name = name.substring(0, name.length() - 4);
317 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400318
319 ContentValues values = new ContentValues(1);
320 values.put(Audio.Playlists.DATA, path);
321 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500322 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800323 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400324 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
325 try {
326 Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values);
327 } catch (RemoteException e) {
328 Log.e(TAG, "RemoteException in endSendObject", e);
329 }
330 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400331 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400332 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400333 } else {
334 deleteFile(handle);
335 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400336 }
337
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400338 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500339 String where;
340 String[] whereArgs;
341
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400342 if (storageID == 0xFFFFFFFF) {
343 // query all stores
344 if (format == 0) {
345 // query all formats
346 if (parent == 0) {
347 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500348 where = null;
349 whereArgs = null;
350 } else {
351 if (parent == 0xFFFFFFFF) {
352 // all objects in root of store
353 parent = 0;
354 }
355 where = PARENT_WHERE;
356 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400357 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400358 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400359 // query specific format
360 if (parent == 0) {
361 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500362 where = FORMAT_WHERE;
363 whereArgs = new String[] { Integer.toString(format) };
364 } else {
365 if (parent == 0xFFFFFFFF) {
366 // all objects in root of store
367 parent = 0;
368 }
369 where = FORMAT_PARENT_WHERE;
370 whereArgs = new String[] { Integer.toString(format),
371 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400372 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400373 }
374 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400375 // query specific store
376 if (format == 0) {
377 // query all formats
378 if (parent == 0) {
379 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500380 where = STORAGE_WHERE;
381 whereArgs = new String[] { Integer.toString(storageID) };
382 } else {
383 if (parent == 0xFFFFFFFF) {
384 // all objects in root of store
385 parent = 0;
386 }
387 where = STORAGE_PARENT_WHERE;
388 whereArgs = new String[] { Integer.toString(storageID),
389 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400390 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400391 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400392 // query specific format
393 if (parent == 0) {
394 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500395 where = STORAGE_FORMAT_WHERE;
396 whereArgs = new String[] { Integer.toString(storageID),
397 Integer.toString(format) };
398 } else {
399 if (parent == 0xFFFFFFFF) {
400 // all objects in root of store
401 parent = 0;
402 }
403 where = STORAGE_FORMAT_PARENT_WHERE;
404 whereArgs = new String[] { Integer.toString(storageID),
405 Integer.toString(format),
406 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400407 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400408 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400409 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500410
411 // if we are restricting queries to mSubDirectories, we need to add the restriction
412 // onto our "where" arguments
413 if (mSubDirectoriesWhere != null) {
414 if (where == null) {
415 where = mSubDirectoriesWhere;
416 whereArgs = mSubDirectoriesWhereArgs;
417 } else {
418 where = where + " AND " + mSubDirectoriesWhere;
419
420 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
421 String[] newWhereArgs =
422 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
423 int i, j;
424 for (i = 0; i < whereArgs.length; i++) {
425 newWhereArgs[i] = whereArgs[i];
426 }
427 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
428 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
429 }
430 whereArgs = newWhereArgs;
431 }
432 }
433
Jeff Brown75ea64f2012-01-25 19:37:13 -0800434 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where, whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400435 }
436
437 private int[] getObjectList(int storageID, int format, int parent) {
438 Cursor c = null;
439 try {
440 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400441 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400442 return null;
443 }
444 int count = c.getCount();
445 if (count > 0) {
446 int[] result = new int[count];
447 for (int i = 0; i < count; i++) {
448 c.moveToNext();
449 result[i] = c.getInt(0);
450 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400451 return result;
452 }
453 } catch (RemoteException e) {
454 Log.e(TAG, "RemoteException in getObjectList", e);
455 } finally {
456 if (c != null) {
457 c.close();
458 }
459 }
460 return null;
461 }
462
Mike Lockwood7a047c82010-08-02 10:52:20 -0400463 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400464 Cursor c = null;
465 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400466 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400467 if (c != null) {
468 return c.getCount();
469 }
470 } catch (RemoteException e) {
471 Log.e(TAG, "RemoteException in getNumObjects", e);
472 } finally {
473 if (c != null) {
474 c.close();
475 }
476 }
477 return -1;
478 }
479
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400480 private int[] getSupportedPlaybackFormats() {
481 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400482 // allow transfering arbitrary files
483 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400484
Mike Lockwood792ec842010-09-09 15:30:10 -0400485 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400486 MtpConstants.FORMAT_TEXT,
487 MtpConstants.FORMAT_HTML,
488 MtpConstants.FORMAT_WAV,
489 MtpConstants.FORMAT_MP3,
490 MtpConstants.FORMAT_MPEG,
491 MtpConstants.FORMAT_EXIF_JPEG,
492 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800493 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400494 MtpConstants.FORMAT_GIF,
495 MtpConstants.FORMAT_JFIF,
496 MtpConstants.FORMAT_PNG,
497 MtpConstants.FORMAT_TIFF,
498 MtpConstants.FORMAT_WMA,
499 MtpConstants.FORMAT_OGG,
500 MtpConstants.FORMAT_AAC,
501 MtpConstants.FORMAT_MP4_CONTAINER,
502 MtpConstants.FORMAT_MP2,
503 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400504 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400505 MtpConstants.FORMAT_WPL_PLAYLIST,
506 MtpConstants.FORMAT_M3U_PLAYLIST,
507 MtpConstants.FORMAT_PLS_PLAYLIST,
508 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800509 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400510 };
511 }
512
513 private int[] getSupportedCaptureFormats() {
514 // no capture formats yet
515 return null;
516 }
517
Mike Lockwoodae078f72010-09-26 12:35:51 -0400518 static final int[] FILE_PROPERTIES = {
519 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
520 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400521 MtpConstants.PROPERTY_STORAGE_ID,
522 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400523 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400524 MtpConstants.PROPERTY_OBJECT_SIZE,
525 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400526 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400527 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400528 MtpConstants.PROPERTY_PERSISTENT_UID,
529 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400530 MtpConstants.PROPERTY_DATE_ADDED,
531 };
532
533 static final int[] AUDIO_PROPERTIES = {
534 // NOTE must match FILE_PROPERTIES above
535 MtpConstants.PROPERTY_STORAGE_ID,
536 MtpConstants.PROPERTY_OBJECT_FORMAT,
537 MtpConstants.PROPERTY_PROTECTION_STATUS,
538 MtpConstants.PROPERTY_OBJECT_SIZE,
539 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
540 MtpConstants.PROPERTY_DATE_MODIFIED,
541 MtpConstants.PROPERTY_PARENT_OBJECT,
542 MtpConstants.PROPERTY_PERSISTENT_UID,
543 MtpConstants.PROPERTY_NAME,
544 MtpConstants.PROPERTY_DISPLAY_NAME,
545 MtpConstants.PROPERTY_DATE_ADDED,
546
547 // audio specific properties
548 MtpConstants.PROPERTY_ARTIST,
549 MtpConstants.PROPERTY_ALBUM_NAME,
550 MtpConstants.PROPERTY_ALBUM_ARTIST,
551 MtpConstants.PROPERTY_TRACK,
552 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
553 MtpConstants.PROPERTY_DURATION,
554 MtpConstants.PROPERTY_GENRE,
555 MtpConstants.PROPERTY_COMPOSER,
556 };
557
558 static final int[] VIDEO_PROPERTIES = {
559 // NOTE must match FILE_PROPERTIES above
560 MtpConstants.PROPERTY_STORAGE_ID,
561 MtpConstants.PROPERTY_OBJECT_FORMAT,
562 MtpConstants.PROPERTY_PROTECTION_STATUS,
563 MtpConstants.PROPERTY_OBJECT_SIZE,
564 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
565 MtpConstants.PROPERTY_DATE_MODIFIED,
566 MtpConstants.PROPERTY_PARENT_OBJECT,
567 MtpConstants.PROPERTY_PERSISTENT_UID,
568 MtpConstants.PROPERTY_NAME,
569 MtpConstants.PROPERTY_DISPLAY_NAME,
570 MtpConstants.PROPERTY_DATE_ADDED,
571
572 // video specific properties
573 MtpConstants.PROPERTY_ARTIST,
574 MtpConstants.PROPERTY_ALBUM_NAME,
575 MtpConstants.PROPERTY_DURATION,
576 MtpConstants.PROPERTY_DESCRIPTION,
577 };
578
579 static final int[] IMAGE_PROPERTIES = {
580 // NOTE must match FILE_PROPERTIES above
581 MtpConstants.PROPERTY_STORAGE_ID,
582 MtpConstants.PROPERTY_OBJECT_FORMAT,
583 MtpConstants.PROPERTY_PROTECTION_STATUS,
584 MtpConstants.PROPERTY_OBJECT_SIZE,
585 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
586 MtpConstants.PROPERTY_DATE_MODIFIED,
587 MtpConstants.PROPERTY_PARENT_OBJECT,
588 MtpConstants.PROPERTY_PERSISTENT_UID,
589 MtpConstants.PROPERTY_NAME,
590 MtpConstants.PROPERTY_DISPLAY_NAME,
591 MtpConstants.PROPERTY_DATE_ADDED,
592
593 // image specific properties
594 MtpConstants.PROPERTY_DESCRIPTION,
595 };
596
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500597 static final int[] ALL_PROPERTIES = {
598 // NOTE must match FILE_PROPERTIES above
599 MtpConstants.PROPERTY_STORAGE_ID,
600 MtpConstants.PROPERTY_OBJECT_FORMAT,
601 MtpConstants.PROPERTY_PROTECTION_STATUS,
602 MtpConstants.PROPERTY_OBJECT_SIZE,
603 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
604 MtpConstants.PROPERTY_DATE_MODIFIED,
605 MtpConstants.PROPERTY_PARENT_OBJECT,
606 MtpConstants.PROPERTY_PERSISTENT_UID,
607 MtpConstants.PROPERTY_NAME,
608 MtpConstants.PROPERTY_DISPLAY_NAME,
609 MtpConstants.PROPERTY_DATE_ADDED,
610
611 // image specific properties
612 MtpConstants.PROPERTY_DESCRIPTION,
613
614 // audio specific properties
615 MtpConstants.PROPERTY_ARTIST,
616 MtpConstants.PROPERTY_ALBUM_NAME,
617 MtpConstants.PROPERTY_ALBUM_ARTIST,
618 MtpConstants.PROPERTY_TRACK,
619 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
620 MtpConstants.PROPERTY_DURATION,
621 MtpConstants.PROPERTY_GENRE,
622 MtpConstants.PROPERTY_COMPOSER,
623
624 // video specific properties
625 MtpConstants.PROPERTY_ARTIST,
626 MtpConstants.PROPERTY_ALBUM_NAME,
627 MtpConstants.PROPERTY_DURATION,
628 MtpConstants.PROPERTY_DESCRIPTION,
629
630 // image specific properties
631 MtpConstants.PROPERTY_DESCRIPTION,
632 };
633
Mike Lockwoodae078f72010-09-26 12:35:51 -0400634 private int[] getSupportedObjectProperties(int format) {
635 switch (format) {
636 case MtpConstants.FORMAT_MP3:
637 case MtpConstants.FORMAT_WAV:
638 case MtpConstants.FORMAT_WMA:
639 case MtpConstants.FORMAT_OGG:
640 case MtpConstants.FORMAT_AAC:
641 return AUDIO_PROPERTIES;
642 case MtpConstants.FORMAT_MPEG:
643 case MtpConstants.FORMAT_3GP_CONTAINER:
644 case MtpConstants.FORMAT_WMV:
645 return VIDEO_PROPERTIES;
646 case MtpConstants.FORMAT_EXIF_JPEG:
647 case MtpConstants.FORMAT_GIF:
648 case MtpConstants.FORMAT_PNG:
649 case MtpConstants.FORMAT_BMP:
650 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500651 case 0:
652 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400653 default:
654 return FILE_PROPERTIES;
655 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400656 }
657
658 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400659 return new int[] {
660 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
661 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800662 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400663 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400664 }
665
Mike Lockwoodae078f72010-09-26 12:35:51 -0400666
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500667 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400668 int groupCode, int depth) {
669 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400670 if (groupCode != 0) {
671 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
672 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400673
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500674 MtpPropertyGroup propertyGroup;
675 if (property == 0xFFFFFFFFL) {
676 propertyGroup = mPropertyGroupsByFormat.get(format);
677 if (propertyGroup == null) {
678 int[] propertyList = getSupportedObjectProperties(format);
679 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
680 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400681 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500682 } else {
683 propertyGroup = mPropertyGroupsByProperty.get(property);
684 if (propertyGroup == null) {
685 int[] propertyList = new int[] { (int)property };
686 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList);
687 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400688 }
689 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500690
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400691 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400692 }
693
Mike Lockwood5ebac832010-10-12 11:33:47 -0400694 private int renameFile(int handle, String newName) {
695 Cursor c = null;
696
697 // first compute current path
698 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400699 String[] whereArgs = new String[] { Integer.toString(handle) };
700 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800701 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400702 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500703 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400704 }
705 } catch (RemoteException e) {
706 Log.e(TAG, "RemoteException in getObjectFilePath", e);
707 return MtpConstants.RESPONSE_GENERAL_ERROR;
708 } finally {
709 if (c != null) {
710 c.close();
711 }
712 }
713 if (path == null) {
714 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
715 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400716
Mike Lockwood73e56d92011-12-01 16:58:41 -0500717 // do not allow renaming any of the special subdirectories
718 if (isStorageSubDirectory(path)) {
719 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
720 }
721
Mike Lockwood5ebac832010-10-12 11:33:47 -0400722 // now rename the file. make sure this succeeds before updating database
723 File oldFile = new File(path);
724 int lastSlash = path.lastIndexOf('/');
725 if (lastSlash <= 1) {
726 return MtpConstants.RESPONSE_GENERAL_ERROR;
727 }
728 String newPath = path.substring(0, lastSlash + 1) + newName;
729 File newFile = new File(newPath);
730 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400731 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800732 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400733 return MtpConstants.RESPONSE_GENERAL_ERROR;
734 }
735
736 // finally update database
737 ContentValues values = new ContentValues();
738 values.put(Files.FileColumns.DATA, newPath);
739 int updated = 0;
740 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400741 // note - we are relying on a special case in MediaProvider.update() to update
742 // the paths for all children in the case where this is a directory.
Mike Lockwood5ebac832010-10-12 11:33:47 -0400743 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
744 } catch (RemoteException e) {
745 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
746 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400747 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400748 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
749 // this shouldn't happen, but if it does we need to rename the file to its original name
750 newFile.renameTo(oldFile);
751 return MtpConstants.RESPONSE_GENERAL_ERROR;
752 }
753
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800754 // check if nomedia status changed
755 if (newFile.isDirectory()) {
756 // for directories, check if renamed from something hidden to something non-hidden
757 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
758 // directory was unhidden
759 try {
760 mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
761 } catch (RemoteException e) {
762 Log.e(TAG, "failed to unhide/rescan for " + newPath);
763 }
764 }
765 } else {
766 // for files, check if renamed from .nomedia to something else
767 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
768 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
769 try {
770 mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
771 } catch (RemoteException e) {
772 Log.e(TAG, "failed to unhide/rescan for " + newPath);
773 }
774 }
775 }
776
Mike Lockwood5ebac832010-10-12 11:33:47 -0400777 return MtpConstants.RESPONSE_OK;
778 }
779
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400780 private int setObjectProperty(int handle, int property,
781 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400782 switch (property) {
783 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
784 return renameFile(handle, stringValue);
785
786 default:
787 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
788 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400789 }
790
791 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400792 switch (property) {
793 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
794 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500795 // writable string properties kept in shared preferences
796 String value = mDeviceProperties.getString(Integer.toString(property), "");
797 int length = value.length();
798 if (length > 255) {
799 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400800 }
Mike Lockwood775de952011-03-05 17:34:11 -0500801 value.getChars(0, length, outStringValue, 0);
802 outStringValue[length] = 0;
803 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400804
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800805 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
806 // use screen size as max image size
807 Display display = ((WindowManager)mContext.getSystemService(
808 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700809 int width = display.getMaximumSizeDimension();
810 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800811 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
812 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
813 outStringValue[imageSize.length()] = 0;
814 return MtpConstants.RESPONSE_OK;
815
816 default:
817 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
818 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400819 }
820
821 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400822 switch (property) {
823 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
824 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500825 // writable string properties kept in shared prefs
826 SharedPreferences.Editor e = mDeviceProperties.edit();
827 e.putString(Integer.toString(property), stringValue);
828 return (e.commit() ? MtpConstants.RESPONSE_OK
829 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400830 }
831
832 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
833 }
834
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400835 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700836 char[] outName, long[] outModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400837 Cursor c = null;
838 try {
839 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800840 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400841 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400842 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400843 outStorageFormatParent[1] = c.getInt(2);
844 outStorageFormatParent[2] = c.getInt(3);
845
846 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400847 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400848 int lastSlash = path.lastIndexOf('/');
849 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
850 int end = path.length();
851 if (end - start > 255) {
852 end = start + 255;
853 }
854 path.getChars(start, end, outName, 0);
855 outName[end - start] = 0;
856
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700857 outModified[0] = c.getLong(5);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400858 return true;
859 }
860 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400861 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400862 } finally {
863 if (c != null) {
864 c.close();
865 }
866 }
867 return false;
868 }
869
Mike Lockwood365e03e2010-12-08 16:08:01 -0800870 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400871 if (handle == 0) {
872 // special case root directory
873 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
874 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800875 outFileLengthFormat[0] = 0;
876 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400877 return MtpConstants.RESPONSE_OK;
878 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400879 Cursor c = null;
880 try {
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700881 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800882 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400883 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500884 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400885 path.getChars(0, path.length(), outFilePath, 0);
886 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700887 // File transfers from device to host will likely fail if the size is incorrect.
888 // So to be safe, use the actual file size here.
889 outFileLengthFormat[0] = new File(path).length();
890 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400891 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400892 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400893 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400894 }
895 } catch (RemoteException e) {
896 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400897 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400898 } finally {
899 if (c != null) {
900 c.close();
901 }
902 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400903 }
904
Mike Lockwood59c777a2010-08-02 10:37:41 -0400905 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400906 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800907 String path = null;
908 int format = 0;
909
910 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400911 try {
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700912 c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800913 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800914 if (c != null && c.moveToNext()) {
915 // don't convert to media path here, since we will be matching
916 // against paths in the database matching /data/media
917 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700918 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800919 } else {
920 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
921 }
922
923 if (path == null || format == 0) {
924 return MtpConstants.RESPONSE_GENERAL_ERROR;
925 }
926
Mike Lockwood73e56d92011-12-01 16:58:41 -0500927 // do not allow deleting any of the special subdirectories
928 if (isStorageSubDirectory(path)) {
929 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
930 }
931
Mike Lockwood55f808c2010-12-14 13:14:29 -0800932 if (format == MtpConstants.FORMAT_ASSOCIATION) {
933 // recursive case - delete all children first
934 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Marco Nelissenb4b84782012-05-22 15:39:38 -0700935 int count = mMediaProvider.delete(uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -0700936 // the 'like' makes it use the index, the 'lower()' makes it correct
937 // when the path contains sqlite wildcard characters
938 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
939 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800940 }
941
942 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
943 if (mMediaProvider.delete(uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800944 if (format != MtpConstants.FORMAT_ASSOCIATION
945 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
946 try {
947 String parentPath = path.substring(0, path.lastIndexOf("/"));
948 mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
949 } catch (RemoteException e) {
950 Log.e(TAG, "failed to unhide/rescan for " + path);
951 }
952 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400953 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400954 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400955 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400956 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400957 } catch (RemoteException e) {
958 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400959 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800960 } finally {
961 if (c != null) {
962 c.close();
963 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400964 }
965 }
966
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400967 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -0400968 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400969 Cursor c = null;
970 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800971 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400972 if (c == null) {
973 return null;
974 }
975 int count = c.getCount();
976 if (count > 0) {
977 int[] result = new int[count];
978 for (int i = 0; i < count; i++) {
979 c.moveToNext();
980 result[i] = c.getInt(0);
981 }
982 return result;
983 }
984 } catch (RemoteException e) {
985 Log.e(TAG, "RemoteException in getObjectList", e);
986 } finally {
987 if (c != null) {
988 c.close();
989 }
990 }
991 return null;
992 }
993
994 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400995 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -0400996 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400997 int count = references.length;
998 ContentValues[] valuesList = new ContentValues[count];
999 for (int i = 0; i < count; i++) {
1000 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001001 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001002 valuesList[i] = values;
1003 }
1004 try {
Mike Lockwood7adfd182010-11-30 12:18:28 -05001005 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001006 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001007 }
1008 } catch (RemoteException e) {
1009 Log.e(TAG, "RemoteException in setObjectReferences", e);
1010 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001011 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001012 }
1013
Mike Lockwood2837eef2010-08-31 16:25:12 -04001014 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001015 mDatabaseModified = false;
1016 }
1017
1018 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001019 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001020 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001021 mDatabaseModified = false;
1022 }
1023 }
1024
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001025 // used by the JNI code
1026 private int mNativeContext;
1027
1028 private native final void native_setup();
1029 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001030}