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