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