blob: ea128032f6c1741f9cf9fe8afe7207440e55a98e [file] [log] [blame]
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mike Lockwood0cd01362010-12-30 11:54:33 -050017package android.mtp;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040018
19import android.content.Context;
Mike Lockwoodd815f792010-07-12 08:49:01 -040020import android.content.ContentValues;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040021import android.content.IContentProvider;
Mike Lockwood2837eef2010-08-31 16:25:12 -040022import android.content.Intent;
Mike Lockwood775de952011-03-05 17:34:11 -050023import android.content.SharedPreferences;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040024import android.database.Cursor;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -040025import android.database.sqlite.SQLiteDatabase;
Mike Lockwood0cd01362010-12-30 11:54:33 -050026import android.media.MediaScanner;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040027import android.net.Uri;
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -040028import android.os.Environment;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040029import android.os.RemoteException;
Mike Lockwooda3156052010-11-20 12:28:27 -050030import android.provider.MediaStore;
Mike Lockwood9a2046f2010-08-03 15:30:09 -040031import android.provider.MediaStore.Audio;
Mike Lockwood3b2a62e2010-09-08 12:47:57 -040032import android.provider.MediaStore.Files;
Mike Lockwoodae078f72010-09-26 12:35:51 -040033import android.provider.MediaStore.Images;
34import android.provider.MediaStore.MediaColumns;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040035import android.util.Log;
Mike Lockwoodea93fa12010-12-07 10:41:35 -080036import android.view.Display;
37import android.view.WindowManager;
Mike Lockwoodd21eac92010-07-03 00:44:05 -040038
Mike Lockwood5ebac832010-10-12 11:33:47 -040039import java.io.File;
Mike Lockwood7d7fb632010-12-01 18:46:23 -050040import java.util.HashMap;
dujin.chafe464a72011-11-22 12:13:33 +090041import java.util.Locale;
Mike Lockwood5ebac832010-10-12 11:33:47 -040042
Mike Lockwoodd21eac92010-07-03 00:44:05 -040043/**
44 * {@hide}
45 */
46public class MtpDatabase {
47
48 private static final String TAG = "MtpDatabase";
49
Mike Lockwood2837eef2010-08-31 16:25:12 -040050 private final Context mContext;
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 Lockwoodf6f16612012-09-12 15:50:59 -070099 Files.FileColumns.DATE_MODIFIED, // 5
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400100 };
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400101 private static final String ID_WHERE = Files.FileColumns._ID + "=?";
Mike Lockwoodbafca212010-12-13 21:50:09 -0800102 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400103
104 private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
Mike Lockwood58e6831c2012-09-11 10:49:34 -0700105 private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
106 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400107 private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400108 + Files.FileColumns.FORMAT + "=?";
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400109 private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
110 + Files.FileColumns.PARENT + "=?";
111 private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
112 + Files.FileColumns.PARENT + "=?";
113 private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
114 + Files.FileColumns.PARENT + "=?";
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400115
Mike Lockwoodd815f792010-07-12 08:49:01 -0400116 private final MediaScanner mMediaScanner;
117
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400118 static {
119 System.loadLibrary("media_jni");
120 }
121
Mike Lockwood73e56d92011-12-01 16:58:41 -0500122 public MtpDatabase(Context context, String volumeName, String storagePath,
123 String[] subDirectories) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400124 native_setup();
125
Mike Lockwood2837eef2010-08-31 16:25:12 -0400126 mContext = context;
Dianne Hackborn35654b62013-01-14 17:38:02 -0800127 mPackageName = context.getPackageName();
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400128 mMediaProvider = context.getContentResolver().acquireProvider("media");
129 mVolumeName = volumeName;
Mike Lockwood01788562010-10-11 11:22:19 -0400130 mMediaStoragePath = storagePath;
Mike Lockwood8490e662010-09-09 14:16:22 -0400131 mObjectsUri = Files.getMtpObjectsUri(volumeName);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400132 mMediaScanner = new MediaScanner(context);
dujin.chafe464a72011-11-22 12:13:33 +0900133
Mike Lockwood73e56d92011-12-01 16:58:41 -0500134 mSubDirectories = subDirectories;
135 if (subDirectories != null) {
136 // Compute "where" string for restricting queries to subdirectories
137 StringBuilder builder = new StringBuilder();
138 builder.append("(");
139 int count = subDirectories.length;
140 for (int i = 0; i < count; i++) {
141 builder.append(Files.FileColumns.DATA + "=? OR "
142 + Files.FileColumns.DATA + " LIKE ?");
143 if (i != count - 1) {
144 builder.append(" OR ");
145 }
146 }
147 builder.append(")");
148 mSubDirectoriesWhere = builder.toString();
149
150 // Compute "where" arguments for restricting queries to subdirectories
151 mSubDirectoriesWhereArgs = new String[count * 2];
152 for (int i = 0, j = 0; i < count; i++) {
153 String path = subDirectories[i];
154 mSubDirectoriesWhereArgs[j++] = path;
155 mSubDirectoriesWhereArgs[j++] = path + "/%";
156 }
157 }
158
dujin.chafe464a72011-11-22 12:13:33 +0900159 // Set locale to MediaScanner.
160 Locale locale = context.getResources().getConfiguration().locale;
161 if (locale != null) {
162 String language = locale.getLanguage();
163 String country = locale.getCountry();
164 if (language != null) {
165 if (country != null) {
166 mMediaScanner.setLocale(language + "_" + country);
167 } else {
168 mMediaScanner.setLocale(language);
169 }
170 }
171 }
Mike Lockwood775de952011-03-05 17:34:11 -0500172 initDeviceProperties(context);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400173 }
174
175 @Override
Mike Lockwooddbead322010-08-30 09:27:55 -0400176 protected void finalize() throws Throwable {
177 try {
178 native_finalize();
Mike Lockwooddbead322010-08-30 09:27:55 -0400179 } finally {
180 super.finalize();
181 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400182 }
183
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400184 public void addStorage(MtpStorage storage) {
185 mStorageMap.put(storage.getPath(), storage);
186 }
187
188 public void removeStorage(MtpStorage storage) {
189 mStorageMap.remove(storage.getPath());
190 }
191
Mike Lockwood775de952011-03-05 17:34:11 -0500192 private void initDeviceProperties(Context context) {
193 final String devicePropertiesName = "device-properties";
194 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
195 File databaseFile = context.getDatabasePath(devicePropertiesName);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400196
Mike Lockwood775de952011-03-05 17:34:11 -0500197 if (databaseFile.exists()) {
198 // for backward compatibility - read device properties from sqlite database
199 // and migrate them to shared prefs
200 SQLiteDatabase db = null;
201 Cursor c = null;
202 try {
203 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
204 if (db != null) {
205 c = db.query("properties", new String[] { "_id", "code", "value" },
206 null, null, null, null, null);
207 if (c != null) {
208 SharedPreferences.Editor e = mDeviceProperties.edit();
209 while (c.moveToNext()) {
210 String name = c.getString(1);
211 String value = c.getString(2);
212 e.putString(name, value);
213 }
214 e.commit();
215 }
216 }
217 } catch (Exception e) {
218 Log.e(TAG, "failed to migrate device properties", e);
219 } finally {
220 if (c != null) c.close();
221 if (db != null) db.close();
222 }
223 databaseFile.delete();
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400224 }
225 }
226
Mike Lockwood73e56d92011-12-01 16:58:41 -0500227 // check to see if the path is contained in one of our storage subdirectories
228 // returns true if we have no special subdirectories
229 private boolean inStorageSubDirectory(String path) {
230 if (mSubDirectories == null) return true;
231 if (path == null) return false;
232
233 boolean allowed = false;
234 int pathLength = path.length();
235 for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
236 String subdir = mSubDirectories[i];
237 int subdirLength = subdir.length();
238 if (subdirLength < pathLength &&
239 path.charAt(subdirLength) == '/' &&
240 path.startsWith(subdir)) {
241 allowed = true;
242 }
243 }
244 return allowed;
245 }
246
247 // check to see if the path matches one of our storage subdirectories
248 // returns true if we have no special subdirectories
249 private boolean isStorageSubDirectory(String path) {
250 if (mSubDirectories == null) return false;
251 for (int i = 0; i < mSubDirectories.length; i++) {
252 if (path.equals(mSubDirectories[i])) {
253 return true;
254 }
255 }
256 return false;
257 }
258
Mike Lockwoodd815f792010-07-12 08:49:01 -0400259 private int beginSendObject(String path, int format, int parent,
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400260 int storageId, long size, long modified) {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500261 // if mSubDirectories is not null, do not allow copying files to any other locations
262 if (!inStorageSubDirectory(path)) return -1;
263
264 // make sure the object does not exist
Mike Lockwoodbafca212010-12-13 21:50:09 -0800265 if (path != null) {
266 Cursor c = null;
267 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800268 c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800269 new String[] { path }, null, null);
Mike Lockwoodbafca212010-12-13 21:50:09 -0800270 if (c != null && c.getCount() > 0) {
271 Log.w(TAG, "file already exists in beginSendObject: " + path);
272 return -1;
273 }
274 } catch (RemoteException e) {
275 Log.e(TAG, "RemoteException in beginSendObject", e);
276 } finally {
277 if (c != null) {
278 c.close();
279 }
280 }
281 }
282
Mike Lockwood2837eef2010-08-31 16:25:12 -0400283 mDatabaseModified = true;
Mike Lockwoodd815f792010-07-12 08:49:01 -0400284 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400285 values.put(Files.FileColumns.DATA, path);
286 values.put(Files.FileColumns.FORMAT, format);
287 values.put(Files.FileColumns.PARENT, parent);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400288 values.put(Files.FileColumns.STORAGE_ID, storageId);
Mike Lockwood3b2a62e2010-09-08 12:47:57 -0400289 values.put(Files.FileColumns.SIZE, size);
290 values.put(Files.FileColumns.DATE_MODIFIED, modified);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400291
292 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800293 Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
Mike Lockwoodd815f792010-07-12 08:49:01 -0400294 if (uri != null) {
295 return Integer.parseInt(uri.getPathSegments().get(2));
296 } else {
297 return -1;
298 }
299 } catch (RemoteException e) {
300 Log.e(TAG, "RemoteException in beginSendObject", e);
301 return -1;
302 }
303 }
304
Mike Lockwood7a0bd172011-01-18 11:06:19 -0800305 private void endSendObject(String path, int handle, int format, boolean succeeded) {
Mike Lockwoodd815f792010-07-12 08:49:01 -0400306 if (succeeded) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400307 // handle abstract playlists separately
308 // they do not exist in the file system so don't use the media scanner here
Mike Lockwood5367ab62010-08-30 13:23:02 -0400309 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400310 // extract name from path
311 String name = path;
312 int lastSlash = name.lastIndexOf('/');
313 if (lastSlash >= 0) {
314 name = name.substring(lastSlash + 1);
315 }
Mike Lockwood8cc6eb12011-01-18 13:13:05 -0800316 // strip trailing ".pla" from the name
317 if (name.endsWith(".pla")) {
318 name = name.substring(0, name.length() - 4);
319 }
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400320
321 ContentValues values = new ContentValues(1);
322 values.put(Audio.Playlists.DATA, path);
323 values.put(Audio.Playlists.NAME, name);
Mike Lockwood0b58c192010-11-17 15:42:09 -0500324 values.put(Files.FileColumns.FORMAT, format);
Mike Lockwood8ed67ac2011-01-18 13:27:25 -0800325 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400326 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
327 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800328 Uri uri = mMediaProvider.insert(mPackageName,
329 Audio.Playlists.EXTERNAL_CONTENT_URI, values);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400330 } catch (RemoteException e) {
331 Log.e(TAG, "RemoteException in endSendObject", e);
332 }
333 } else {
Mike Lockwoodc37255d2010-09-10 14:47:36 -0400334 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400335 }
Mike Lockwoodd815f792010-07-12 08:49:01 -0400336 } else {
337 deleteFile(handle);
338 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400339 }
340
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400341 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
Mike Lockwood73e56d92011-12-01 16:58:41 -0500342 String where;
343 String[] whereArgs;
344
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400345 if (storageID == 0xFFFFFFFF) {
346 // query all stores
347 if (format == 0) {
348 // query all formats
349 if (parent == 0) {
350 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500351 where = null;
352 whereArgs = null;
353 } else {
354 if (parent == 0xFFFFFFFF) {
355 // all objects in root of store
356 parent = 0;
357 }
358 where = PARENT_WHERE;
359 whereArgs = new String[] { Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400360 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400361 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400362 // query specific format
363 if (parent == 0) {
364 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500365 where = FORMAT_WHERE;
366 whereArgs = new String[] { Integer.toString(format) };
367 } else {
368 if (parent == 0xFFFFFFFF) {
369 // all objects in root of store
370 parent = 0;
371 }
372 where = FORMAT_PARENT_WHERE;
373 whereArgs = new String[] { Integer.toString(format),
374 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400375 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400376 }
377 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400378 // query specific store
379 if (format == 0) {
380 // query all formats
381 if (parent == 0) {
382 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500383 where = STORAGE_WHERE;
384 whereArgs = new String[] { Integer.toString(storageID) };
385 } else {
386 if (parent == 0xFFFFFFFF) {
387 // all objects in root of store
388 parent = 0;
389 }
390 where = STORAGE_PARENT_WHERE;
391 whereArgs = new String[] { Integer.toString(storageID),
392 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400393 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400394 } else {
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400395 // query specific format
396 if (parent == 0) {
397 // query all objects
Mike Lockwood73e56d92011-12-01 16:58:41 -0500398 where = STORAGE_FORMAT_WHERE;
399 whereArgs = new String[] { Integer.toString(storageID),
400 Integer.toString(format) };
401 } else {
402 if (parent == 0xFFFFFFFF) {
403 // all objects in root of store
404 parent = 0;
405 }
406 where = STORAGE_FORMAT_PARENT_WHERE;
407 whereArgs = new String[] { Integer.toString(storageID),
408 Integer.toString(format),
409 Integer.toString(parent) };
Mike Lockwood6acc90f2011-06-17 13:44:24 -0400410 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400411 }
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400412 }
Mike Lockwood73e56d92011-12-01 16:58:41 -0500413
414 // if we are restricting queries to mSubDirectories, we need to add the restriction
415 // onto our "where" arguments
416 if (mSubDirectoriesWhere != null) {
417 if (where == null) {
418 where = mSubDirectoriesWhere;
419 whereArgs = mSubDirectoriesWhereArgs;
420 } else {
421 where = where + " AND " + mSubDirectoriesWhere;
422
423 // create new array to hold whereArgs and mSubDirectoriesWhereArgs
424 String[] newWhereArgs =
425 new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
426 int i, j;
427 for (i = 0; i < whereArgs.length; i++) {
428 newWhereArgs[i] = whereArgs[i];
429 }
430 for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
431 newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
432 }
433 whereArgs = newWhereArgs;
434 }
435 }
436
Dianne Hackborn35654b62013-01-14 17:38:02 -0800437 return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
438 whereArgs, null, null);
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400439 }
440
441 private int[] getObjectList(int storageID, int format, int parent) {
442 Cursor c = null;
443 try {
444 c = createObjectQuery(storageID, format, parent);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400445 if (c == null) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400446 return null;
447 }
448 int count = c.getCount();
449 if (count > 0) {
450 int[] result = new int[count];
451 for (int i = 0; i < count; i++) {
452 c.moveToNext();
453 result[i] = c.getInt(0);
454 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400455 return result;
456 }
457 } catch (RemoteException e) {
458 Log.e(TAG, "RemoteException in getObjectList", e);
459 } finally {
460 if (c != null) {
461 c.close();
462 }
463 }
464 return null;
465 }
466
Mike Lockwood7a047c82010-08-02 10:52:20 -0400467 private int getNumObjects(int storageID, int format, int parent) {
Mike Lockwood7a047c82010-08-02 10:52:20 -0400468 Cursor c = null;
469 try {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400470 c = createObjectQuery(storageID, format, parent);
Mike Lockwood7a047c82010-08-02 10:52:20 -0400471 if (c != null) {
472 return c.getCount();
473 }
474 } catch (RemoteException e) {
475 Log.e(TAG, "RemoteException in getNumObjects", e);
476 } finally {
477 if (c != null) {
478 c.close();
479 }
480 }
481 return -1;
482 }
483
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400484 private int[] getSupportedPlaybackFormats() {
485 return new int[] {
Mike Lockwoode5211692010-09-08 13:50:45 -0400486 // allow transfering arbitrary files
487 MtpConstants.FORMAT_UNDEFINED,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400488
Mike Lockwood792ec842010-09-09 15:30:10 -0400489 MtpConstants.FORMAT_ASSOCIATION,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400490 MtpConstants.FORMAT_TEXT,
491 MtpConstants.FORMAT_HTML,
492 MtpConstants.FORMAT_WAV,
493 MtpConstants.FORMAT_MP3,
494 MtpConstants.FORMAT_MPEG,
495 MtpConstants.FORMAT_EXIF_JPEG,
496 MtpConstants.FORMAT_TIFF_EP,
bo huang240582e2012-02-27 16:27:00 +0800497 MtpConstants.FORMAT_BMP,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400498 MtpConstants.FORMAT_GIF,
499 MtpConstants.FORMAT_JFIF,
500 MtpConstants.FORMAT_PNG,
501 MtpConstants.FORMAT_TIFF,
502 MtpConstants.FORMAT_WMA,
503 MtpConstants.FORMAT_OGG,
504 MtpConstants.FORMAT_AAC,
505 MtpConstants.FORMAT_MP4_CONTAINER,
506 MtpConstants.FORMAT_MP2,
507 MtpConstants.FORMAT_3GP_CONTAINER,
Mike Lockwood792ec842010-09-09 15:30:10 -0400508 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
Mike Lockwood12b8a992010-09-23 21:33:29 -0400509 MtpConstants.FORMAT_WPL_PLAYLIST,
510 MtpConstants.FORMAT_M3U_PLAYLIST,
511 MtpConstants.FORMAT_PLS_PLAYLIST,
512 MtpConstants.FORMAT_XML_DOCUMENT,
Glenn Kastenf9f223e2011-01-13 11:17:00 -0800513 MtpConstants.FORMAT_FLAC,
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400514 };
515 }
516
517 private int[] getSupportedCaptureFormats() {
518 // no capture formats yet
519 return null;
520 }
521
Mike Lockwoodae078f72010-09-26 12:35:51 -0400522 static final int[] FILE_PROPERTIES = {
523 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
524 // and IMAGE_PROPERTIES below
Mike Lockwood5367ab62010-08-30 13:23:02 -0400525 MtpConstants.PROPERTY_STORAGE_ID,
526 MtpConstants.PROPERTY_OBJECT_FORMAT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400527 MtpConstants.PROPERTY_PROTECTION_STATUS,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400528 MtpConstants.PROPERTY_OBJECT_SIZE,
529 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400530 MtpConstants.PROPERTY_DATE_MODIFIED,
Mike Lockwood5367ab62010-08-30 13:23:02 -0400531 MtpConstants.PROPERTY_PARENT_OBJECT,
Mike Lockwoodd3bfecb2010-09-23 23:04:28 -0400532 MtpConstants.PROPERTY_PERSISTENT_UID,
533 MtpConstants.PROPERTY_NAME,
Mike Lockwoodae078f72010-09-26 12:35:51 -0400534 MtpConstants.PROPERTY_DATE_ADDED,
535 };
536
537 static final int[] AUDIO_PROPERTIES = {
538 // NOTE must match FILE_PROPERTIES above
539 MtpConstants.PROPERTY_STORAGE_ID,
540 MtpConstants.PROPERTY_OBJECT_FORMAT,
541 MtpConstants.PROPERTY_PROTECTION_STATUS,
542 MtpConstants.PROPERTY_OBJECT_SIZE,
543 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
544 MtpConstants.PROPERTY_DATE_MODIFIED,
545 MtpConstants.PROPERTY_PARENT_OBJECT,
546 MtpConstants.PROPERTY_PERSISTENT_UID,
547 MtpConstants.PROPERTY_NAME,
548 MtpConstants.PROPERTY_DISPLAY_NAME,
549 MtpConstants.PROPERTY_DATE_ADDED,
550
551 // audio specific properties
552 MtpConstants.PROPERTY_ARTIST,
553 MtpConstants.PROPERTY_ALBUM_NAME,
554 MtpConstants.PROPERTY_ALBUM_ARTIST,
555 MtpConstants.PROPERTY_TRACK,
556 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
557 MtpConstants.PROPERTY_DURATION,
558 MtpConstants.PROPERTY_GENRE,
559 MtpConstants.PROPERTY_COMPOSER,
560 };
561
562 static final int[] VIDEO_PROPERTIES = {
563 // NOTE must match FILE_PROPERTIES above
564 MtpConstants.PROPERTY_STORAGE_ID,
565 MtpConstants.PROPERTY_OBJECT_FORMAT,
566 MtpConstants.PROPERTY_PROTECTION_STATUS,
567 MtpConstants.PROPERTY_OBJECT_SIZE,
568 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
569 MtpConstants.PROPERTY_DATE_MODIFIED,
570 MtpConstants.PROPERTY_PARENT_OBJECT,
571 MtpConstants.PROPERTY_PERSISTENT_UID,
572 MtpConstants.PROPERTY_NAME,
573 MtpConstants.PROPERTY_DISPLAY_NAME,
574 MtpConstants.PROPERTY_DATE_ADDED,
575
576 // video specific properties
577 MtpConstants.PROPERTY_ARTIST,
578 MtpConstants.PROPERTY_ALBUM_NAME,
579 MtpConstants.PROPERTY_DURATION,
580 MtpConstants.PROPERTY_DESCRIPTION,
581 };
582
583 static final int[] IMAGE_PROPERTIES = {
584 // NOTE must match FILE_PROPERTIES above
585 MtpConstants.PROPERTY_STORAGE_ID,
586 MtpConstants.PROPERTY_OBJECT_FORMAT,
587 MtpConstants.PROPERTY_PROTECTION_STATUS,
588 MtpConstants.PROPERTY_OBJECT_SIZE,
589 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
590 MtpConstants.PROPERTY_DATE_MODIFIED,
591 MtpConstants.PROPERTY_PARENT_OBJECT,
592 MtpConstants.PROPERTY_PERSISTENT_UID,
593 MtpConstants.PROPERTY_NAME,
594 MtpConstants.PROPERTY_DISPLAY_NAME,
595 MtpConstants.PROPERTY_DATE_ADDED,
596
597 // image specific properties
598 MtpConstants.PROPERTY_DESCRIPTION,
599 };
600
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500601 static final int[] ALL_PROPERTIES = {
602 // NOTE must match FILE_PROPERTIES above
603 MtpConstants.PROPERTY_STORAGE_ID,
604 MtpConstants.PROPERTY_OBJECT_FORMAT,
605 MtpConstants.PROPERTY_PROTECTION_STATUS,
606 MtpConstants.PROPERTY_OBJECT_SIZE,
607 MtpConstants.PROPERTY_OBJECT_FILE_NAME,
608 MtpConstants.PROPERTY_DATE_MODIFIED,
609 MtpConstants.PROPERTY_PARENT_OBJECT,
610 MtpConstants.PROPERTY_PERSISTENT_UID,
611 MtpConstants.PROPERTY_NAME,
612 MtpConstants.PROPERTY_DISPLAY_NAME,
613 MtpConstants.PROPERTY_DATE_ADDED,
614
615 // image specific properties
616 MtpConstants.PROPERTY_DESCRIPTION,
617
618 // audio specific properties
619 MtpConstants.PROPERTY_ARTIST,
620 MtpConstants.PROPERTY_ALBUM_NAME,
621 MtpConstants.PROPERTY_ALBUM_ARTIST,
622 MtpConstants.PROPERTY_TRACK,
623 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
624 MtpConstants.PROPERTY_DURATION,
625 MtpConstants.PROPERTY_GENRE,
626 MtpConstants.PROPERTY_COMPOSER,
627
628 // video specific properties
629 MtpConstants.PROPERTY_ARTIST,
630 MtpConstants.PROPERTY_ALBUM_NAME,
631 MtpConstants.PROPERTY_DURATION,
632 MtpConstants.PROPERTY_DESCRIPTION,
633
634 // image specific properties
635 MtpConstants.PROPERTY_DESCRIPTION,
636 };
637
Mike Lockwoodae078f72010-09-26 12:35:51 -0400638 private int[] getSupportedObjectProperties(int format) {
639 switch (format) {
640 case MtpConstants.FORMAT_MP3:
641 case MtpConstants.FORMAT_WAV:
642 case MtpConstants.FORMAT_WMA:
643 case MtpConstants.FORMAT_OGG:
644 case MtpConstants.FORMAT_AAC:
645 return AUDIO_PROPERTIES;
646 case MtpConstants.FORMAT_MPEG:
647 case MtpConstants.FORMAT_3GP_CONTAINER:
648 case MtpConstants.FORMAT_WMV:
649 return VIDEO_PROPERTIES;
650 case MtpConstants.FORMAT_EXIF_JPEG:
651 case MtpConstants.FORMAT_GIF:
652 case MtpConstants.FORMAT_PNG:
653 case MtpConstants.FORMAT_BMP:
654 return IMAGE_PROPERTIES;
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500655 case 0:
656 return ALL_PROPERTIES;
Mike Lockwoodae078f72010-09-26 12:35:51 -0400657 default:
658 return FILE_PROPERTIES;
659 }
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400660 }
661
662 private int[] getSupportedDeviceProperties() {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400663 return new int[] {
664 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
665 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800666 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400667 };
Mike Lockwood4b322ce2010-08-10 07:37:50 -0400668 }
669
Mike Lockwoodae078f72010-09-26 12:35:51 -0400670
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500671 private MtpPropertyList getObjectPropertyList(long handle, int format, long property,
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400672 int groupCode, int depth) {
673 // FIXME - implement group support
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400674 if (groupCode != 0) {
675 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
676 }
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400677
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500678 MtpPropertyGroup propertyGroup;
679 if (property == 0xFFFFFFFFL) {
680 propertyGroup = mPropertyGroupsByFormat.get(format);
681 if (propertyGroup == null) {
682 int[] propertyList = getSupportedObjectProperties(format);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800683 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
684 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500685 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400686 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500687 } else {
688 propertyGroup = mPropertyGroupsByProperty.get(property);
689 if (propertyGroup == null) {
690 int[] propertyList = new int[] { (int)property };
Dianne Hackborn35654b62013-01-14 17:38:02 -0800691 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
692 mVolumeName, propertyList);
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500693 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400694 }
695 }
Mike Lockwood7d7fb632010-12-01 18:46:23 -0500696
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400697 return propertyGroup.getPropertyList((int)handle, format, depth);
Mike Lockwoode2ad6ec2010-10-14 18:03:25 -0400698 }
699
Mike Lockwood5ebac832010-10-12 11:33:47 -0400700 private int renameFile(int handle, String newName) {
701 Cursor c = null;
702
703 // first compute current path
704 String path = null;
Mike Lockwood5ebac832010-10-12 11:33:47 -0400705 String[] whereArgs = new String[] { Integer.toString(handle) };
706 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800707 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
708 whereArgs, null, null);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400709 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500710 path = c.getString(1);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400711 }
712 } catch (RemoteException e) {
713 Log.e(TAG, "RemoteException in getObjectFilePath", e);
714 return MtpConstants.RESPONSE_GENERAL_ERROR;
715 } finally {
716 if (c != null) {
717 c.close();
718 }
719 }
720 if (path == null) {
721 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
722 }
Mike Lockwood5ebac832010-10-12 11:33:47 -0400723
Mike Lockwood73e56d92011-12-01 16:58:41 -0500724 // do not allow renaming any of the special subdirectories
725 if (isStorageSubDirectory(path)) {
726 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
727 }
728
Mike Lockwood5ebac832010-10-12 11:33:47 -0400729 // now rename the file. make sure this succeeds before updating database
730 File oldFile = new File(path);
731 int lastSlash = path.lastIndexOf('/');
732 if (lastSlash <= 1) {
733 return MtpConstants.RESPONSE_GENERAL_ERROR;
734 }
735 String newPath = path.substring(0, lastSlash + 1) + newName;
736 File newFile = new File(newPath);
737 boolean success = oldFile.renameTo(newFile);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400738 if (!success) {
Mike Lockwoodf26a5862011-01-21 21:00:54 -0800739 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
Mike Lockwood5ebac832010-10-12 11:33:47 -0400740 return MtpConstants.RESPONSE_GENERAL_ERROR;
741 }
742
743 // finally update database
744 ContentValues values = new ContentValues();
745 values.put(Files.FileColumns.DATA, newPath);
746 int updated = 0;
747 try {
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400748 // note - we are relying on a special case in MediaProvider.update() to update
749 // the paths for all children in the case where this is a directory.
Dianne Hackborn35654b62013-01-14 17:38:02 -0800750 updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
Mike Lockwood5ebac832010-10-12 11:33:47 -0400751 } catch (RemoteException e) {
752 Log.e(TAG, "RemoteException in mMediaProvider.update", e);
753 }
Mike Lockwood6a6a3af2010-10-12 14:19:51 -0400754 if (updated == 0) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400755 Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
756 // this shouldn't happen, but if it does we need to rename the file to its original name
757 newFile.renameTo(oldFile);
758 return MtpConstants.RESPONSE_GENERAL_ERROR;
759 }
760
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800761 // check if nomedia status changed
762 if (newFile.isDirectory()) {
763 // for directories, check if renamed from something hidden to something non-hidden
764 if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
765 // directory was unhidden
766 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800767 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800768 } catch (RemoteException e) {
769 Log.e(TAG, "failed to unhide/rescan for " + newPath);
770 }
771 }
772 } else {
773 // for files, check if renamed from .nomedia to something else
774 if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
775 && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
776 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800777 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800778 } catch (RemoteException e) {
779 Log.e(TAG, "failed to unhide/rescan for " + newPath);
780 }
781 }
782 }
783
Mike Lockwood5ebac832010-10-12 11:33:47 -0400784 return MtpConstants.RESPONSE_OK;
785 }
786
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400787 private int setObjectProperty(int handle, int property,
788 long intValue, String stringValue) {
Mike Lockwood5ebac832010-10-12 11:33:47 -0400789 switch (property) {
790 case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
791 return renameFile(handle, stringValue);
792
793 default:
794 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
795 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400796 }
797
798 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400799 switch (property) {
800 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
801 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500802 // writable string properties kept in shared preferences
803 String value = mDeviceProperties.getString(Integer.toString(property), "");
804 int length = value.length();
805 if (length > 255) {
806 length = 255;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400807 }
Mike Lockwood775de952011-03-05 17:34:11 -0500808 value.getChars(0, length, outStringValue, 0);
809 outStringValue[length] = 0;
810 return MtpConstants.RESPONSE_OK;
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400811
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800812 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
813 // use screen size as max image size
814 Display display = ((WindowManager)mContext.getSystemService(
815 Context.WINDOW_SERVICE)).getDefaultDisplay();
Dianne Hackborn44bc17c2011-04-20 18:18:51 -0700816 int width = display.getMaximumSizeDimension();
817 int height = display.getMaximumSizeDimension();
Mike Lockwoodea93fa12010-12-07 10:41:35 -0800818 String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
819 imageSize.getChars(0, imageSize.length(), outStringValue, 0);
820 outStringValue[imageSize.length()] = 0;
821 return MtpConstants.RESPONSE_OK;
822
823 default:
824 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
825 }
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400826 }
827
828 private int setDeviceProperty(int property, long intValue, String stringValue) {
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400829 switch (property) {
830 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
831 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
Mike Lockwood775de952011-03-05 17:34:11 -0500832 // writable string properties kept in shared prefs
833 SharedPreferences.Editor e = mDeviceProperties.edit();
834 e.putString(Integer.toString(property), stringValue);
835 return (e.commit() ? MtpConstants.RESPONSE_OK
836 : MtpConstants.RESPONSE_GENERAL_ERROR);
Mike Lockwood59e3f0d2010-09-02 14:57:30 -0400837 }
838
839 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
840 }
841
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400842 private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700843 char[] outName, long[] outModified) {
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400844 Cursor c = null;
845 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800846 c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800847 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400848 if (c != null && c.moveToNext()) {
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400849 outStorageFormatParent[0] = c.getInt(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400850 outStorageFormatParent[1] = c.getInt(2);
851 outStorageFormatParent[2] = c.getInt(3);
852
853 // extract name from path
Mike Lockwoodb239b6832011-04-05 10:21:27 -0400854 String path = c.getString(4);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400855 int lastSlash = path.lastIndexOf('/');
856 int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
857 int end = path.length();
858 if (end - start > 255) {
859 end = start + 255;
860 }
861 path.getChars(start, end, outName, 0);
862 outName[end - start] = 0;
863
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700864 outModified[0] = c.getLong(5);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400865 return true;
866 }
867 } catch (RemoteException e) {
Mike Lockwood2b5f9ad12010-10-29 19:16:27 -0400868 Log.e(TAG, "RemoteException in getObjectInfo", e);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400869 } finally {
870 if (c != null) {
871 c.close();
872 }
873 }
874 return false;
875 }
876
Mike Lockwood365e03e2010-12-08 16:08:01 -0800877 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
Mike Lockwood01788562010-10-11 11:22:19 -0400878 if (handle == 0) {
879 // special case root directory
880 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
881 outFilePath[mMediaStoragePath.length()] = 0;
Mike Lockwood365e03e2010-12-08 16:08:01 -0800882 outFileLengthFormat[0] = 0;
883 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
Mike Lockwood01788562010-10-11 11:22:19 -0400884 return MtpConstants.RESPONSE_OK;
885 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400886 Cursor c = null;
887 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800888 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800889 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400890 if (c != null && c.moveToNext()) {
Mike Lockwood1c4e88d2011-01-12 12:38:41 -0500891 String path = c.getString(1);
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400892 path.getChars(0, path.length(), outFilePath, 0);
893 outFilePath[path.length()] = 0;
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700894 // File transfers from device to host will likely fail if the size is incorrect.
895 // So to be safe, use the actual file size here.
896 outFileLengthFormat[0] = new File(path).length();
897 outFileLengthFormat[1] = c.getLong(2);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400898 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400899 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400900 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400901 }
902 } catch (RemoteException e) {
903 Log.e(TAG, "RemoteException in getObjectFilePath", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400904 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400905 } finally {
906 if (c != null) {
907 c.close();
908 }
909 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400910 }
911
Mike Lockwood59c777a2010-08-02 10:37:41 -0400912 private int deleteFile(int handle) {
Mike Lockwood2837eef2010-08-31 16:25:12 -0400913 mDatabaseModified = true;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800914 String path = null;
915 int format = 0;
916
917 Cursor c = null;
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400918 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800919 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800920 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800921 if (c != null && c.moveToNext()) {
922 // don't convert to media path here, since we will be matching
923 // against paths in the database matching /data/media
924 path = c.getString(1);
Mike Lockwoodf6f16612012-09-12 15:50:59 -0700925 format = c.getInt(2);
Mike Lockwood55f808c2010-12-14 13:14:29 -0800926 } else {
927 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
928 }
929
930 if (path == null || format == 0) {
931 return MtpConstants.RESPONSE_GENERAL_ERROR;
932 }
933
Mike Lockwood73e56d92011-12-01 16:58:41 -0500934 // do not allow deleting any of the special subdirectories
935 if (isStorageSubDirectory(path)) {
936 return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
937 }
938
Mike Lockwood55f808c2010-12-14 13:14:29 -0800939 if (format == MtpConstants.FORMAT_ASSOCIATION) {
940 // recursive case - delete all children first
941 Uri uri = Files.getMtpObjectsUri(mVolumeName);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800942 int count = mMediaProvider.delete(mPackageName, uri,
Mike Lockwood1e855d92012-06-26 16:31:41 -0700943 // the 'like' makes it use the index, the 'lower()' makes it correct
944 // when the path contains sqlite wildcard characters
945 "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
946 new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
Mike Lockwood55f808c2010-12-14 13:14:29 -0800947 }
948
949 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
Dianne Hackborn35654b62013-01-14 17:38:02 -0800950 if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800951 if (format != MtpConstants.FORMAT_ASSOCIATION
952 && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
953 try {
954 String parentPath = path.substring(0, path.lastIndexOf("/"));
Dianne Hackborn35654b62013-01-14 17:38:02 -0800955 mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
Marco Nelissenca78f3d2012-01-27 09:43:20 -0800956 } catch (RemoteException e) {
957 Log.e(TAG, "failed to unhide/rescan for " + path);
958 }
959 }
Mike Lockwood5367ab62010-08-30 13:23:02 -0400960 return MtpConstants.RESPONSE_OK;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400961 } else {
Mike Lockwood5367ab62010-08-30 13:23:02 -0400962 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
Mike Lockwood59c777a2010-08-02 10:37:41 -0400963 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400964 } catch (RemoteException e) {
965 Log.e(TAG, "RemoteException in deleteFile", e);
Mike Lockwood5367ab62010-08-30 13:23:02 -0400966 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood55f808c2010-12-14 13:14:29 -0800967 } finally {
968 if (c != null) {
969 c.close();
970 }
Mike Lockwoodd21eac92010-07-03 00:44:05 -0400971 }
972 }
973
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400974 private int[] getObjectReferences(int handle) {
Mike Lockwood8490e662010-09-09 14:16:22 -0400975 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400976 Cursor c = null;
977 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -0800978 c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
Mike Lockwood9a2046f2010-08-03 15:30:09 -0400979 if (c == null) {
980 return null;
981 }
982 int count = c.getCount();
983 if (count > 0) {
984 int[] result = new int[count];
985 for (int i = 0; i < count; i++) {
986 c.moveToNext();
987 result[i] = c.getInt(0);
988 }
989 return result;
990 }
991 } catch (RemoteException e) {
992 Log.e(TAG, "RemoteException in getObjectList", e);
993 } finally {
994 if (c != null) {
995 c.close();
996 }
997 }
998 return null;
999 }
1000
1001 private int setObjectReferences(int handle, int[] references) {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001002 mDatabaseModified = true;
Mike Lockwood8490e662010-09-09 14:16:22 -04001003 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001004 int count = references.length;
1005 ContentValues[] valuesList = new ContentValues[count];
1006 for (int i = 0; i < count; i++) {
1007 ContentValues values = new ContentValues();
Mike Lockwood3b2a62e2010-09-08 12:47:57 -04001008 values.put(Files.FileColumns._ID, references[i]);
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001009 valuesList[i] = values;
1010 }
1011 try {
Dianne Hackborn35654b62013-01-14 17:38:02 -08001012 if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
Mike Lockwood5367ab62010-08-30 13:23:02 -04001013 return MtpConstants.RESPONSE_OK;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001014 }
1015 } catch (RemoteException e) {
1016 Log.e(TAG, "RemoteException in setObjectReferences", e);
1017 }
Mike Lockwood5367ab62010-08-30 13:23:02 -04001018 return MtpConstants.RESPONSE_GENERAL_ERROR;
Mike Lockwood9a2046f2010-08-03 15:30:09 -04001019 }
1020
Mike Lockwood2837eef2010-08-31 16:25:12 -04001021 private void sessionStarted() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001022 mDatabaseModified = false;
1023 }
1024
1025 private void sessionEnded() {
Mike Lockwood2837eef2010-08-31 16:25:12 -04001026 if (mDatabaseModified) {
Mike Lockwooda3156052010-11-20 12:28:27 -05001027 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
Mike Lockwood2837eef2010-08-31 16:25:12 -04001028 mDatabaseModified = false;
1029 }
1030 }
1031
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001032 // used by the JNI code
1033 private int mNativeContext;
1034
1035 private native final void native_setup();
1036 private native final void native_finalize();
Mike Lockwoodd21eac92010-07-03 00:44:05 -04001037}