blob: cc464dba5d8a30c638f179fda03f9655e830e86b [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;
28import 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.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040033import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080034import android.view.Display;
35import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040036
Mike Lockwood5ebac832010-10-12 11:33:47 -040037import java.io.File;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050038import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090039import java.util.Locale;
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;
Dianne Hackborn35654b62013-01-14 17:38:02 -080049 private final String mPackageName;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040050 private final IContentProvider mMediaProvider;
51 private final String mVolumeName;
52 private final Uri mObjectsUri;
Mike Lockwood73e56d92011-12-01 16:58:41 -050053 // path to primary storage
54 private final String mMediaStoragePath;
55 // if not null, restrict all queries to these subdirectories
56 private final String[] mSubDirectories;
57 // where clause for restricting queries to files in mSubDirectories
58 private String mSubDirectoriesWhere;
59 // where arguments for restricting queries to files in mSubDirectories
60 private String[] mSubDirectoriesWhereArgs;
61
Mike Lockwoodb239b6832011-04-05 10:21:27 -040062 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040063
Mike Lockwood7d7fb632010-12-01 18:46:23 -050064 // cached property groups for single properties
65 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
66 = new HashMap<Integer, MtpPropertyGroup>();
67
68 // cached property groups for all properties for a given format
69 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
70 = new HashMap<Integer, MtpPropertyGroup>();
71
Mike Lockwood2837eef2010-08-31 16:25:12 -040072 // true if the database has been modified in the current MTP session
73 private boolean mDatabaseModified;
74
Mike Lockwood775de952011-03-05 17:34:11 -050075 // SharedPreferences for writable MTP device properties
76 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040077 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
78
Mike Lockwoodd21eac92010-07-03 00:44:05 -040079 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040080 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040081 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040082 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040083 Files.FileColumns._ID, // 0
84 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040085 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070086 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040087 Files.FileColumns._ID, // 0
88 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070089 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040090 };
91 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040092 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -040093 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040094 Files.FileColumns.FORMAT, // 2
95 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -040096 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -070097 Files.FileColumns.DATE_ADDED, // 5
98 Files.FileColumns.DATE_MODIFIED, // 6
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 Lockwood58e68312012-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;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800126 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400127 mMediaProvider = context.getContentResolver().acquireProvider("media");
128 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400129 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400130 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400131 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900132
Mike Lockwood73e56d92011-12-01 16:58:41 -0500133 mSubDirectories = subDirectories;
134 if (subDirectories != null) {
135 // Compute "where" string for restricting queries to subdirectories
136 StringBuilder builder = new StringBuilder();
137 builder.append("(");
138 int count = subDirectories.length;
139 for (int i = 0; i < count; i++) {
140 builder.append(Files.FileColumns.DATA + "=? OR "
141 + Files.FileColumns.DATA + " LIKE ?");
142 if (i != count - 1) {
143 builder.append(" OR ");
144 }
145 }
146 builder.append(")");
147 mSubDirectoriesWhere = builder.toString();
148
149 // Compute "where" arguments for restricting queries to subdirectories
150 mSubDirectoriesWhereArgs = new String[count * 2];
151 for (int i = 0, j = 0; i < count; i++) {
152 String path = subDirectories[i];
153 mSubDirectoriesWhereArgs[j++] = path;
154 mSubDirectoriesWhereArgs[j++] = path + "/%";
155 }
156 }
157
dujin.chafe464a72011-11-22 12:13:33 +0900158 // Set locale to MediaScanner.
159 Locale locale = context.getResources().getConfiguration().locale;
160 if (locale != null) {
161 String language = locale.getLanguage();
162 String country = locale.getCountry();
163 if (language != null) {
164 if (country != null) {
165 mMediaScanner.setLocale(language + "_" + country);
166 } else {
167 mMediaScanner.setLocale(language);
168 }
169 }
170 }
Mike Lockwood775de952011-03-05 17:34:11 -0500171 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400172 }
173
174 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400175 protected void finalize() throws Throwable {
176 try {
177 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400178 } finally {
179 super.finalize();
180 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400181 }
182
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400183 public void addStorage(MtpStorage storage) {
184 mStorageMap.put(storage.getPath(), storage);
185 }
186
187 public void removeStorage(MtpStorage storage) {
188 mStorageMap.remove(storage.getPath());
189 }
190
Mike Lockwood775de952011-03-05 17:34:11 -0500191 private void initDeviceProperties(Context context) {
192 final String devicePropertiesName = "device-properties";
193 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
194 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400195
Mike Lockwood775de952011-03-05 17:34:11 -0500196 if (databaseFile.exists()) {
197 // for backward compatibility - read device properties from sqlite database
198 // and migrate them to shared prefs
199 SQLiteDatabase db = null;
200 Cursor c = null;
201 try {
202 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
203 if (db != null) {
204 c = db.query("properties", new String[] { "_id", "code", "value" },
205 null, null, null, null, null);
206 if (c != null) {
207 SharedPreferences.Editor e = mDeviceProperties.edit();
208 while (c.moveToNext()) {
209 String name = c.getString(1);
210 String value = c.getString(2);
211 e.putString(name, value);
212 }
213 e.commit();
214 }
215 }
216 } catch (Exception e) {
217 Log.e(TAG, "failed to migrate device properties", e);
218 } finally {
219 if (c != null) c.close();
220 if (db != null) db.close();
221 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900222 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400223 }
224 }
225
Mike Lockwood73e56d92011-12-01 16:58:41 -0500226 // check to see if the path is contained in one of our storage subdirectories
227 // returns true if we have no special subdirectories
228 private boolean inStorageSubDirectory(String path) {
229 if (mSubDirectories == null) return true;
230 if (path == null) return false;
231
232 boolean allowed = false;
233 int pathLength = path.length();
234 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
235 String subdir = mSubDirectories[i];
236 int subdirLength = subdir.length();
237 if (subdirLength < pathLength &&
238 path.charAt(subdirLength) == '/' &&
239 path.startsWith(subdir)) {
240 allowed = true;
241 }
242 }
243 return allowed;
244 }
245
246 // check to see if the path matches one of our storage subdirectories
247 // returns true if we have no special subdirectories
248 private boolean isStorageSubDirectory(String path) {
249 if (mSubDirectories == null) return false;
250 for (int i = 0; i < mSubDirectories.length; i++) {
251 if (path.equals(mSubDirectories[i])) {
252 return true;
253 }
254 }
255 return false;
256 }
257
Mike Lockwoodd815f792010-07-12 08:49:01 -0400258 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400259 int storageId, long size, long modified) {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500260 // if mSubDirectories is not null, do not allow copying files to any other locations
261 if (!inStorageSubDirectory(path)) return -1;
262
263 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800264 if (path != null) {
265 Cursor c = null;
266 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800267 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800268 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800269 if (c != null && c.getCount() > 0) {
270 Log.w(TAG, "file already exists in beginSendObject: " + path);
271 return -1;
272 }
273 } catch (RemoteException e) {
274 Log.e(TAG, "RemoteException in beginSendObject", e);
275 } finally {
276 if (c != null) {
277 c.close();
278 }
279 }
280 }
281
Mike Lockwood2837eef2010-08-31 16:25:12 -0400282 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400283 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400284 values.put(Files.FileColumns.DATA, path);
285 values.put(Files.FileColumns.FORMAT, format);
286 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400287 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400288 values.put(Files.FileColumns.SIZE, size);
289 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400290
291 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800292 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400293 if (uri != null) {
294 return Integer.parseInt(uri.getPathSegments().get(2));
295 } else {
296 return -1;
297 }
298 } catch (RemoteException e) {
299 Log.e(TAG, "RemoteException in beginSendObject", e);
300 return -1;
301 }
302 }
303
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800304 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400305 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400306 // handle abstract playlists separately
307 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400308 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400309 // extract name from path
310 String name = path;
311 int lastSlash = name.lastIndexOf('/');
312 if (lastSlash >= 0) {
313 name = name.substring(lastSlash + 1);
314 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800315 // strip trailing ".pla" from the name
316 if (name.endsWith(".pla")) {
317 name = name.substring(0, name.length() - 4);
318 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400319
320 ContentValues values = new ContentValues(1);
321 values.put(Audio.Playlists.DATA, path);
322 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500323 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800324 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400325 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
326 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800327 Uri uri = mMediaProvider.insert(mPackageName,
328 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400329 } catch (RemoteException e) {
330 Log.e(TAG, "RemoteException in endSendObject", e);
331 }
332 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400333 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400334 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400335 } else {
336 deleteFile(handle);
337 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400338 }
339
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400340 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500341 String where;
342 String[] whereArgs;
343
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400344 if (storageID == 0xFFFFFFFF) {
345 // query all stores
346 if (format == 0) {
347 // query all formats
348 if (parent == 0) {
349 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500350 where = null;
351 whereArgs = null;
352 } else {
353 if (parent == 0xFFFFFFFF) {
354 // all objects in root of store
355 parent = 0;
356 }
357 where = PARENT_WHERE;
358 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400359 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400360 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400361 // query specific format
362 if (parent == 0) {
363 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500364 where = FORMAT_WHERE;
365 whereArgs = new String[] { Integer.toString(format) };
366 } else {
367 if (parent == 0xFFFFFFFF) {
368 // all objects in root of store
369 parent = 0;
370 }
371 where = FORMAT_PARENT_WHERE;
372 whereArgs = new String[] { Integer.toString(format),
373 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400374 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400375 }
376 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400377 // query specific store
378 if (format == 0) {
379 // query all formats
380 if (parent == 0) {
381 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500382 where = STORAGE_WHERE;
383 whereArgs = new String[] { Integer.toString(storageID) };
384 } else {
385 if (parent == 0xFFFFFFFF) {
386 // all objects in root of store
387 parent = 0;
388 }
389 where = STORAGE_PARENT_WHERE;
390 whereArgs = new String[] { Integer.toString(storageID),
391 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400392 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400393 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400394 // query specific format
395 if (parent == 0) {
396 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500397 where = STORAGE_FORMAT_WHERE;
398 whereArgs = new String[] { Integer.toString(storageID),
399 Integer.toString(format) };
400 } else {
401 if (parent == 0xFFFFFFFF) {
402 // all objects in root of store
403 parent = 0;
404 }
405 where = STORAGE_FORMAT_PARENT_WHERE;
406 whereArgs = new String[] { Integer.toString(storageID),
407 Integer.toString(format),
408 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400409 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400410 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400411 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500412
413 // if we are restricting queries to mSubDirectories, we need to add the restriction
414 // onto our "where" arguments
415 if (mSubDirectoriesWhere != null) {
416 if (where == null) {
417 where = mSubDirectoriesWhere;
418 whereArgs = mSubDirectoriesWhereArgs;
419 } else {
420 where = where + " AND " + mSubDirectoriesWhere;
421
422 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
423 String[] newWhereArgs =
424 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
425 int i, j;
426 for (i = 0; i < whereArgs.length; i++) {
427 newWhereArgs[i] = whereArgs[i];
428 }
429 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
430 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
431 }
432 whereArgs = newWhereArgs;
433 }
434 }
435
Dianne Hackborn35654b62013-01-14 17:38:02 -0800436 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
437 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400438 }
439
440 private int[] getObjectList(int storageID, int format, int parent) {
441 Cursor c = null;
442 try {
443 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400444 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400445 return null;
446 }
447 int count = c.getCount();
448 if (count > 0) {
449 int[] result = new int[count];
450 for (int i = 0; i < count; i++) {
451 c.moveToNext();
452 result[i] = c.getInt(0);
453 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400454 return result;
455 }
456 } catch (RemoteException e) {
457 Log.e(TAG, "RemoteException in getObjectList", e);
458 } finally {
459 if (c != null) {
460 c.close();
461 }
462 }
463 return null;
464 }
465
Mike Lockwood7a047c82010-08-02 10:52:20 -0400466 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400467 Cursor c = null;
468 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400469 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400470 if (c != null) {
471 return c.getCount();
472 }
473 } catch (RemoteException e) {
474 Log.e(TAG, "RemoteException in getNumObjects", e);
475 } finally {
476 if (c != null) {
477 c.close();
478 }
479 }
480 return -1;
481 }
482
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400483 private int[] getSupportedPlaybackFormats() {
484 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400485 // allow transfering arbitrary files
486 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400487
Mike Lockwood792ec842010-09-09 15:30:10 -0400488 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400489 MtpConstants.FORMAT_TEXT,
490 MtpConstants.FORMAT_HTML,
491 MtpConstants.FORMAT_WAV,
492 MtpConstants.FORMAT_MP3,
493 MtpConstants.FORMAT_MPEG,
494 MtpConstants.FORMAT_EXIF_JPEG,
495 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800496 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400497 MtpConstants.FORMAT_GIF,
498 MtpConstants.FORMAT_JFIF,
499 MtpConstants.FORMAT_PNG,
500 MtpConstants.FORMAT_TIFF,
501 MtpConstants.FORMAT_WMA,
502 MtpConstants.FORMAT_OGG,
503 MtpConstants.FORMAT_AAC,
504 MtpConstants.FORMAT_MP4_CONTAINER,
505 MtpConstants.FORMAT_MP2,
506 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400507 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400508 MtpConstants.FORMAT_WPL_PLAYLIST,
509 MtpConstants.FORMAT_M3U_PLAYLIST,
510 MtpConstants.FORMAT_PLS_PLAYLIST,
511 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800512 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400513 };
514 }
515
516 private int[] getSupportedCaptureFormats() {
517 // no capture formats yet
518 return null;
519 }
520
Mike Lockwoodae078f72010-09-26 12:35:51 -0400521 static final int[] FILE_PROPERTIES = {
522 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
523 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400524 MtpConstants.PROPERTY_STORAGE_ID,
525 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400526 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400527 MtpConstants.PROPERTY_OBJECT_SIZE,
528 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400529 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400530 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400531 MtpConstants.PROPERTY_PERSISTENT_UID,
532 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400533 MtpConstants.PROPERTY_DATE_ADDED,
534 };
535
536 static final int[] AUDIO_PROPERTIES = {
537 // NOTE must match FILE_PROPERTIES above
538 MtpConstants.PROPERTY_STORAGE_ID,
539 MtpConstants.PROPERTY_OBJECT_FORMAT,
540 MtpConstants.PROPERTY_PROTECTION_STATUS,
541 MtpConstants.PROPERTY_OBJECT_SIZE,
542 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
543 MtpConstants.PROPERTY_DATE_MODIFIED,
544 MtpConstants.PROPERTY_PARENT_OBJECT,
545 MtpConstants.PROPERTY_PERSISTENT_UID,
546 MtpConstants.PROPERTY_NAME,
547 MtpConstants.PROPERTY_DISPLAY_NAME,
548 MtpConstants.PROPERTY_DATE_ADDED,
549
550 // audio specific properties
551 MtpConstants.PROPERTY_ARTIST,
552 MtpConstants.PROPERTY_ALBUM_NAME,
553 MtpConstants.PROPERTY_ALBUM_ARTIST,
554 MtpConstants.PROPERTY_TRACK,
555 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
556 MtpConstants.PROPERTY_DURATION,
557 MtpConstants.PROPERTY_GENRE,
558 MtpConstants.PROPERTY_COMPOSER,
559 };
560
561 static final int[] VIDEO_PROPERTIES = {
562 // NOTE must match FILE_PROPERTIES above
563 MtpConstants.PROPERTY_STORAGE_ID,
564 MtpConstants.PROPERTY_OBJECT_FORMAT,
565 MtpConstants.PROPERTY_PROTECTION_STATUS,
566 MtpConstants.PROPERTY_OBJECT_SIZE,
567 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
568 MtpConstants.PROPERTY_DATE_MODIFIED,
569 MtpConstants.PROPERTY_PARENT_OBJECT,
570 MtpConstants.PROPERTY_PERSISTENT_UID,
571 MtpConstants.PROPERTY_NAME,
572 MtpConstants.PROPERTY_DISPLAY_NAME,
573 MtpConstants.PROPERTY_DATE_ADDED,
574
575 // video specific properties
576 MtpConstants.PROPERTY_ARTIST,
577 MtpConstants.PROPERTY_ALBUM_NAME,
578 MtpConstants.PROPERTY_DURATION,
579 MtpConstants.PROPERTY_DESCRIPTION,
580 };
581
582 static final int[] IMAGE_PROPERTIES = {
583 // NOTE must match FILE_PROPERTIES above
584 MtpConstants.PROPERTY_STORAGE_ID,
585 MtpConstants.PROPERTY_OBJECT_FORMAT,
586 MtpConstants.PROPERTY_PROTECTION_STATUS,
587 MtpConstants.PROPERTY_OBJECT_SIZE,
588 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
589 MtpConstants.PROPERTY_DATE_MODIFIED,
590 MtpConstants.PROPERTY_PARENT_OBJECT,
591 MtpConstants.PROPERTY_PERSISTENT_UID,
592 MtpConstants.PROPERTY_NAME,
593 MtpConstants.PROPERTY_DISPLAY_NAME,
594 MtpConstants.PROPERTY_DATE_ADDED,
595
596 // image specific properties
597 MtpConstants.PROPERTY_DESCRIPTION,
598 };
599
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500600 static final int[] ALL_PROPERTIES = {
601 // NOTE must match FILE_PROPERTIES above
602 MtpConstants.PROPERTY_STORAGE_ID,
603 MtpConstants.PROPERTY_OBJECT_FORMAT,
604 MtpConstants.PROPERTY_PROTECTION_STATUS,
605 MtpConstants.PROPERTY_OBJECT_SIZE,
606 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
607 MtpConstants.PROPERTY_DATE_MODIFIED,
608 MtpConstants.PROPERTY_PARENT_OBJECT,
609 MtpConstants.PROPERTY_PERSISTENT_UID,
610 MtpConstants.PROPERTY_NAME,
611 MtpConstants.PROPERTY_DISPLAY_NAME,
612 MtpConstants.PROPERTY_DATE_ADDED,
613
614 // image specific properties
615 MtpConstants.PROPERTY_DESCRIPTION,
616
617 // audio specific properties
618 MtpConstants.PROPERTY_ARTIST,
619 MtpConstants.PROPERTY_ALBUM_NAME,
620 MtpConstants.PROPERTY_ALBUM_ARTIST,
621 MtpConstants.PROPERTY_TRACK,
622 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
623 MtpConstants.PROPERTY_DURATION,
624 MtpConstants.PROPERTY_GENRE,
625 MtpConstants.PROPERTY_COMPOSER,
626
627 // video specific properties
628 MtpConstants.PROPERTY_ARTIST,
629 MtpConstants.PROPERTY_ALBUM_NAME,
630 MtpConstants.PROPERTY_DURATION,
631 MtpConstants.PROPERTY_DESCRIPTION,
632
633 // image specific properties
634 MtpConstants.PROPERTY_DESCRIPTION,
635 };
636
Mike Lockwoodae078f72010-09-26 12:35:51 -0400637 private int[] getSupportedObjectProperties(int format) {
638 switch (format) {
639 case MtpConstants.FORMAT_MP3:
640 case MtpConstants.FORMAT_WAV:
641 case MtpConstants.FORMAT_WMA:
642 case MtpConstants.FORMAT_OGG:
643 case MtpConstants.FORMAT_AAC:
644 return AUDIO_PROPERTIES;
645 case MtpConstants.FORMAT_MPEG:
646 case MtpConstants.FORMAT_3GP_CONTAINER:
647 case MtpConstants.FORMAT_WMV:
648 return VIDEO_PROPERTIES;
649 case MtpConstants.FORMAT_EXIF_JPEG:
650 case MtpConstants.FORMAT_GIF:
651 case MtpConstants.FORMAT_PNG:
652 case MtpConstants.FORMAT_BMP:
653 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500654 case 0:
655 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400656 default:
657 return FILE_PROPERTIES;
658 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400659 }
660
661 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400662 return new int[] {
663 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
664 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800665 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400666 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400667 }
668
Mike Lockwoodae078f72010-09-26 12:35:51 -0400669
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500670 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400671 int groupCode, int depth) {
672 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400673 if (groupCode != 0) {
674 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
675 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400676
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500677 MtpPropertyGroup propertyGroup;
678 if (property == 0xFFFFFFFFL) {
679 propertyGroup = mPropertyGroupsByFormat.get(format);
680 if (propertyGroup == null) {
681 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800682 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
683 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500684 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400685 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500686 } else {
687 propertyGroup = mPropertyGroupsByProperty.get(property);
688 if (propertyGroup == null) {
689 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800690 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
691 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500692 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400693 }
694 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500695
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400696 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400697 }
698
Mike Lockwood5ebac832010-10-12 11:33:47 -0400699 private int renameFile(int handle, String newName) {
700 Cursor c = null;
701
702 // first compute current path
703 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400704 String[] whereArgs = new String[] { Integer.toString(handle) };
705 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800706 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
707 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400708 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500709 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400710 }
711 } catch (RemoteException e) {
712 Log.e(TAG, "RemoteException in getObjectFilePath", e);
713 return MtpConstants.RESPONSE_GENERAL_ERROR;
714 } finally {
715 if (c != null) {
716 c.close();
717 }
718 }
719 if (path == null) {
720 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
721 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400722
Mike Lockwood73e56d92011-12-01 16:58:41 -0500723 // do not allow renaming any of the special subdirectories
724 if (isStorageSubDirectory(path)) {
725 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
726 }
727
Mike Lockwood5ebac832010-10-12 11:33:47 -0400728 // now rename the file. make sure this succeeds before updating database
729 File oldFile = new File(path);
730 int lastSlash = path.lastIndexOf('/');
731 if (lastSlash <= 1) {
732 return MtpConstants.RESPONSE_GENERAL_ERROR;
733 }
734 String newPath = path.substring(0, lastSlash + 1) + newName;
735 File newFile = new File(newPath);
736 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400737 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800738 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400739 return MtpConstants.RESPONSE_GENERAL_ERROR;
740 }
741
742 // finally update database
743 ContentValues values = new ContentValues();
744 values.put(Files.FileColumns.DATA, newPath);
745 int updated = 0;
746 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400747 // note - we are relying on a special case in MediaProvider.update() to update
748 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800749 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400750 } catch (RemoteException e) {
751 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
752 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400753 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400754 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
755 // this shouldn't happen, but if it does we need to rename the file to its original name
756 newFile.renameTo(oldFile);
757 return MtpConstants.RESPONSE_GENERAL_ERROR;
758 }
759
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800760 // check if nomedia status changed
761 if (newFile.isDirectory()) {
762 // for directories, check if renamed from something hidden to something non-hidden
763 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
764 // directory was unhidden
765 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800766 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800767 } catch (RemoteException e) {
768 Log.e(TAG, "failed to unhide/rescan for " + newPath);
769 }
770 }
771 } else {
772 // for files, check if renamed from .nomedia to something else
773 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
774 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
775 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800776 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800777 } catch (RemoteException e) {
778 Log.e(TAG, "failed to unhide/rescan for " + newPath);
779 }
780 }
781 }
782
Mike Lockwood5ebac832010-10-12 11:33:47 -0400783 return MtpConstants.RESPONSE_OK;
784 }
785
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400786 private int setObjectProperty(int handle, int property,
787 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400788 switch (property) {
789 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
790 return renameFile(handle, stringValue);
791
792 default:
793 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
794 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400795 }
796
797 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400798 switch (property) {
799 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
800 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500801 // writable string properties kept in shared preferences
802 String value = mDeviceProperties.getString(Integer.toString(property), "");
803 int length = value.length();
804 if (length > 255) {
805 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400806 }
Mike Lockwood775de952011-03-05 17:34:11 -0500807 value.getChars(0, length, outStringValue, 0);
808 outStringValue[length] = 0;
809 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400810
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800811 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
812 // use screen size as max image size
813 Display display = ((WindowManager)mContext.getSystemService(
814 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700815 int width = display.getMaximumSizeDimension();
816 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800817 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
818 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
819 outStringValue[imageSize.length()] = 0;
820 return MtpConstants.RESPONSE_OK;
821
822 default:
823 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
824 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400825 }
826
827 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400828 switch (property) {
829 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
830 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500831 // writable string properties kept in shared prefs
832 SharedPreferences.Editor e = mDeviceProperties.edit();
833 e.putString(Integer.toString(property), stringValue);
834 return (e.commit() ? MtpConstants.RESPONSE_OK
835 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400836 }
837
838 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
839 }
840
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400841 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700842 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400843 Cursor c = null;
844 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800845 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800846 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400847 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400848 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400849 outStorageFormatParent[1] = c.getInt(2);
850 outStorageFormatParent[2] = c.getInt(3);
851
852 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400853 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400854 int lastSlash = path.lastIndexOf('/');
855 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
856 int end = path.length();
857 if (end - start > 255) {
858 end = start + 255;
859 }
860 path.getChars(start, end, outName, 0);
861 outName[end - start] = 0;
862
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700863 outCreatedModified[0] = c.getLong(5);
864 outCreatedModified[1] = c.getLong(6);
865 // use modification date as creation date if date added is not set
866 if (outCreatedModified[0] == 0) {
867 outCreatedModified[0] = outCreatedModified[1];
868 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400869 return true;
870 }
871 } catch (RemoteException e) {
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -0400872 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400873 } finally {
874 if (c != null) {
875 c.close();
876 }
877 }
878 return false;
879 }
880
Mike Lockwood365e03e2010-12-08 16:08:01 -0800881 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400882 if (handle == 0) {
883 // special case root directory
884 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
885 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800886 outFileLengthFormat[0] = 0;
887 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400888 return MtpConstants.RESPONSE_OK;
889 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400890 Cursor c = null;
891 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800892 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800893 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400894 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500895 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400896 path.getChars(0, path.length(), outFilePath, 0);
897 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700898 // File transfers from device to host will likely fail if the size is incorrect.
899 // So to be safe, use the actual file size here.
900 outFileLengthFormat[0] = new File(path).length();
901 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400902 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400903 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400904 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400905 }
906 } catch (RemoteException e) {
907 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400908 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400909 } finally {
910 if (c != null) {
911 c.close();
912 }
913 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400914 }
915
Mike Lockwood59c777a2010-08-02 10:37:41 -0400916 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400917 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800918 String path = null;
919 int format = 0;
920
921 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400922 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800923 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800924 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800925 if (c != null && c.moveToNext()) {
926 // don't convert to media path here, since we will be matching
927 // against paths in the database matching /data/media
928 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700929 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800930 } else {
931 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
932 }
933
934 if (path == null || format == 0) {
935 return MtpConstants.RESPONSE_GENERAL_ERROR;
936 }
937
Mike Lockwood73e56d92011-12-01 16:58:41 -0500938 // do not allow deleting any of the special subdirectories
939 if (isStorageSubDirectory(path)) {
940 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
941 }
942
Mike Lockwood55f808c2010-12-14 13:14:29 -0800943 if (format == MtpConstants.FORMAT_ASSOCIATION) {
944 // recursive case - delete all children first
945 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800946 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -0700947 // the 'like' makes it use the index, the 'lower()' makes it correct
948 // when the path contains sqlite wildcard characters
949 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
950 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800951 }
952
953 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800954 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800955 if (format != MtpConstants.FORMAT_ASSOCIATION
956 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
957 try {
958 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -0800959 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800960 } catch (RemoteException e) {
961 Log.e(TAG, "failed to unhide/rescan for " + path);
962 }
963 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400964 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400965 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400966 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400967 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400968 } catch (RemoteException e) {
969 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400970 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800971 } finally {
972 if (c != null) {
973 c.close();
974 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400975 }
976 }
977
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400978 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -0400979 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400980 Cursor c = null;
981 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800982 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400983 if (c == null) {
984 return null;
985 }
986 int count = c.getCount();
987 if (count > 0) {
988 int[] result = new int[count];
989 for (int i = 0; i < count; i++) {
990 c.moveToNext();
991 result[i] = c.getInt(0);
992 }
993 return result;
994 }
995 } catch (RemoteException e) {
996 Log.e(TAG, "RemoteException in getObjectList", e);
997 } finally {
998 if (c != null) {
999 c.close();
1000 }
1001 }
1002 return null;
1003 }
1004
1005 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001006 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001007 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001008 int count = references.length;
1009 ContentValues[] valuesList = new ContentValues[count];
1010 for (int i = 0; i < count; i++) {
1011 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001012 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001013 valuesList[i] = values;
1014 }
1015 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001016 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001017 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001018 }
1019 } catch (RemoteException e) {
1020 Log.e(TAG, "RemoteException in setObjectReferences", e);
1021 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001022 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001023 }
1024
Mike Lockwood2837eef2010-08-31 16:25:12 -04001025 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001026 mDatabaseModified = false;
1027 }
1028
1029 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001030 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001031 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001032 mDatabaseModified = false;
1033 }
1034 }
1035
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001036 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001037 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001038
1039 private native final void native_setup();
1040 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001041}