MtpDatabase.java revision 2837eefc5459427138c080d445bb491c75630163
1/* 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 17package android.media; 18 19import android.content.Context; 20import android.content.ContentValues; 21import android.content.IContentProvider; 22import android.content.Intent; 23import android.database.Cursor; 24import android.net.Uri; 25import android.os.RemoteException; 26import android.provider.MediaStore.Audio; 27import android.provider.MediaStore.MediaColumns; 28import android.provider.MediaStore.MtpObjects; 29import android.provider.Mtp; 30import android.util.Log; 31 32/** 33 * {@hide} 34 */ 35public class MtpDatabase { 36 37 private static final String TAG = "MtpDatabase"; 38 39 private final Context mContext; 40 private final IContentProvider mMediaProvider; 41 private final String mVolumeName; 42 private final Uri mObjectsUri; 43 44 // true if the database has been modified in the current MTP session 45 private boolean mDatabaseModified; 46 47 // FIXME - this should be passed in via the constructor 48 private final int mStorageID = 0x00010001; 49 50 private static final String[] ID_PROJECTION = new String[] { 51 MtpObjects.ObjectColumns._ID, // 0 52 }; 53 private static final String[] PATH_SIZE_PROJECTION = new String[] { 54 MtpObjects.ObjectColumns._ID, // 0 55 MtpObjects.ObjectColumns.DATA, // 1 56 MtpObjects.ObjectColumns.SIZE, // 2 57 }; 58 private static final String[] OBJECT_INFO_PROJECTION = new String[] { 59 MtpObjects.ObjectColumns._ID, // 0 60 MtpObjects.ObjectColumns.DATA, // 1 61 MtpObjects.ObjectColumns.FORMAT, // 2 62 MtpObjects.ObjectColumns.PARENT, // 3 63 MtpObjects.ObjectColumns.SIZE, // 4 64 MtpObjects.ObjectColumns.DATE_MODIFIED, // 5 65 }; 66 private static final String ID_WHERE = MtpObjects.ObjectColumns._ID + "=?"; 67 private static final String PATH_WHERE = MtpObjects.ObjectColumns.DATA + "=?"; 68 private static final String PARENT_WHERE = MtpObjects.ObjectColumns.PARENT + "=?"; 69 private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " 70 + MtpObjects.ObjectColumns.FORMAT + "=?"; 71 72 private final MediaScanner mMediaScanner; 73 74 static { 75 System.loadLibrary("media_jni"); 76 } 77 78 public MtpDatabase(Context context, String volumeName) { 79 native_setup(); 80 81 mContext = context; 82 mMediaProvider = context.getContentResolver().acquireProvider("media"); 83 mVolumeName = volumeName; 84 mObjectsUri = MtpObjects.getContentUri(volumeName); 85 mMediaScanner = new MediaScanner(context); 86 } 87 88 @Override 89 protected void finalize() throws Throwable { 90 try { 91 native_finalize(); 92 } finally { 93 super.finalize(); 94 } 95 } 96 97 private int beginSendObject(String path, int format, int parent, 98 int storage, long size, long modified) { 99 mDatabaseModified = true; 100 ContentValues values = new ContentValues(); 101 values.put(MtpObjects.ObjectColumns.DATA, path); 102 values.put(MtpObjects.ObjectColumns.FORMAT, format); 103 values.put(MtpObjects.ObjectColumns.PARENT, parent); 104 // storage is ignored for now 105 values.put(MtpObjects.ObjectColumns.SIZE, size); 106 values.put(MtpObjects.ObjectColumns.DATE_MODIFIED, modified); 107 108 try { 109 Uri uri = mMediaProvider.insert(mObjectsUri, values); 110 if (uri != null) { 111 return Integer.parseInt(uri.getPathSegments().get(2)); 112 } else { 113 return -1; 114 } 115 } catch (RemoteException e) { 116 Log.e(TAG, "RemoteException in beginSendObject", e); 117 return -1; 118 } 119 } 120 121 private void endSendObject(String path, int handle, int format, boolean succeeded) { 122 if (succeeded) { 123 // handle abstract playlists separately 124 // they do not exist in the file system so don't use the media scanner here 125 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { 126 // Strip Windows Media Player file extension 127 if (path.endsWith(".pla")) { 128 path = path.substring(0, path.length() - 4); 129 } 130 131 // extract name from path 132 String name = path; 133 int lastSlash = name.lastIndexOf('/'); 134 if (lastSlash >= 0) { 135 name = name.substring(lastSlash + 1); 136 } 137 138 ContentValues values = new ContentValues(1); 139 values.put(Audio.Playlists.DATA, path); 140 values.put(Audio.Playlists.NAME, name); 141 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); 142 try { 143 Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values); 144 } catch (RemoteException e) { 145 Log.e(TAG, "RemoteException in endSendObject", e); 146 } 147 } else { 148 Uri uri = mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); 149 } 150 } else { 151 deleteFile(handle); 152 } 153 } 154 155 private int[] getObjectList(int storageID, int format, int parent) { 156 // we can ignore storageID until we support multiple storages 157 Log.d(TAG, "getObjectList parent: " + parent); 158 Cursor c = null; 159 try { 160 if (format != 0) { 161 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 162 PARENT_FORMAT_WHERE, 163 new String[] { Integer.toString(parent), Integer.toString(format) }, 164 null); 165 } else { 166 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 167 PARENT_WHERE, new String[] { Integer.toString(parent) }, null); 168 } 169 if (c == null) { 170 Log.d(TAG, "null cursor"); 171 return null; 172 } 173 int count = c.getCount(); 174 if (count > 0) { 175 int[] result = new int[count]; 176 for (int i = 0; i < count; i++) { 177 c.moveToNext(); 178 result[i] = c.getInt(0); 179 } 180 Log.d(TAG, "returning " + result); 181 return result; 182 } 183 } catch (RemoteException e) { 184 Log.e(TAG, "RemoteException in getObjectList", e); 185 } finally { 186 if (c != null) { 187 c.close(); 188 } 189 } 190 return null; 191 } 192 193 private int getNumObjects(int storageID, int format, int parent) { 194 // we can ignore storageID until we support multiple storages 195 Log.d(TAG, "getObjectList parent: " + parent); 196 Cursor c = null; 197 try { 198 if (format != 0) { 199 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 200 PARENT_FORMAT_WHERE, 201 new String[] { Integer.toString(parent), Integer.toString(format) }, 202 null); 203 } else { 204 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 205 PARENT_WHERE, new String[] { Integer.toString(parent) }, null); 206 } 207 if (c != null) { 208 return c.getCount(); 209 } 210 } catch (RemoteException e) { 211 Log.e(TAG, "RemoteException in getNumObjects", e); 212 } finally { 213 if (c != null) { 214 c.close(); 215 } 216 } 217 return -1; 218 } 219 220 private int[] getSupportedPlaybackFormats() { 221 return new int[] { 222 MtpConstants.FORMAT_ASSOCIATION, 223 MtpConstants.FORMAT_MP3, 224 MtpConstants.FORMAT_MPEG, 225 MtpConstants.FORMAT_EXIF_JPEG, 226 MtpConstants.FORMAT_TIFF_EP, 227 MtpConstants.FORMAT_GIF, 228 MtpConstants.FORMAT_JFIF, 229 MtpConstants.FORMAT_PNG, 230 MtpConstants.FORMAT_TIFF, 231 MtpConstants.FORMAT_WMA, 232 MtpConstants.FORMAT_OGG, 233 MtpConstants.FORMAT_AAC, 234 MtpConstants.FORMAT_MP4_CONTAINER, 235 MtpConstants.FORMAT_MP2, 236 MtpConstants.FORMAT_3GP_CONTAINER, 237 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, 238 MtpConstants.FORMAT_WPL_PLAYLIST, 239 MtpConstants.FORMAT_M3U_PLAYLIST, 240 MtpConstants.FORMAT_PLS_PLAYLIST, 241 }; 242 } 243 244 private int[] getSupportedCaptureFormats() { 245 // no capture formats yet 246 return null; 247 } 248 249 private int[] getSupportedObjectProperties(int handle) { 250 return new int[] { 251 MtpConstants.PROPERTY_STORAGE_ID, 252 MtpConstants.PROPERTY_OBJECT_FORMAT, 253 MtpConstants.PROPERTY_OBJECT_SIZE, 254 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 255 MtpConstants.PROPERTY_PARENT_OBJECT, 256 }; 257 } 258 259 private int[] getSupportedDeviceProperties() { 260 // no device properties yet 261 return null; 262 } 263 264 private int getObjectProperty(int handle, int property, 265 long[] outIntValue, char[] outStringValue) { 266 Log.d(TAG, "getObjectProperty: " + property); 267 String column = null; 268 boolean isString = false; 269 270 switch (property) { 271 case MtpConstants.PROPERTY_STORAGE_ID: 272 outIntValue[0] = mStorageID; 273 return MtpConstants.RESPONSE_OK; 274 case MtpConstants.PROPERTY_OBJECT_FORMAT: 275 column = MtpObjects.ObjectColumns.FORMAT; 276 break; 277 case MtpConstants.PROPERTY_PROTECTION_STATUS: 278 // protection status is always 0 279 outIntValue[0] = 0; 280 return MtpConstants.RESPONSE_OK; 281 case MtpConstants.PROPERTY_OBJECT_SIZE: 282 column = MtpObjects.ObjectColumns.SIZE; 283 break; 284 case MtpConstants.PROPERTY_OBJECT_FILE_NAME: 285 column = MtpObjects.ObjectColumns.DATA; 286 isString = true; 287 break; 288 case MtpConstants.PROPERTY_DATE_MODIFIED: 289 column = MtpObjects.ObjectColumns.DATE_MODIFIED; 290 break; 291 case MtpConstants.PROPERTY_PARENT_OBJECT: 292 column = MtpObjects.ObjectColumns.PARENT; 293 break; 294 case MtpConstants.PROPERTY_PERSISTENT_UID: 295 // PUID is concatenation of storageID and object handle 296 long puid = mStorageID; 297 puid <<= 32; 298 puid += handle; 299 outIntValue[0] = puid; 300 return MtpConstants.RESPONSE_OK; 301 default: 302 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; 303 } 304 305 Cursor c = null; 306 try { 307 // for now we are only reading properties from the "objects" table 308 c = mMediaProvider.query(mObjectsUri, 309 new String [] { MtpObjects.ObjectColumns._ID, column }, 310 ID_WHERE, new String[] { Integer.toString(handle) }, null); 311 if (c != null && c.moveToNext()) { 312 if (isString) { 313 String value = c.getString(1); 314 int start = 0; 315 316 if (property == MtpConstants.PROPERTY_OBJECT_FILE_NAME) { 317 // extract name from full path 318 int lastSlash = value.lastIndexOf('/'); 319 if (lastSlash >= 0) { 320 start = lastSlash + 1; 321 } 322 } 323 int end = value.length(); 324 if (end - start > 255) { 325 end = start + 255; 326 } 327 value.getChars(start, end, outStringValue, 0); 328 outStringValue[end - start] = 0; 329 } else { 330 outIntValue[0] = c.getLong(1); 331 } 332 return MtpConstants.RESPONSE_OK; 333 } 334 } catch (Exception e) { 335 return MtpConstants.RESPONSE_GENERAL_ERROR; 336 } finally { 337 if (c != null) { 338 c.close(); 339 } 340 } 341 // query failed if we get here 342 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 343 } 344 345 private boolean getObjectInfo(int handle, int[] outStorageFormatParent, 346 char[] outName, long[] outSizeModified) { 347 Log.d(TAG, "getObjectInfo: " + handle); 348 Cursor c = null; 349 try { 350 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, 351 ID_WHERE, new String[] { Integer.toString(handle) }, null); 352 if (c != null && c.moveToNext()) { 353 outStorageFormatParent[0] = mStorageID; 354 outStorageFormatParent[1] = c.getInt(2); 355 outStorageFormatParent[2] = c.getInt(3); 356 357 // extract name from path 358 String path = c.getString(1); 359 int lastSlash = path.lastIndexOf('/'); 360 int start = (lastSlash >= 0 ? lastSlash + 1 : 0); 361 int end = path.length(); 362 if (end - start > 255) { 363 end = start + 255; 364 } 365 path.getChars(start, end, outName, 0); 366 outName[end - start] = 0; 367 368 outSizeModified[0] = c.getLong(4); 369 outSizeModified[1] = c.getLong(5); 370 return true; 371 } 372 } catch (RemoteException e) { 373 Log.e(TAG, "RemoteException in getObjectProperty", e); 374 } finally { 375 if (c != null) { 376 c.close(); 377 } 378 } 379 return false; 380 } 381 382 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLength) { 383 Log.d(TAG, "getObjectFilePath: " + handle); 384 Cursor c = null; 385 try { 386 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_PROJECTION, 387 ID_WHERE, new String[] { Integer.toString(handle) }, null); 388 if (c != null && c.moveToNext()) { 389 String path = c.getString(1); 390 path.getChars(0, path.length(), outFilePath, 0); 391 outFilePath[path.length()] = 0; 392 outFileLength[0] = c.getLong(2); 393 return MtpConstants.RESPONSE_OK; 394 } else { 395 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 396 } 397 } catch (RemoteException e) { 398 Log.e(TAG, "RemoteException in getObjectFilePath", e); 399 return MtpConstants.RESPONSE_GENERAL_ERROR; 400 } finally { 401 if (c != null) { 402 c.close(); 403 } 404 } 405 } 406 407 private int deleteFile(int handle) { 408 Log.d(TAG, "deleteFile: " + handle); 409 mDatabaseModified = true; 410 Uri uri = MtpObjects.getContentUri(mVolumeName, handle); 411 try { 412 if (mMediaProvider.delete(uri, null, null) == 1) { 413 return MtpConstants.RESPONSE_OK; 414 } else { 415 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 416 } 417 } catch (RemoteException e) { 418 Log.e(TAG, "RemoteException in deleteFile", e); 419 return MtpConstants.RESPONSE_GENERAL_ERROR; 420 } 421 } 422 423 private int[] getObjectReferences(int handle) { 424 Log.d(TAG, "getObjectReferences for: " + handle); 425 Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle); 426 Cursor c = null; 427 try { 428 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null); 429 if (c == null) { 430 return null; 431 } 432 int count = c.getCount(); 433 if (count > 0) { 434 int[] result = new int[count]; 435 for (int i = 0; i < count; i++) { 436 c.moveToNext(); 437 result[i] = c.getInt(0); 438 } 439 return result; 440 } 441 } catch (RemoteException e) { 442 Log.e(TAG, "RemoteException in getObjectList", e); 443 } finally { 444 if (c != null) { 445 c.close(); 446 } 447 } 448 return null; 449 } 450 451 private int setObjectReferences(int handle, int[] references) { 452 mDatabaseModified = true; 453 Uri uri = MtpObjects.getReferencesUri(mVolumeName, handle); 454 int count = references.length; 455 ContentValues[] valuesList = new ContentValues[count]; 456 for (int i = 0; i < count; i++) { 457 ContentValues values = new ContentValues(); 458 values.put(MtpObjects.ObjectColumns._ID, references[i]); 459 valuesList[i] = values; 460 } 461 try { 462 if (count == mMediaProvider.bulkInsert(uri, valuesList)) { 463 return MtpConstants.RESPONSE_OK; 464 } 465 } catch (RemoteException e) { 466 Log.e(TAG, "RemoteException in setObjectReferences", e); 467 } 468 return MtpConstants.RESPONSE_GENERAL_ERROR; 469 } 470 471 private void sessionStarted() { 472 Log.d(TAG, "sessionStarted"); 473 mDatabaseModified = false; 474 } 475 476 private void sessionEnded() { 477 Log.d(TAG, "sessionEnded"); 478 if (mDatabaseModified) { 479 Log.d(TAG, "sending ACTION_MTP_SESSION_END"); 480 mContext.sendBroadcast(new Intent(Mtp.ACTION_MTP_SESSION_END)); 481 mDatabaseModified = false; 482 } 483 } 484 485 // used by the JNI code 486 private int mNativeContext; 487 488 private native final void native_setup(); 489 private native final void native_finalize(); 490} 491