blob: 9ceefc3466788d1d5a1daacb7960feb7132b663f [file] [log] [blame]
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040018
19import android.content.Context;
Mike Lockwoodd815f792010-07-12 08:49:01 -040020import android.content.ContentValues;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040021import android.content.IContentProvider;
Mike Lockwood2837eef2010-08-31 16:25:12 -040022import android.content.Intent;
Mike Lockwood775de952011-03-05 17:34:11 -050023import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040024import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040025import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050026import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040027import android.net.Uri;
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -040028import android.os.Environment;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040029import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050030import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040031import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040032import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040033import android.provider.MediaStore.Images;
34import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040035import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080036import android.view.Display;
37import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040038
Mike Lockwood5ebac832010-10-12 11:33:47 -040039import java.io.File;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050040import java.util.HashMap;
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;
Dianne Hackborn35654b62013-01-14 17:38:02 -080051 private final String mPackageName;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040052 private final IContentProvider mMediaProvider;
53 private final String mVolumeName;
54 private final Uri mObjectsUri;
Mike Lockwood73e56d92011-12-01 16:58:41 -050055 // path to primary storage
56 private final String mMediaStoragePath;
57 // if not null, restrict all queries to these subdirectories
58 private final String[] mSubDirectories;
59 // where clause for restricting queries to files in mSubDirectories
60 private String mSubDirectoriesWhere;
61 // where arguments for restricting queries to files in mSubDirectories
62 private String[] mSubDirectoriesWhereArgs;
63
Mike Lockwoodb239b6832011-04-05 10:21:27 -040064 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
Mike Lockwoodd21eac92010-07-03 00:44:05 -040065
Mike Lockwood7d7fb632010-12-01 18:46:23 -050066 // cached property groups for single properties
67 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
68 = new HashMap<Integer, MtpPropertyGroup>();
69
70 // cached property groups for all properties for a given format
71 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
72 = new HashMap<Integer, MtpPropertyGroup>();
73
Mike Lockwood2837eef2010-08-31 16:25:12 -040074 // true if the database has been modified in the current MTP session
75 private boolean mDatabaseModified;
76
Mike Lockwood775de952011-03-05 17:34:11 -050077 // SharedPreferences for writable MTP device properties
78 private SharedPreferences mDeviceProperties;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040079 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
80
Mike Lockwoodd21eac92010-07-03 00:44:05 -040081 private static final String[] ID_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040082 Files.FileColumns._ID, // 0
Mike Lockwoodd21eac92010-07-03 00:44:05 -040083 };
Mike Lockwood6a6a3af2010-10-12 14:19:51 -040084 private static final String[] PATH_PROJECTION = new String[] {
Mike Lockwood5ebac832010-10-12 11:33:47 -040085 Files.FileColumns._ID, // 0
86 Files.FileColumns.DATA, // 1
Mike Lockwood5ebac832010-10-12 11:33:47 -040087 };
Mike Lockwoodf6f16612012-09-12 15:50:59 -070088 private static final String[] PATH_FORMAT_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040089 Files.FileColumns._ID, // 0
90 Files.FileColumns.DATA, // 1
Mike Lockwoodf6f16612012-09-12 15:50:59 -070091 Files.FileColumns.FORMAT, // 2
Mike Lockwoodd21eac92010-07-03 00:44:05 -040092 };
93 private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040094 Files.FileColumns._ID, // 0
Mike Lockwoodb239b6832011-04-05 10:21:27 -040095 Files.FileColumns.STORAGE_ID, // 1
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040096 Files.FileColumns.FORMAT, // 2
97 Files.FileColumns.PARENT, // 3
Mike Lockwoodb239b6832011-04-05 10:21:27 -040098 Files.FileColumns.DATA, // 4
Mike Lockwood1341f1e2013-04-01 10:52:47 -070099 Files.FileColumns.DATE_ADDED, // 5
100 Files.FileColumns.DATE_MODIFIED, // 6
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400101 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400102 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800103 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400104
105 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e68312012-09-11 10:49:34 -0700106 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
107 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400108 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400109 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400110 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
111 + Files.FileColumns.PARENT + "=?";
112 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
113 + Files.FileColumns.PARENT + "=?";
114 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
115 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400116
Mike Lockwoodd815f792010-07-12 08:49:01 -0400117 private final MediaScanner mMediaScanner;
118
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400119 static {
120 System.loadLibrary("media_jni");
121 }
122
Mike Lockwood73e56d92011-12-01 16:58:41 -0500123 public MtpDatabase(Context context, String volumeName, String storagePath,
124 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400125 native_setup();
126
Mike Lockwood2837eef2010-08-31 16:25:12 -0400127 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800128 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400129 mMediaProvider = context.getContentResolver().acquireProvider("media");
130 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400131 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400132 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400133 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900134
Mike Lockwood73e56d92011-12-01 16:58:41 -0500135 mSubDirectories = subDirectories;
136 if (subDirectories != null) {
137 // Compute "where" string for restricting queries to subdirectories
138 StringBuilder builder = new StringBuilder();
139 builder.append("(");
140 int count = subDirectories.length;
141 for (int i = 0; i < count; i++) {
142 builder.append(Files.FileColumns.DATA + "=? OR "
143 + Files.FileColumns.DATA + " LIKE ?");
144 if (i != count - 1) {
145 builder.append(" OR ");
146 }
147 }
148 builder.append(")");
149 mSubDirectoriesWhere = builder.toString();
150
151 // Compute "where" arguments for restricting queries to subdirectories
152 mSubDirectoriesWhereArgs = new String[count * 2];
153 for (int i = 0, j = 0; i < count; i++) {
154 String path = subDirectories[i];
155 mSubDirectoriesWhereArgs[j++] = path;
156 mSubDirectoriesWhereArgs[j++] = path + "/%";
157 }
158 }
159
dujin.chafe464a72011-11-22 12:13:33 +0900160 // Set locale to MediaScanner.
161 Locale locale = context.getResources().getConfiguration().locale;
162 if (locale != null) {
163 String language = locale.getLanguage();
164 String country = locale.getCountry();
165 if (language != null) {
166 if (country != null) {
167 mMediaScanner.setLocale(language + "_" + country);
168 } else {
169 mMediaScanner.setLocale(language);
170 }
171 }
172 }
Mike Lockwood775de952011-03-05 17:34:11 -0500173 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400174 }
175
176 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400177 protected void finalize() throws Throwable {
178 try {
179 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400180 } finally {
181 super.finalize();
182 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400183 }
184
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400185 public void addStorage(MtpStorage storage) {
186 mStorageMap.put(storage.getPath(), storage);
187 }
188
189 public void removeStorage(MtpStorage storage) {
190 mStorageMap.remove(storage.getPath());
191 }
192
Mike Lockwood775de952011-03-05 17:34:11 -0500193 private void initDeviceProperties(Context context) {
194 final String devicePropertiesName = "device-properties";
195 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
196 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400197
Mike Lockwood775de952011-03-05 17:34:11 -0500198 if (databaseFile.exists()) {
199 // for backward compatibility - read device properties from sqlite database
200 // and migrate them to shared prefs
201 SQLiteDatabase db = null;
202 Cursor c = null;
203 try {
204 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
205 if (db != null) {
206 c = db.query("properties", new String[] { "_id", "code", "value" },
207 null, null, null, null, null);
208 if (c != null) {
209 SharedPreferences.Editor e = mDeviceProperties.edit();
210 while (c.moveToNext()) {
211 String name = c.getString(1);
212 String value = c.getString(2);
213 e.putString(name, value);
214 }
215 e.commit();
216 }
217 }
218 } catch (Exception e) {
219 Log.e(TAG, "failed to migrate device properties", e);
220 } finally {
221 if (c != null) c.close();
222 if (db != null) db.close();
223 }
jangwon.lee3ed02532013-07-22 19:52:48 +0900224 context.deleteDatabase(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400225 }
226 }
227
Mike Lockwood73e56d92011-12-01 16:58:41 -0500228 // check to see if the path is contained in one of our storage subdirectories
229 // returns true if we have no special subdirectories
230 private boolean inStorageSubDirectory(String path) {
231 if (mSubDirectories == null) return true;
232 if (path == null) return false;
233
234 boolean allowed = false;
235 int pathLength = path.length();
236 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
237 String subdir = mSubDirectories[i];
238 int subdirLength = subdir.length();
239 if (subdirLength < pathLength &&
240 path.charAt(subdirLength) == '/' &&
241 path.startsWith(subdir)) {
242 allowed = true;
243 }
244 }
245 return allowed;
246 }
247
248 // check to see if the path matches one of our storage subdirectories
249 // returns true if we have no special subdirectories
250 private boolean isStorageSubDirectory(String path) {
251 if (mSubDirectories == null) return false;
252 for (int i = 0; i < mSubDirectories.length; i++) {
253 if (path.equals(mSubDirectories[i])) {
254 return true;
255 }
256 }
257 return false;
258 }
259
Mike Lockwoodd815f792010-07-12 08:49:01 -0400260 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400261 int storageId, long size, long modified) {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500262 // if mSubDirectories is not null, do not allow copying files to any other locations
263 if (!inStorageSubDirectory(path)) return -1;
264
265 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800266 if (path != null) {
267 Cursor c = null;
268 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800269 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800270 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800271 if (c != null && c.getCount() > 0) {
272 Log.w(TAG, "file already exists in beginSendObject: " + path);
273 return -1;
274 }
275 } catch (RemoteException e) {
276 Log.e(TAG, "RemoteException in beginSendObject", e);
277 } finally {
278 if (c != null) {
279 c.close();
280 }
281 }
282 }
283
Mike Lockwood2837eef2010-08-31 16:25:12 -0400284 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400285 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400286 values.put(Files.FileColumns.DATA, path);
287 values.put(Files.FileColumns.FORMAT, format);
288 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400289 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400290 values.put(Files.FileColumns.SIZE, size);
291 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400292
293 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800294 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400295 if (uri != null) {
296 return Integer.parseInt(uri.getPathSegments().get(2));
297 } else {
298 return -1;
299 }
300 } catch (RemoteException e) {
301 Log.e(TAG, "RemoteException in beginSendObject", e);
302 return -1;
303 }
304 }
305
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800306 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400307 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400308 // handle abstract playlists separately
309 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400310 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400311 // extract name from path
312 String name = path;
313 int lastSlash = name.lastIndexOf('/');
314 if (lastSlash >= 0) {
315 name = name.substring(lastSlash + 1);
316 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800317 // strip trailing ".pla" from the name
318 if (name.endsWith(".pla")) {
319 name = name.substring(0, name.length() - 4);
320 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400321
322 ContentValues values = new ContentValues(1);
323 values.put(Audio.Playlists.DATA, path);
324 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500325 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800326 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400327 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
328 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800329 Uri uri = mMediaProvider.insert(mPackageName,
330 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400331 } catch (RemoteException e) {
332 Log.e(TAG, "RemoteException in endSendObject", e);
333 }
334 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400335 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400336 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400337 } else {
338 deleteFile(handle);
339 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400340 }
341
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400342 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500343 String where;
344 String[] whereArgs;
345
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400346 if (storageID == 0xFFFFFFFF) {
347 // query all stores
348 if (format == 0) {
349 // query all formats
350 if (parent == 0) {
351 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500352 where = null;
353 whereArgs = null;
354 } else {
355 if (parent == 0xFFFFFFFF) {
356 // all objects in root of store
357 parent = 0;
358 }
359 where = PARENT_WHERE;
360 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400361 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400362 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400363 // query specific format
364 if (parent == 0) {
365 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500366 where = FORMAT_WHERE;
367 whereArgs = new String[] { Integer.toString(format) };
368 } else {
369 if (parent == 0xFFFFFFFF) {
370 // all objects in root of store
371 parent = 0;
372 }
373 where = FORMAT_PARENT_WHERE;
374 whereArgs = new String[] { Integer.toString(format),
375 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400376 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400377 }
378 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400379 // query specific store
380 if (format == 0) {
381 // query all formats
382 if (parent == 0) {
383 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500384 where = STORAGE_WHERE;
385 whereArgs = new String[] { Integer.toString(storageID) };
386 } else {
387 if (parent == 0xFFFFFFFF) {
388 // all objects in root of store
389 parent = 0;
390 }
391 where = STORAGE_PARENT_WHERE;
392 whereArgs = new String[] { Integer.toString(storageID),
393 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400394 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400395 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400396 // query specific format
397 if (parent == 0) {
398 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500399 where = STORAGE_FORMAT_WHERE;
400 whereArgs = new String[] { Integer.toString(storageID),
401 Integer.toString(format) };
402 } else {
403 if (parent == 0xFFFFFFFF) {
404 // all objects in root of store
405 parent = 0;
406 }
407 where = STORAGE_FORMAT_PARENT_WHERE;
408 whereArgs = new String[] { Integer.toString(storageID),
409 Integer.toString(format),
410 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400411 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400412 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400413 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500414
415 // if we are restricting queries to mSubDirectories, we need to add the restriction
416 // onto our "where" arguments
417 if (mSubDirectoriesWhere != null) {
418 if (where == null) {
419 where = mSubDirectoriesWhere;
420 whereArgs = mSubDirectoriesWhereArgs;
421 } else {
422 where = where + " AND " + mSubDirectoriesWhere;
423
424 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
425 String[] newWhereArgs =
426 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
427 int i, j;
428 for (i = 0; i < whereArgs.length; i++) {
429 newWhereArgs[i] = whereArgs[i];
430 }
431 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
432 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
433 }
434 whereArgs = newWhereArgs;
435 }
436 }
437
Dianne Hackborn35654b62013-01-14 17:38:02 -0800438 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
439 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400440 }
441
442 private int[] getObjectList(int storageID, int format, int parent) {
443 Cursor c = null;
444 try {
445 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400446 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400447 return null;
448 }
449 int count = c.getCount();
450 if (count > 0) {
451 int[] result = new int[count];
452 for (int i = 0; i < count; i++) {
453 c.moveToNext();
454 result[i] = c.getInt(0);
455 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400456 return result;
457 }
458 } catch (RemoteException e) {
459 Log.e(TAG, "RemoteException in getObjectList", e);
460 } finally {
461 if (c != null) {
462 c.close();
463 }
464 }
465 return null;
466 }
467
Mike Lockwood7a047c82010-08-02 10:52:20 -0400468 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400469 Cursor c = null;
470 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400471 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400472 if (c != null) {
473 return c.getCount();
474 }
475 } catch (RemoteException e) {
476 Log.e(TAG, "RemoteException in getNumObjects", e);
477 } finally {
478 if (c != null) {
479 c.close();
480 }
481 }
482 return -1;
483 }
484
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400485 private int[] getSupportedPlaybackFormats() {
486 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400487 // allow transfering arbitrary files
488 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400489
Mike Lockwood792ec842010-09-09 15:30:10 -0400490 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400491 MtpConstants.FORMAT_TEXT,
492 MtpConstants.FORMAT_HTML,
493 MtpConstants.FORMAT_WAV,
494 MtpConstants.FORMAT_MP3,
495 MtpConstants.FORMAT_MPEG,
496 MtpConstants.FORMAT_EXIF_JPEG,
497 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800498 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400499 MtpConstants.FORMAT_GIF,
500 MtpConstants.FORMAT_JFIF,
501 MtpConstants.FORMAT_PNG,
502 MtpConstants.FORMAT_TIFF,
503 MtpConstants.FORMAT_WMA,
504 MtpConstants.FORMAT_OGG,
505 MtpConstants.FORMAT_AAC,
506 MtpConstants.FORMAT_MP4_CONTAINER,
507 MtpConstants.FORMAT_MP2,
508 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400509 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400510 MtpConstants.FORMAT_WPL_PLAYLIST,
511 MtpConstants.FORMAT_M3U_PLAYLIST,
512 MtpConstants.FORMAT_PLS_PLAYLIST,
513 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800514 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400515 };
516 }
517
518 private int[] getSupportedCaptureFormats() {
519 // no capture formats yet
520 return null;
521 }
522
Mike Lockwoodae078f72010-09-26 12:35:51 -0400523 static final int[] FILE_PROPERTIES = {
524 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
525 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400526 MtpConstants.PROPERTY_STORAGE_ID,
527 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400528 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400529 MtpConstants.PROPERTY_OBJECT_SIZE,
530 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400531 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400532 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400533 MtpConstants.PROPERTY_PERSISTENT_UID,
534 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400535 MtpConstants.PROPERTY_DATE_ADDED,
536 };
537
538 static final int[] AUDIO_PROPERTIES = {
539 // NOTE must match FILE_PROPERTIES above
540 MtpConstants.PROPERTY_STORAGE_ID,
541 MtpConstants.PROPERTY_OBJECT_FORMAT,
542 MtpConstants.PROPERTY_PROTECTION_STATUS,
543 MtpConstants.PROPERTY_OBJECT_SIZE,
544 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
545 MtpConstants.PROPERTY_DATE_MODIFIED,
546 MtpConstants.PROPERTY_PARENT_OBJECT,
547 MtpConstants.PROPERTY_PERSISTENT_UID,
548 MtpConstants.PROPERTY_NAME,
549 MtpConstants.PROPERTY_DISPLAY_NAME,
550 MtpConstants.PROPERTY_DATE_ADDED,
551
552 // audio specific properties
553 MtpConstants.PROPERTY_ARTIST,
554 MtpConstants.PROPERTY_ALBUM_NAME,
555 MtpConstants.PROPERTY_ALBUM_ARTIST,
556 MtpConstants.PROPERTY_TRACK,
557 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
558 MtpConstants.PROPERTY_DURATION,
559 MtpConstants.PROPERTY_GENRE,
560 MtpConstants.PROPERTY_COMPOSER,
561 };
562
563 static final int[] VIDEO_PROPERTIES = {
564 // NOTE must match FILE_PROPERTIES above
565 MtpConstants.PROPERTY_STORAGE_ID,
566 MtpConstants.PROPERTY_OBJECT_FORMAT,
567 MtpConstants.PROPERTY_PROTECTION_STATUS,
568 MtpConstants.PROPERTY_OBJECT_SIZE,
569 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
570 MtpConstants.PROPERTY_DATE_MODIFIED,
571 MtpConstants.PROPERTY_PARENT_OBJECT,
572 MtpConstants.PROPERTY_PERSISTENT_UID,
573 MtpConstants.PROPERTY_NAME,
574 MtpConstants.PROPERTY_DISPLAY_NAME,
575 MtpConstants.PROPERTY_DATE_ADDED,
576
577 // video specific properties
578 MtpConstants.PROPERTY_ARTIST,
579 MtpConstants.PROPERTY_ALBUM_NAME,
580 MtpConstants.PROPERTY_DURATION,
581 MtpConstants.PROPERTY_DESCRIPTION,
582 };
583
584 static final int[] IMAGE_PROPERTIES = {
585 // NOTE must match FILE_PROPERTIES above
586 MtpConstants.PROPERTY_STORAGE_ID,
587 MtpConstants.PROPERTY_OBJECT_FORMAT,
588 MtpConstants.PROPERTY_PROTECTION_STATUS,
589 MtpConstants.PROPERTY_OBJECT_SIZE,
590 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
591 MtpConstants.PROPERTY_DATE_MODIFIED,
592 MtpConstants.PROPERTY_PARENT_OBJECT,
593 MtpConstants.PROPERTY_PERSISTENT_UID,
594 MtpConstants.PROPERTY_NAME,
595 MtpConstants.PROPERTY_DISPLAY_NAME,
596 MtpConstants.PROPERTY_DATE_ADDED,
597
598 // image specific properties
599 MtpConstants.PROPERTY_DESCRIPTION,
600 };
601
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500602 static final int[] ALL_PROPERTIES = {
603 // NOTE must match FILE_PROPERTIES above
604 MtpConstants.PROPERTY_STORAGE_ID,
605 MtpConstants.PROPERTY_OBJECT_FORMAT,
606 MtpConstants.PROPERTY_PROTECTION_STATUS,
607 MtpConstants.PROPERTY_OBJECT_SIZE,
608 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
609 MtpConstants.PROPERTY_DATE_MODIFIED,
610 MtpConstants.PROPERTY_PARENT_OBJECT,
611 MtpConstants.PROPERTY_PERSISTENT_UID,
612 MtpConstants.PROPERTY_NAME,
613 MtpConstants.PROPERTY_DISPLAY_NAME,
614 MtpConstants.PROPERTY_DATE_ADDED,
615
616 // image specific properties
617 MtpConstants.PROPERTY_DESCRIPTION,
618
619 // audio specific properties
620 MtpConstants.PROPERTY_ARTIST,
621 MtpConstants.PROPERTY_ALBUM_NAME,
622 MtpConstants.PROPERTY_ALBUM_ARTIST,
623 MtpConstants.PROPERTY_TRACK,
624 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
625 MtpConstants.PROPERTY_DURATION,
626 MtpConstants.PROPERTY_GENRE,
627 MtpConstants.PROPERTY_COMPOSER,
628
629 // video specific properties
630 MtpConstants.PROPERTY_ARTIST,
631 MtpConstants.PROPERTY_ALBUM_NAME,
632 MtpConstants.PROPERTY_DURATION,
633 MtpConstants.PROPERTY_DESCRIPTION,
634
635 // image specific properties
636 MtpConstants.PROPERTY_DESCRIPTION,
637 };
638
Mike Lockwoodae078f72010-09-26 12:35:51 -0400639 private int[] getSupportedObjectProperties(int format) {
640 switch (format) {
641 case MtpConstants.FORMAT_MP3:
642 case MtpConstants.FORMAT_WAV:
643 case MtpConstants.FORMAT_WMA:
644 case MtpConstants.FORMAT_OGG:
645 case MtpConstants.FORMAT_AAC:
646 return AUDIO_PROPERTIES;
647 case MtpConstants.FORMAT_MPEG:
648 case MtpConstants.FORMAT_3GP_CONTAINER:
649 case MtpConstants.FORMAT_WMV:
650 return VIDEO_PROPERTIES;
651 case MtpConstants.FORMAT_EXIF_JPEG:
652 case MtpConstants.FORMAT_GIF:
653 case MtpConstants.FORMAT_PNG:
654 case MtpConstants.FORMAT_BMP:
655 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500656 case 0:
657 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400658 default:
659 return FILE_PROPERTIES;
660 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400661 }
662
663 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400664 return new int[] {
665 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
666 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800667 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400668 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400669 }
670
Mike Lockwoodae078f72010-09-26 12:35:51 -0400671
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500672 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400673 int groupCode, int depth) {
674 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400675 if (groupCode != 0) {
676 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
677 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400678
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500679 MtpPropertyGroup propertyGroup;
680 if (property == 0xFFFFFFFFL) {
681 propertyGroup = mPropertyGroupsByFormat.get(format);
682 if (propertyGroup == null) {
683 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800684 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
685 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500686 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400687 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500688 } else {
689 propertyGroup = mPropertyGroupsByProperty.get(property);
690 if (propertyGroup == null) {
691 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800692 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
693 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500694 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400695 }
696 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500697
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400698 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400699 }
700
Mike Lockwood5ebac832010-10-12 11:33:47 -0400701 private int renameFile(int handle, String newName) {
702 Cursor c = null;
703
704 // first compute current path
705 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400706 String[] whereArgs = new String[] { Integer.toString(handle) };
707 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800708 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
709 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400710 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500711 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400712 }
713 } catch (RemoteException e) {
714 Log.e(TAG, "RemoteException in getObjectFilePath", e);
715 return MtpConstants.RESPONSE_GENERAL_ERROR;
716 } finally {
717 if (c != null) {
718 c.close();
719 }
720 }
721 if (path == null) {
722 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
723 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400724
Mike Lockwood73e56d92011-12-01 16:58:41 -0500725 // do not allow renaming any of the special subdirectories
726 if (isStorageSubDirectory(path)) {
727 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
728 }
729
Mike Lockwood5ebac832010-10-12 11:33:47 -0400730 // now rename the file. make sure this succeeds before updating database
731 File oldFile = new File(path);
732 int lastSlash = path.lastIndexOf('/');
733 if (lastSlash <= 1) {
734 return MtpConstants.RESPONSE_GENERAL_ERROR;
735 }
736 String newPath = path.substring(0, lastSlash + 1) + newName;
737 File newFile = new File(newPath);
738 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400739 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800740 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400741 return MtpConstants.RESPONSE_GENERAL_ERROR;
742 }
743
744 // finally update database
745 ContentValues values = new ContentValues();
746 values.put(Files.FileColumns.DATA, newPath);
747 int updated = 0;
748 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400749 // note - we are relying on a special case in MediaProvider.update() to update
750 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800751 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400752 } catch (RemoteException e) {
753 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
754 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400755 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400756 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
757 // this shouldn't happen, but if it does we need to rename the file to its original name
758 newFile.renameTo(oldFile);
759 return MtpConstants.RESPONSE_GENERAL_ERROR;
760 }
761
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800762 // check if nomedia status changed
763 if (newFile.isDirectory()) {
764 // for directories, check if renamed from something hidden to something non-hidden
765 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
766 // directory was unhidden
767 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800768 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800769 } catch (RemoteException e) {
770 Log.e(TAG, "failed to unhide/rescan for " + newPath);
771 }
772 }
773 } else {
774 // for files, check if renamed from .nomedia to something else
775 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
776 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
777 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800778 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800779 } catch (RemoteException e) {
780 Log.e(TAG, "failed to unhide/rescan for " + newPath);
781 }
782 }
783 }
784
Mike Lockwood5ebac832010-10-12 11:33:47 -0400785 return MtpConstants.RESPONSE_OK;
786 }
787
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400788 private int setObjectProperty(int handle, int property,
789 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400790 switch (property) {
791 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
792 return renameFile(handle, stringValue);
793
794 default:
795 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
796 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400797 }
798
799 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400800 switch (property) {
801 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
802 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500803 // writable string properties kept in shared preferences
804 String value = mDeviceProperties.getString(Integer.toString(property), "");
805 int length = value.length();
806 if (length > 255) {
807 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400808 }
Mike Lockwood775de952011-03-05 17:34:11 -0500809 value.getChars(0, length, outStringValue, 0);
810 outStringValue[length] = 0;
811 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400812
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800813 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
814 // use screen size as max image size
815 Display display = ((WindowManager)mContext.getSystemService(
816 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700817 int width = display.getMaximumSizeDimension();
818 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800819 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
820 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
821 outStringValue[imageSize.length()] = 0;
822 return MtpConstants.RESPONSE_OK;
823
824 default:
825 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
826 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400827 }
828
829 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400830 switch (property) {
831 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
832 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500833 // writable string properties kept in shared prefs
834 SharedPreferences.Editor e = mDeviceProperties.edit();
835 e.putString(Integer.toString(property), stringValue);
836 return (e.commit() ? MtpConstants.RESPONSE_OK
837 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400838 }
839
840 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
841 }
842
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400843 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700844 char[] outName, long[] outCreatedModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400845 Cursor c = null;
846 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800847 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800848 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400849 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400850 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400851 outStorageFormatParent[1] = c.getInt(2);
852 outStorageFormatParent[2] = c.getInt(3);
853
854 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400855 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400856 int lastSlash = path.lastIndexOf('/');
857 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
858 int end = path.length();
859 if (end - start > 255) {
860 end = start + 255;
861 }
862 path.getChars(start, end, outName, 0);
863 outName[end - start] = 0;
864
Mike Lockwood1341f1e2013-04-01 10:52:47 -0700865 outCreatedModified[0] = c.getLong(5);
866 outCreatedModified[1] = c.getLong(6);
867 // use modification date as creation date if date added is not set
868 if (outCreatedModified[0] == 0) {
869 outCreatedModified[0] = outCreatedModified[1];
870 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400871 return true;
872 }
873 } catch (RemoteException e) {
Mike Lockwood2b5f9ad2010-10-29 19:16:27 -0400874 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400875 } finally {
876 if (c != null) {
877 c.close();
878 }
879 }
880 return false;
881 }
882
Mike Lockwood365e03e2010-12-08 16:08:01 -0800883 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400884 if (handle == 0) {
885 // special case root directory
886 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
887 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800888 outFileLengthFormat[0] = 0;
889 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400890 return MtpConstants.RESPONSE_OK;
891 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400892 Cursor c = null;
893 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800894 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800895 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400896 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500897 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400898 path.getChars(0, path.length(), outFilePath, 0);
899 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700900 // File transfers from device to host will likely fail if the size is incorrect.
901 // So to be safe, use the actual file size here.
902 outFileLengthFormat[0] = new File(path).length();
903 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400904 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400905 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400906 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400907 }
908 } catch (RemoteException e) {
909 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400910 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400911 } finally {
912 if (c != null) {
913 c.close();
914 }
915 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400916 }
917
Mike Lockwood59c777a2010-08-02 10:37:41 -0400918 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400919 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800920 String path = null;
921 int format = 0;
922
923 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400924 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800925 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800926 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800927 if (c != null && c.moveToNext()) {
928 // don't convert to media path here, since we will be matching
929 // against paths in the database matching /data/media
930 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700931 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800932 } else {
933 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
934 }
935
936 if (path == null || format == 0) {
937 return MtpConstants.RESPONSE_GENERAL_ERROR;
938 }
939
Mike Lockwood73e56d92011-12-01 16:58:41 -0500940 // do not allow deleting any of the special subdirectories
941 if (isStorageSubDirectory(path)) {
942 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
943 }
944
Mike Lockwood55f808c2010-12-14 13:14:29 -0800945 if (format == MtpConstants.FORMAT_ASSOCIATION) {
946 // recursive case - delete all children first
947 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800948 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -0700949 // the 'like' makes it use the index, the 'lower()' makes it correct
950 // when the path contains sqlite wildcard characters
951 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
952 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800953 }
954
955 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800956 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800957 if (format != MtpConstants.FORMAT_ASSOCIATION
958 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
959 try {
960 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -0800961 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800962 } catch (RemoteException e) {
963 Log.e(TAG, "failed to unhide/rescan for " + path);
964 }
965 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400966 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400967 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400968 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400969 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400970 } catch (RemoteException e) {
971 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400972 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800973 } finally {
974 if (c != null) {
975 c.close();
976 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400977 }
978 }
979
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400980 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -0400981 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400982 Cursor c = null;
983 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800984 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400985 if (c == null) {
986 return null;
987 }
988 int count = c.getCount();
989 if (count > 0) {
990 int[] result = new int[count];
991 for (int i = 0; i < count; i++) {
992 c.moveToNext();
993 result[i] = c.getInt(0);
994 }
995 return result;
996 }
997 } catch (RemoteException e) {
998 Log.e(TAG, "RemoteException in getObjectList", e);
999 } finally {
1000 if (c != null) {
1001 c.close();
1002 }
1003 }
1004 return null;
1005 }
1006
1007 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001008 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001009 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001010 int count = references.length;
1011 ContentValues[] valuesList = new ContentValues[count];
1012 for (int i = 0; i < count; i++) {
1013 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001014 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001015 valuesList[i] = values;
1016 }
1017 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001018 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001019 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001020 }
1021 } catch (RemoteException e) {
1022 Log.e(TAG, "RemoteException in setObjectReferences", e);
1023 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001024 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001025 }
1026
Mike Lockwood2837eef2010-08-31 16:25:12 -04001027 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001028 mDatabaseModified = false;
1029 }
1030
1031 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001032 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001033 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001034 mDatabaseModified = false;
1035 }
1036 }
1037
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001038 // used by the JNI code
Ashok Bhate2e59322013-12-17 19:04:19 +00001039 private long mNativeContext;
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001040
1041 private native final void native_setup();
1042 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001043}