MtpDatabase.java revision ac8dea12c17aa047e03a358110aeb60401d36aa2
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.mtp; 18 19import android.content.Context; 20import android.content.ContentValues; 21import android.content.IContentProvider; 22import android.content.Intent; 23import android.content.SharedPreferences; 24import android.database.Cursor; 25import android.database.sqlite.SQLiteDatabase; 26import android.media.MediaScanner; 27import android.net.Uri; 28import android.os.Environment; 29import android.os.RemoteException; 30import android.provider.MediaStore; 31import android.provider.MediaStore.Audio; 32import android.provider.MediaStore.Files; 33import android.provider.MediaStore.Images; 34import android.provider.MediaStore.MediaColumns; 35import android.util.Log; 36import android.view.Display; 37import android.view.WindowManager; 38 39import java.io.File; 40import java.util.HashMap; 41 42/** 43 * {@hide} 44 */ 45public class MtpDatabase { 46 47 private static final String TAG = "MtpDatabase"; 48 49 private final Context mContext; 50 private final IContentProvider mMediaProvider; 51 private final String mVolumeName; 52 private final Uri mObjectsUri; 53 private final String mMediaStoragePath; // path to primary storage 54 private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>(); 55 56 // cached property groups for single properties 57 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty 58 = new HashMap<Integer, MtpPropertyGroup>(); 59 60 // cached property groups for all properties for a given format 61 private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat 62 = new HashMap<Integer, MtpPropertyGroup>(); 63 64 // true if the database has been modified in the current MTP session 65 private boolean mDatabaseModified; 66 67 // SharedPreferences for writable MTP device properties 68 private SharedPreferences mDeviceProperties; 69 private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1; 70 71 private static final String[] ID_PROJECTION = new String[] { 72 Files.FileColumns._ID, // 0 73 }; 74 private static final String[] PATH_PROJECTION = new String[] { 75 Files.FileColumns._ID, // 0 76 Files.FileColumns.DATA, // 1 77 }; 78 private static final String[] PATH_SIZE_FORMAT_PROJECTION = new String[] { 79 Files.FileColumns._ID, // 0 80 Files.FileColumns.DATA, // 1 81 Files.FileColumns.SIZE, // 2 82 Files.FileColumns.FORMAT, // 3 83 }; 84 private static final String[] OBJECT_INFO_PROJECTION = new String[] { 85 Files.FileColumns._ID, // 0 86 Files.FileColumns.STORAGE_ID, // 1 87 Files.FileColumns.FORMAT, // 2 88 Files.FileColumns.PARENT, // 3 89 Files.FileColumns.DATA, // 4 90 Files.FileColumns.SIZE, // 5 91 Files.FileColumns.DATE_MODIFIED, // 6 92 }; 93 private static final String ID_WHERE = Files.FileColumns._ID + "=?"; 94 private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; 95 private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; 96 private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " 97 + Files.FileColumns.FORMAT + "=?"; 98 private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND " 99 + Files.FileColumns.STORAGE_ID + "=?"; 100 private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND " 101 + Files.FileColumns.FORMAT + "=?"; 102 103 private final MediaScanner mMediaScanner; 104 105 static { 106 System.loadLibrary("media_jni"); 107 } 108 109 public MtpDatabase(Context context, String volumeName, String storagePath) { 110 native_setup(); 111 112 mContext = context; 113 mMediaProvider = context.getContentResolver().acquireProvider("media"); 114 mVolumeName = volumeName; 115 mMediaStoragePath = storagePath; 116 mObjectsUri = Files.getMtpObjectsUri(volumeName); 117 mMediaScanner = new MediaScanner(context); 118 initDeviceProperties(context); 119 } 120 121 @Override 122 protected void finalize() throws Throwable { 123 try { 124 native_finalize(); 125 } finally { 126 super.finalize(); 127 } 128 } 129 130 public void addStorage(MtpStorage storage) { 131 mStorageMap.put(storage.getPath(), storage); 132 } 133 134 public void removeStorage(MtpStorage storage) { 135 mStorageMap.remove(storage.getPath()); 136 } 137 138 private void initDeviceProperties(Context context) { 139 final String devicePropertiesName = "device-properties"; 140 mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE); 141 File databaseFile = context.getDatabasePath(devicePropertiesName); 142 143 if (databaseFile.exists()) { 144 // for backward compatibility - read device properties from sqlite database 145 // and migrate them to shared prefs 146 SQLiteDatabase db = null; 147 Cursor c = null; 148 try { 149 db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null); 150 if (db != null) { 151 c = db.query("properties", new String[] { "_id", "code", "value" }, 152 null, null, null, null, null); 153 if (c != null) { 154 SharedPreferences.Editor e = mDeviceProperties.edit(); 155 while (c.moveToNext()) { 156 String name = c.getString(1); 157 String value = c.getString(2); 158 e.putString(name, value); 159 } 160 e.commit(); 161 } 162 } 163 } catch (Exception e) { 164 Log.e(TAG, "failed to migrate device properties", e); 165 } finally { 166 if (c != null) c.close(); 167 if (db != null) db.close(); 168 } 169 databaseFile.delete(); 170 } 171 } 172 173 private int beginSendObject(String path, int format, int parent, 174 int storageId, long size, long modified) { 175 // first make sure the object does not exist 176 if (path != null) { 177 Cursor c = null; 178 try { 179 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE, 180 new String[] { path }, null); 181 if (c != null && c.getCount() > 0) { 182 Log.w(TAG, "file already exists in beginSendObject: " + path); 183 return -1; 184 } 185 } catch (RemoteException e) { 186 Log.e(TAG, "RemoteException in beginSendObject", e); 187 } finally { 188 if (c != null) { 189 c.close(); 190 } 191 } 192 } 193 194 mDatabaseModified = true; 195 ContentValues values = new ContentValues(); 196 values.put(Files.FileColumns.DATA, path); 197 values.put(Files.FileColumns.FORMAT, format); 198 values.put(Files.FileColumns.PARENT, parent); 199 values.put(Files.FileColumns.STORAGE_ID, storageId); 200 values.put(Files.FileColumns.SIZE, size); 201 values.put(Files.FileColumns.DATE_MODIFIED, modified); 202 203 try { 204 Uri uri = mMediaProvider.insert(mObjectsUri, values); 205 if (uri != null) { 206 return Integer.parseInt(uri.getPathSegments().get(2)); 207 } else { 208 return -1; 209 } 210 } catch (RemoteException e) { 211 Log.e(TAG, "RemoteException in beginSendObject", e); 212 return -1; 213 } 214 } 215 216 private void endSendObject(String path, int handle, int format, boolean succeeded) { 217 if (succeeded) { 218 // handle abstract playlists separately 219 // they do not exist in the file system so don't use the media scanner here 220 if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { 221 // extract name from path 222 String name = path; 223 int lastSlash = name.lastIndexOf('/'); 224 if (lastSlash >= 0) { 225 name = name.substring(lastSlash + 1); 226 } 227 // strip trailing ".pla" from the name 228 if (name.endsWith(".pla")) { 229 name = name.substring(0, name.length() - 4); 230 } 231 232 ContentValues values = new ContentValues(1); 233 values.put(Audio.Playlists.DATA, path); 234 values.put(Audio.Playlists.NAME, name); 235 values.put(Files.FileColumns.FORMAT, format); 236 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); 237 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); 238 try { 239 Uri uri = mMediaProvider.insert(Audio.Playlists.EXTERNAL_CONTENT_URI, values); 240 } catch (RemoteException e) { 241 Log.e(TAG, "RemoteException in endSendObject", e); 242 } 243 } else { 244 mMediaScanner.scanMtpFile(path, mVolumeName, handle, format); 245 } 246 } else { 247 deleteFile(handle); 248 } 249 } 250 251 private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException { 252 if (storageID != 0) { 253 if (format != 0) { 254 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, 255 PARENT_STORAGE_FORMAT_WHERE, 256 new String[] { Integer.toString(parent), Integer.toString(storageID), 257 Integer.toString(format) }, null); 258 } else { 259 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, 260 PARENT_STORAGE_WHERE, new String[] 261 { Integer.toString(parent), Integer.toString(storageID) }, null); 262 } 263 } else { 264 if (format != 0) { 265 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, 266 PARENT_FORMAT_WHERE, 267 new String[] { Integer.toString(parent), Integer.toString(format) }, 268 null); 269 } else { 270 return mMediaProvider.query(mObjectsUri, ID_PROJECTION, 271 PARENT_WHERE, new String[] { Integer.toString(parent) }, null); 272 } 273 } 274 } 275 276 private int[] getObjectList(int storageID, int format, int parent) { 277 Cursor c = null; 278 try { 279 c = createObjectQuery(storageID, format, parent); 280 if (c == null) { 281 return null; 282 } 283 int count = c.getCount(); 284 if (count > 0) { 285 int[] result = new int[count]; 286 for (int i = 0; i < count; i++) { 287 c.moveToNext(); 288 result[i] = c.getInt(0); 289 } 290 return result; 291 } 292 } catch (RemoteException e) { 293 Log.e(TAG, "RemoteException in getObjectList", e); 294 } finally { 295 if (c != null) { 296 c.close(); 297 } 298 } 299 return null; 300 } 301 302 private int getNumObjects(int storageID, int format, int parent) { 303 Cursor c = null; 304 try { 305 c = createObjectQuery(storageID, format, parent); 306 if (c != null) { 307 return c.getCount(); 308 } 309 } catch (RemoteException e) { 310 Log.e(TAG, "RemoteException in getNumObjects", e); 311 } finally { 312 if (c != null) { 313 c.close(); 314 } 315 } 316 return -1; 317 } 318 319 private int[] getSupportedPlaybackFormats() { 320 return new int[] { 321 // allow transfering arbitrary files 322 MtpConstants.FORMAT_UNDEFINED, 323 324 MtpConstants.FORMAT_ASSOCIATION, 325 MtpConstants.FORMAT_TEXT, 326 MtpConstants.FORMAT_HTML, 327 MtpConstants.FORMAT_WAV, 328 MtpConstants.FORMAT_MP3, 329 MtpConstants.FORMAT_MPEG, 330 MtpConstants.FORMAT_EXIF_JPEG, 331 MtpConstants.FORMAT_TIFF_EP, 332 MtpConstants.FORMAT_GIF, 333 MtpConstants.FORMAT_JFIF, 334 MtpConstants.FORMAT_PNG, 335 MtpConstants.FORMAT_TIFF, 336 MtpConstants.FORMAT_WMA, 337 MtpConstants.FORMAT_OGG, 338 MtpConstants.FORMAT_AAC, 339 MtpConstants.FORMAT_MP4_CONTAINER, 340 MtpConstants.FORMAT_MP2, 341 MtpConstants.FORMAT_3GP_CONTAINER, 342 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, 343 MtpConstants.FORMAT_WPL_PLAYLIST, 344 MtpConstants.FORMAT_M3U_PLAYLIST, 345 MtpConstants.FORMAT_PLS_PLAYLIST, 346 MtpConstants.FORMAT_XML_DOCUMENT, 347 MtpConstants.FORMAT_FLAC, 348 }; 349 } 350 351 private int[] getSupportedCaptureFormats() { 352 // no capture formats yet 353 return null; 354 } 355 356 static final int[] FILE_PROPERTIES = { 357 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES 358 // and IMAGE_PROPERTIES below 359 MtpConstants.PROPERTY_STORAGE_ID, 360 MtpConstants.PROPERTY_OBJECT_FORMAT, 361 MtpConstants.PROPERTY_PROTECTION_STATUS, 362 MtpConstants.PROPERTY_OBJECT_SIZE, 363 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 364 MtpConstants.PROPERTY_DATE_MODIFIED, 365 MtpConstants.PROPERTY_PARENT_OBJECT, 366 MtpConstants.PROPERTY_PERSISTENT_UID, 367 MtpConstants.PROPERTY_NAME, 368 MtpConstants.PROPERTY_DATE_ADDED, 369 }; 370 371 static final int[] AUDIO_PROPERTIES = { 372 // NOTE must match FILE_PROPERTIES above 373 MtpConstants.PROPERTY_STORAGE_ID, 374 MtpConstants.PROPERTY_OBJECT_FORMAT, 375 MtpConstants.PROPERTY_PROTECTION_STATUS, 376 MtpConstants.PROPERTY_OBJECT_SIZE, 377 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 378 MtpConstants.PROPERTY_DATE_MODIFIED, 379 MtpConstants.PROPERTY_PARENT_OBJECT, 380 MtpConstants.PROPERTY_PERSISTENT_UID, 381 MtpConstants.PROPERTY_NAME, 382 MtpConstants.PROPERTY_DISPLAY_NAME, 383 MtpConstants.PROPERTY_DATE_ADDED, 384 385 // audio specific properties 386 MtpConstants.PROPERTY_ARTIST, 387 MtpConstants.PROPERTY_ALBUM_NAME, 388 MtpConstants.PROPERTY_ALBUM_ARTIST, 389 MtpConstants.PROPERTY_TRACK, 390 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, 391 MtpConstants.PROPERTY_DURATION, 392 MtpConstants.PROPERTY_GENRE, 393 MtpConstants.PROPERTY_COMPOSER, 394 }; 395 396 static final int[] VIDEO_PROPERTIES = { 397 // NOTE must match FILE_PROPERTIES above 398 MtpConstants.PROPERTY_STORAGE_ID, 399 MtpConstants.PROPERTY_OBJECT_FORMAT, 400 MtpConstants.PROPERTY_PROTECTION_STATUS, 401 MtpConstants.PROPERTY_OBJECT_SIZE, 402 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 403 MtpConstants.PROPERTY_DATE_MODIFIED, 404 MtpConstants.PROPERTY_PARENT_OBJECT, 405 MtpConstants.PROPERTY_PERSISTENT_UID, 406 MtpConstants.PROPERTY_NAME, 407 MtpConstants.PROPERTY_DISPLAY_NAME, 408 MtpConstants.PROPERTY_DATE_ADDED, 409 410 // video specific properties 411 MtpConstants.PROPERTY_ARTIST, 412 MtpConstants.PROPERTY_ALBUM_NAME, 413 MtpConstants.PROPERTY_DURATION, 414 MtpConstants.PROPERTY_DESCRIPTION, 415 }; 416 417 static final int[] IMAGE_PROPERTIES = { 418 // NOTE must match FILE_PROPERTIES above 419 MtpConstants.PROPERTY_STORAGE_ID, 420 MtpConstants.PROPERTY_OBJECT_FORMAT, 421 MtpConstants.PROPERTY_PROTECTION_STATUS, 422 MtpConstants.PROPERTY_OBJECT_SIZE, 423 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 424 MtpConstants.PROPERTY_DATE_MODIFIED, 425 MtpConstants.PROPERTY_PARENT_OBJECT, 426 MtpConstants.PROPERTY_PERSISTENT_UID, 427 MtpConstants.PROPERTY_NAME, 428 MtpConstants.PROPERTY_DISPLAY_NAME, 429 MtpConstants.PROPERTY_DATE_ADDED, 430 431 // image specific properties 432 MtpConstants.PROPERTY_DESCRIPTION, 433 }; 434 435 static final int[] ALL_PROPERTIES = { 436 // NOTE must match FILE_PROPERTIES above 437 MtpConstants.PROPERTY_STORAGE_ID, 438 MtpConstants.PROPERTY_OBJECT_FORMAT, 439 MtpConstants.PROPERTY_PROTECTION_STATUS, 440 MtpConstants.PROPERTY_OBJECT_SIZE, 441 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 442 MtpConstants.PROPERTY_DATE_MODIFIED, 443 MtpConstants.PROPERTY_PARENT_OBJECT, 444 MtpConstants.PROPERTY_PERSISTENT_UID, 445 MtpConstants.PROPERTY_NAME, 446 MtpConstants.PROPERTY_DISPLAY_NAME, 447 MtpConstants.PROPERTY_DATE_ADDED, 448 449 // image specific properties 450 MtpConstants.PROPERTY_DESCRIPTION, 451 452 // audio specific properties 453 MtpConstants.PROPERTY_ARTIST, 454 MtpConstants.PROPERTY_ALBUM_NAME, 455 MtpConstants.PROPERTY_ALBUM_ARTIST, 456 MtpConstants.PROPERTY_TRACK, 457 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, 458 MtpConstants.PROPERTY_DURATION, 459 MtpConstants.PROPERTY_GENRE, 460 MtpConstants.PROPERTY_COMPOSER, 461 462 // video specific properties 463 MtpConstants.PROPERTY_ARTIST, 464 MtpConstants.PROPERTY_ALBUM_NAME, 465 MtpConstants.PROPERTY_DURATION, 466 MtpConstants.PROPERTY_DESCRIPTION, 467 468 // image specific properties 469 MtpConstants.PROPERTY_DESCRIPTION, 470 }; 471 472 private int[] getSupportedObjectProperties(int format) { 473 switch (format) { 474 case MtpConstants.FORMAT_MP3: 475 case MtpConstants.FORMAT_WAV: 476 case MtpConstants.FORMAT_WMA: 477 case MtpConstants.FORMAT_OGG: 478 case MtpConstants.FORMAT_AAC: 479 return AUDIO_PROPERTIES; 480 case MtpConstants.FORMAT_MPEG: 481 case MtpConstants.FORMAT_3GP_CONTAINER: 482 case MtpConstants.FORMAT_WMV: 483 return VIDEO_PROPERTIES; 484 case MtpConstants.FORMAT_EXIF_JPEG: 485 case MtpConstants.FORMAT_GIF: 486 case MtpConstants.FORMAT_PNG: 487 case MtpConstants.FORMAT_BMP: 488 return IMAGE_PROPERTIES; 489 case 0: 490 return ALL_PROPERTIES; 491 default: 492 return FILE_PROPERTIES; 493 } 494 } 495 496 private int[] getSupportedDeviceProperties() { 497 return new int[] { 498 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, 499 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, 500 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE, 501 }; 502 } 503 504 505 private MtpPropertyList getObjectPropertyList(long handle, int format, long property, 506 int groupCode, int depth) { 507 // FIXME - implement group support 508 if (groupCode != 0) { 509 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); 510 } 511 512 MtpPropertyGroup propertyGroup; 513 if (property == 0xFFFFFFFFL) { 514 propertyGroup = mPropertyGroupsByFormat.get(format); 515 if (propertyGroup == null) { 516 int[] propertyList = getSupportedObjectProperties(format); 517 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); 518 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup); 519 } 520 } else { 521 propertyGroup = mPropertyGroupsByProperty.get(property); 522 if (propertyGroup == null) { 523 int[] propertyList = new int[] { (int)property }; 524 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); 525 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup); 526 } 527 } 528 529 return propertyGroup.getPropertyList((int)handle, format, depth); 530 } 531 532 private int renameFile(int handle, String newName) { 533 Cursor c = null; 534 535 // first compute current path 536 String path = null; 537 String[] whereArgs = new String[] { Integer.toString(handle) }; 538 try { 539 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); 540 if (c != null && c.moveToNext()) { 541 path = c.getString(1); 542 } 543 } catch (RemoteException e) { 544 Log.e(TAG, "RemoteException in getObjectFilePath", e); 545 return MtpConstants.RESPONSE_GENERAL_ERROR; 546 } finally { 547 if (c != null) { 548 c.close(); 549 } 550 } 551 if (path == null) { 552 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 553 } 554 555 // now rename the file. make sure this succeeds before updating database 556 File oldFile = new File(path); 557 int lastSlash = path.lastIndexOf('/'); 558 if (lastSlash <= 1) { 559 return MtpConstants.RESPONSE_GENERAL_ERROR; 560 } 561 String newPath = path.substring(0, lastSlash + 1) + newName; 562 File newFile = new File(newPath); 563 boolean success = oldFile.renameTo(newFile); 564 if (!success) { 565 Log.w(TAG, "renaming "+ path + " to " + newPath + " failed"); 566 return MtpConstants.RESPONSE_GENERAL_ERROR; 567 } 568 569 // finally update database 570 ContentValues values = new ContentValues(); 571 values.put(Files.FileColumns.DATA, newPath); 572 int updated = 0; 573 try { 574 // note - we are relying on a special case in MediaProvider.update() to update 575 // the paths for all children in the case where this is a directory. 576 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); 577 } catch (RemoteException e) { 578 Log.e(TAG, "RemoteException in mMediaProvider.update", e); 579 } 580 if (updated == 0) { 581 Log.e(TAG, "Unable to update path for " + path + " to " + newPath); 582 // this shouldn't happen, but if it does we need to rename the file to its original name 583 newFile.renameTo(oldFile); 584 return MtpConstants.RESPONSE_GENERAL_ERROR; 585 } 586 587 return MtpConstants.RESPONSE_OK; 588 } 589 590 private int setObjectProperty(int handle, int property, 591 long intValue, String stringValue) { 592 switch (property) { 593 case MtpConstants.PROPERTY_OBJECT_FILE_NAME: 594 return renameFile(handle, stringValue); 595 596 default: 597 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; 598 } 599 } 600 601 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) { 602 switch (property) { 603 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: 604 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: 605 // writable string properties kept in shared preferences 606 String value = mDeviceProperties.getString(Integer.toString(property), ""); 607 int length = value.length(); 608 if (length > 255) { 609 length = 255; 610 } 611 value.getChars(0, length, outStringValue, 0); 612 outStringValue[length] = 0; 613 return MtpConstants.RESPONSE_OK; 614 615 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE: 616 // use screen size as max image size 617 Display display = ((WindowManager)mContext.getSystemService( 618 Context.WINDOW_SERVICE)).getDefaultDisplay(); 619 int width = display.getMaximumSizeDimension(); 620 int height = display.getMaximumSizeDimension(); 621 String imageSize = Integer.toString(width) + "x" + Integer.toString(height); 622 imageSize.getChars(0, imageSize.length(), outStringValue, 0); 623 outStringValue[imageSize.length()] = 0; 624 return MtpConstants.RESPONSE_OK; 625 626 default: 627 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; 628 } 629 } 630 631 private int setDeviceProperty(int property, long intValue, String stringValue) { 632 switch (property) { 633 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: 634 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: 635 // writable string properties kept in shared prefs 636 SharedPreferences.Editor e = mDeviceProperties.edit(); 637 e.putString(Integer.toString(property), stringValue); 638 return (e.commit() ? MtpConstants.RESPONSE_OK 639 : MtpConstants.RESPONSE_GENERAL_ERROR); 640 } 641 642 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; 643 } 644 645 private boolean getObjectInfo(int handle, int[] outStorageFormatParent, 646 char[] outName, long[] outSizeModified) { 647 Cursor c = null; 648 try { 649 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, 650 ID_WHERE, new String[] { Integer.toString(handle) }, null); 651 if (c != null && c.moveToNext()) { 652 outStorageFormatParent[0] = c.getInt(1); 653 outStorageFormatParent[1] = c.getInt(2); 654 outStorageFormatParent[2] = c.getInt(3); 655 656 // extract name from path 657 String path = c.getString(4); 658 int lastSlash = path.lastIndexOf('/'); 659 int start = (lastSlash >= 0 ? lastSlash + 1 : 0); 660 int end = path.length(); 661 if (end - start > 255) { 662 end = start + 255; 663 } 664 path.getChars(start, end, outName, 0); 665 outName[end - start] = 0; 666 667 outSizeModified[0] = c.getLong(5); 668 outSizeModified[1] = c.getLong(6); 669 return true; 670 } 671 } catch (RemoteException e) { 672 Log.e(TAG, "RemoteException in getObjectInfo", e); 673 } finally { 674 if (c != null) { 675 c.close(); 676 } 677 } 678 return false; 679 } 680 681 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) { 682 if (handle == 0) { 683 // special case root directory 684 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0); 685 outFilePath[mMediaStoragePath.length()] = 0; 686 outFileLengthFormat[0] = 0; 687 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION; 688 return MtpConstants.RESPONSE_OK; 689 } 690 Cursor c = null; 691 try { 692 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, 693 ID_WHERE, new String[] { Integer.toString(handle) }, null); 694 if (c != null && c.moveToNext()) { 695 String path = c.getString(1); 696 path.getChars(0, path.length(), outFilePath, 0); 697 outFilePath[path.length()] = 0; 698 outFileLengthFormat[0] = c.getLong(2); 699 outFileLengthFormat[1] = c.getLong(3); 700 return MtpConstants.RESPONSE_OK; 701 } else { 702 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 703 } 704 } catch (RemoteException e) { 705 Log.e(TAG, "RemoteException in getObjectFilePath", e); 706 return MtpConstants.RESPONSE_GENERAL_ERROR; 707 } finally { 708 if (c != null) { 709 c.close(); 710 } 711 } 712 } 713 714 private int deleteFile(int handle) { 715 mDatabaseModified = true; 716 String path = null; 717 int format = 0; 718 719 Cursor c = null; 720 try { 721 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, 722 ID_WHERE, new String[] { Integer.toString(handle) }, null); 723 if (c != null && c.moveToNext()) { 724 // don't convert to media path here, since we will be matching 725 // against paths in the database matching /data/media 726 path = c.getString(1); 727 format = c.getInt(3); 728 } else { 729 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 730 } 731 732 if (path == null || format == 0) { 733 return MtpConstants.RESPONSE_GENERAL_ERROR; 734 } 735 736 if (format == MtpConstants.FORMAT_ASSOCIATION) { 737 // recursive case - delete all children first 738 Uri uri = Files.getMtpObjectsUri(mVolumeName); 739 int count = mMediaProvider.delete(uri, "_data LIKE ?", 740 new String[] { path + "/%"}); 741 } 742 743 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); 744 if (mMediaProvider.delete(uri, null, null) > 0) { 745 return MtpConstants.RESPONSE_OK; 746 } else { 747 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 748 } 749 } catch (RemoteException e) { 750 Log.e(TAG, "RemoteException in deleteFile", e); 751 return MtpConstants.RESPONSE_GENERAL_ERROR; 752 } finally { 753 if (c != null) { 754 c.close(); 755 } 756 } 757 } 758 759 private int[] getObjectReferences(int handle) { 760 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); 761 Cursor c = null; 762 try { 763 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null); 764 if (c == null) { 765 return null; 766 } 767 int count = c.getCount(); 768 if (count > 0) { 769 int[] result = new int[count]; 770 for (int i = 0; i < count; i++) { 771 c.moveToNext(); 772 result[i] = c.getInt(0); 773 } 774 return result; 775 } 776 } catch (RemoteException e) { 777 Log.e(TAG, "RemoteException in getObjectList", e); 778 } finally { 779 if (c != null) { 780 c.close(); 781 } 782 } 783 return null; 784 } 785 786 private int setObjectReferences(int handle, int[] references) { 787 mDatabaseModified = true; 788 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); 789 int count = references.length; 790 ContentValues[] valuesList = new ContentValues[count]; 791 for (int i = 0; i < count; i++) { 792 ContentValues values = new ContentValues(); 793 values.put(Files.FileColumns._ID, references[i]); 794 valuesList[i] = values; 795 } 796 try { 797 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) { 798 return MtpConstants.RESPONSE_OK; 799 } 800 } catch (RemoteException e) { 801 Log.e(TAG, "RemoteException in setObjectReferences", e); 802 } 803 return MtpConstants.RESPONSE_GENERAL_ERROR; 804 } 805 806 private void sessionStarted() { 807 mDatabaseModified = false; 808 } 809 810 private void sessionEnded() { 811 if (mDatabaseModified) { 812 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END)); 813 mDatabaseModified = false; 814 } 815 } 816 817 // used by the JNI code 818 private int mNativeContext; 819 820 private native final void native_setup(); 821 private native final void native_finalize(); 822} 823