MtpDatabase.java revision 8ed67acf38ccdfdb89553ed58620a135f2100e10
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 Log.d(TAG, "getObjectList parent: " + parent); 226 Cursor c = null; 227 try { 228 if (format != 0) { 229 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 230 PARENT_FORMAT_WHERE, 231 new String[] { Integer.toString(parent), Integer.toString(format) }, 232 null); 233 } else { 234 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 235 PARENT_WHERE, new String[] { Integer.toString(parent) }, null); 236 } 237 if (c == null) { 238 Log.d(TAG, "null cursor"); 239 return null; 240 } 241 int count = c.getCount(); 242 if (count > 0) { 243 int[] result = new int[count]; 244 for (int i = 0; i < count; i++) { 245 c.moveToNext(); 246 result[i] = c.getInt(0); 247 } 248 Log.d(TAG, "returning " + result); 249 return result; 250 } 251 } catch (RemoteException e) { 252 Log.e(TAG, "RemoteException in getObjectList", e); 253 } finally { 254 if (c != null) { 255 c.close(); 256 } 257 } 258 return null; 259 } 260 261 private int getNumObjects(int storageID, int format, int parent) { 262 // we can ignore storageID until we support multiple storages 263 Log.d(TAG, "getObjectList parent: " + parent); 264 Cursor c = null; 265 try { 266 if (format != 0) { 267 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 268 PARENT_FORMAT_WHERE, 269 new String[] { Integer.toString(parent), Integer.toString(format) }, 270 null); 271 } else { 272 c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, 273 PARENT_WHERE, new String[] { Integer.toString(parent) }, null); 274 } 275 if (c != null) { 276 return c.getCount(); 277 } 278 } catch (RemoteException e) { 279 Log.e(TAG, "RemoteException in getNumObjects", e); 280 } finally { 281 if (c != null) { 282 c.close(); 283 } 284 } 285 return -1; 286 } 287 288 private int[] getSupportedPlaybackFormats() { 289 return new int[] { 290 // allow transfering arbitrary files 291 MtpConstants.FORMAT_UNDEFINED, 292 293 MtpConstants.FORMAT_ASSOCIATION, 294 MtpConstants.FORMAT_TEXT, 295 MtpConstants.FORMAT_HTML, 296 MtpConstants.FORMAT_WAV, 297 MtpConstants.FORMAT_MP3, 298 MtpConstants.FORMAT_MPEG, 299 MtpConstants.FORMAT_EXIF_JPEG, 300 MtpConstants.FORMAT_TIFF_EP, 301 MtpConstants.FORMAT_GIF, 302 MtpConstants.FORMAT_JFIF, 303 MtpConstants.FORMAT_PNG, 304 MtpConstants.FORMAT_TIFF, 305 MtpConstants.FORMAT_WMA, 306 MtpConstants.FORMAT_OGG, 307 MtpConstants.FORMAT_AAC, 308 MtpConstants.FORMAT_MP4_CONTAINER, 309 MtpConstants.FORMAT_MP2, 310 MtpConstants.FORMAT_3GP_CONTAINER, 311 MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, 312 MtpConstants.FORMAT_WPL_PLAYLIST, 313 MtpConstants.FORMAT_M3U_PLAYLIST, 314 MtpConstants.FORMAT_PLS_PLAYLIST, 315 MtpConstants.FORMAT_XML_DOCUMENT, 316 }; 317 } 318 319 private int[] getSupportedCaptureFormats() { 320 // no capture formats yet 321 return null; 322 } 323 324 static final int[] FILE_PROPERTIES = { 325 // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES 326 // and IMAGE_PROPERTIES below 327 MtpConstants.PROPERTY_STORAGE_ID, 328 MtpConstants.PROPERTY_OBJECT_FORMAT, 329 MtpConstants.PROPERTY_PROTECTION_STATUS, 330 MtpConstants.PROPERTY_OBJECT_SIZE, 331 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 332 MtpConstants.PROPERTY_DATE_MODIFIED, 333 MtpConstants.PROPERTY_PARENT_OBJECT, 334 MtpConstants.PROPERTY_PERSISTENT_UID, 335 MtpConstants.PROPERTY_NAME, 336 MtpConstants.PROPERTY_DATE_ADDED, 337 }; 338 339 static final int[] AUDIO_PROPERTIES = { 340 // NOTE must match FILE_PROPERTIES above 341 MtpConstants.PROPERTY_STORAGE_ID, 342 MtpConstants.PROPERTY_OBJECT_FORMAT, 343 MtpConstants.PROPERTY_PROTECTION_STATUS, 344 MtpConstants.PROPERTY_OBJECT_SIZE, 345 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 346 MtpConstants.PROPERTY_DATE_MODIFIED, 347 MtpConstants.PROPERTY_PARENT_OBJECT, 348 MtpConstants.PROPERTY_PERSISTENT_UID, 349 MtpConstants.PROPERTY_NAME, 350 MtpConstants.PROPERTY_DISPLAY_NAME, 351 MtpConstants.PROPERTY_DATE_ADDED, 352 353 // audio specific properties 354 MtpConstants.PROPERTY_ARTIST, 355 MtpConstants.PROPERTY_ALBUM_NAME, 356 MtpConstants.PROPERTY_ALBUM_ARTIST, 357 MtpConstants.PROPERTY_TRACK, 358 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, 359 MtpConstants.PROPERTY_DURATION, 360 MtpConstants.PROPERTY_GENRE, 361 MtpConstants.PROPERTY_COMPOSER, 362 }; 363 364 static final int[] VIDEO_PROPERTIES = { 365 // NOTE must match FILE_PROPERTIES above 366 MtpConstants.PROPERTY_STORAGE_ID, 367 MtpConstants.PROPERTY_OBJECT_FORMAT, 368 MtpConstants.PROPERTY_PROTECTION_STATUS, 369 MtpConstants.PROPERTY_OBJECT_SIZE, 370 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 371 MtpConstants.PROPERTY_DATE_MODIFIED, 372 MtpConstants.PROPERTY_PARENT_OBJECT, 373 MtpConstants.PROPERTY_PERSISTENT_UID, 374 MtpConstants.PROPERTY_NAME, 375 MtpConstants.PROPERTY_DISPLAY_NAME, 376 MtpConstants.PROPERTY_DATE_ADDED, 377 378 // video specific properties 379 MtpConstants.PROPERTY_ARTIST, 380 MtpConstants.PROPERTY_ALBUM_NAME, 381 MtpConstants.PROPERTY_DURATION, 382 MtpConstants.PROPERTY_DESCRIPTION, 383 }; 384 385 static final int[] IMAGE_PROPERTIES = { 386 // NOTE must match FILE_PROPERTIES above 387 MtpConstants.PROPERTY_STORAGE_ID, 388 MtpConstants.PROPERTY_OBJECT_FORMAT, 389 MtpConstants.PROPERTY_PROTECTION_STATUS, 390 MtpConstants.PROPERTY_OBJECT_SIZE, 391 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 392 MtpConstants.PROPERTY_DATE_MODIFIED, 393 MtpConstants.PROPERTY_PARENT_OBJECT, 394 MtpConstants.PROPERTY_PERSISTENT_UID, 395 MtpConstants.PROPERTY_NAME, 396 MtpConstants.PROPERTY_DISPLAY_NAME, 397 MtpConstants.PROPERTY_DATE_ADDED, 398 399 // image specific properties 400 MtpConstants.PROPERTY_DESCRIPTION, 401 }; 402 403 static final int[] ALL_PROPERTIES = { 404 // NOTE must match FILE_PROPERTIES above 405 MtpConstants.PROPERTY_STORAGE_ID, 406 MtpConstants.PROPERTY_OBJECT_FORMAT, 407 MtpConstants.PROPERTY_PROTECTION_STATUS, 408 MtpConstants.PROPERTY_OBJECT_SIZE, 409 MtpConstants.PROPERTY_OBJECT_FILE_NAME, 410 MtpConstants.PROPERTY_DATE_MODIFIED, 411 MtpConstants.PROPERTY_PARENT_OBJECT, 412 MtpConstants.PROPERTY_PERSISTENT_UID, 413 MtpConstants.PROPERTY_NAME, 414 MtpConstants.PROPERTY_DISPLAY_NAME, 415 MtpConstants.PROPERTY_DATE_ADDED, 416 417 // image specific properties 418 MtpConstants.PROPERTY_DESCRIPTION, 419 420 // audio specific properties 421 MtpConstants.PROPERTY_ARTIST, 422 MtpConstants.PROPERTY_ALBUM_NAME, 423 MtpConstants.PROPERTY_ALBUM_ARTIST, 424 MtpConstants.PROPERTY_TRACK, 425 MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, 426 MtpConstants.PROPERTY_DURATION, 427 MtpConstants.PROPERTY_GENRE, 428 MtpConstants.PROPERTY_COMPOSER, 429 430 // video specific properties 431 MtpConstants.PROPERTY_ARTIST, 432 MtpConstants.PROPERTY_ALBUM_NAME, 433 MtpConstants.PROPERTY_DURATION, 434 MtpConstants.PROPERTY_DESCRIPTION, 435 436 // image specific properties 437 MtpConstants.PROPERTY_DESCRIPTION, 438 }; 439 440 private int[] getSupportedObjectProperties(int format) { 441 switch (format) { 442 case MtpConstants.FORMAT_MP3: 443 case MtpConstants.FORMAT_WAV: 444 case MtpConstants.FORMAT_WMA: 445 case MtpConstants.FORMAT_OGG: 446 case MtpConstants.FORMAT_AAC: 447 return AUDIO_PROPERTIES; 448 case MtpConstants.FORMAT_MPEG: 449 case MtpConstants.FORMAT_3GP_CONTAINER: 450 case MtpConstants.FORMAT_WMV: 451 return VIDEO_PROPERTIES; 452 case MtpConstants.FORMAT_EXIF_JPEG: 453 case MtpConstants.FORMAT_GIF: 454 case MtpConstants.FORMAT_PNG: 455 case MtpConstants.FORMAT_BMP: 456 return IMAGE_PROPERTIES; 457 case 0: 458 return ALL_PROPERTIES; 459 default: 460 return FILE_PROPERTIES; 461 } 462 } 463 464 private int[] getSupportedDeviceProperties() { 465 return new int[] { 466 MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, 467 MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, 468 MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE, 469 }; 470 } 471 472 473 private MtpPropertyList getObjectPropertyList(long handle, int format, long property, 474 int groupCode, int depth) { 475 // FIXME - implement group support 476 if (groupCode != 0) { 477 return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); 478 } 479 480 MtpPropertyGroup propertyGroup; 481 if (property == 0xFFFFFFFFL) { 482 propertyGroup = mPropertyGroupsByFormat.get(format); 483 if (propertyGroup == null) { 484 int[] propertyList = getSupportedObjectProperties(format); 485 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); 486 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup); 487 } 488 } else { 489 propertyGroup = mPropertyGroupsByProperty.get(property); 490 if (propertyGroup == null) { 491 int[] propertyList = new int[] { (int)property }; 492 propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); 493 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup); 494 } 495 } 496 497 return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID); 498 } 499 500 private int renameFile(int handle, String newName) { 501 Cursor c = null; 502 503 // first compute current path 504 String path = null; 505 String[] whereArgs = new String[] { Integer.toString(handle) }; 506 try { 507 c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); 508 if (c != null && c.moveToNext()) { 509 path = c.getString(1); 510 } 511 } catch (RemoteException e) { 512 Log.e(TAG, "RemoteException in getObjectFilePath", e); 513 return MtpConstants.RESPONSE_GENERAL_ERROR; 514 } finally { 515 if (c != null) { 516 c.close(); 517 } 518 } 519 if (path == null) { 520 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 521 } 522 523 // now rename the file. make sure this succeeds before updating database 524 File oldFile = new File(path); 525 int lastSlash = path.lastIndexOf('/'); 526 if (lastSlash <= 1) { 527 return MtpConstants.RESPONSE_GENERAL_ERROR; 528 } 529 String newPath = path.substring(0, lastSlash + 1) + newName; 530 File newFile = new File(newPath); 531 boolean success = oldFile.renameTo(newFile); 532 Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); 533 if (!success) { 534 return MtpConstants.RESPONSE_GENERAL_ERROR; 535 } 536 537 // finally update database 538 ContentValues values = new ContentValues(); 539 values.put(Files.FileColumns.DATA, newPath); 540 int updated = 0; 541 try { 542 // note - we are relying on a special case in MediaProvider.update() to update 543 // the paths for all children in the case where this is a directory. 544 updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); 545 } catch (RemoteException e) { 546 Log.e(TAG, "RemoteException in mMediaProvider.update", e); 547 } 548 if (updated == 0) { 549 Log.e(TAG, "Unable to update path for " + path + " to " + newPath); 550 // this shouldn't happen, but if it does we need to rename the file to its original name 551 newFile.renameTo(oldFile); 552 return MtpConstants.RESPONSE_GENERAL_ERROR; 553 } 554 555 return MtpConstants.RESPONSE_OK; 556 } 557 558 private int setObjectProperty(int handle, int property, 559 long intValue, String stringValue) { 560 Log.d(TAG, "setObjectProperty: " + property); 561 562 switch (property) { 563 case MtpConstants.PROPERTY_OBJECT_FILE_NAME: 564 return renameFile(handle, stringValue); 565 566 default: 567 return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; 568 } 569 } 570 571 private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) { 572 Log.d(TAG, "getDeviceProperty: " + property); 573 574 switch (property) { 575 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: 576 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: 577 // writable string properties kept in our device property database 578 Cursor c = null; 579 try { 580 c = mDevicePropDb.query("properties", DEVICE_PROPERTY_PROJECTION, 581 DEVICE_PROPERTY_WHERE, new String[] { Integer.toString(property) }, 582 null, null, null); 583 584 if (c != null && c.moveToNext()) { 585 String value = c.getString(1); 586 int length = value.length(); 587 if (length > 255) { 588 length = 255; 589 } 590 value.getChars(0, length, outStringValue, 0); 591 outStringValue[length] = 0; 592 } else { 593 outStringValue[0] = 0; 594 } 595 return MtpConstants.RESPONSE_OK; 596 } finally { 597 if (c != null) { 598 c.close(); 599 } 600 } 601 602 case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE: 603 // use screen size as max image size 604 Display display = ((WindowManager)mContext.getSystemService( 605 Context.WINDOW_SERVICE)).getDefaultDisplay(); 606 int width = display.getWidth(); 607 int height = display.getHeight(); 608 String imageSize = Integer.toString(width) + "x" + Integer.toString(height); 609 imageSize.getChars(0, imageSize.length(), outStringValue, 0); 610 outStringValue[imageSize.length()] = 0; 611 return MtpConstants.RESPONSE_OK; 612 613 default: 614 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; 615 } 616 } 617 618 private int setDeviceProperty(int property, long intValue, String stringValue) { 619 Log.d(TAG, "setDeviceProperty: " + property + " : " + stringValue); 620 621 switch (property) { 622 case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: 623 case MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: 624 // writable string properties kept in our device property database 625 try { 626 ContentValues values = new ContentValues(); 627 values.put("code", property); 628 values.put("value", stringValue); 629 mDevicePropDb.insert("properties", "code", values); 630 return MtpConstants.RESPONSE_OK; 631 } catch (Exception e) { 632 return MtpConstants.RESPONSE_GENERAL_ERROR; 633 } 634 } 635 636 return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; 637 } 638 639 private boolean getObjectInfo(int handle, int[] outStorageFormatParent, 640 char[] outName, long[] outSizeModified) { 641 Log.d(TAG, "getObjectInfo: " + handle); 642 Cursor c = null; 643 try { 644 c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, 645 ID_WHERE, new String[] { Integer.toString(handle) }, null); 646 if (c != null && c.moveToNext()) { 647 outStorageFormatParent[0] = mStorageID; 648 outStorageFormatParent[1] = c.getInt(2); 649 outStorageFormatParent[2] = c.getInt(3); 650 651 // extract name from path 652 String path = c.getString(1); 653 int lastSlash = path.lastIndexOf('/'); 654 int start = (lastSlash >= 0 ? lastSlash + 1 : 0); 655 int end = path.length(); 656 if (end - start > 255) { 657 end = start + 255; 658 } 659 path.getChars(start, end, outName, 0); 660 outName[end - start] = 0; 661 662 outSizeModified[0] = c.getLong(4); 663 outSizeModified[1] = c.getLong(5); 664 return true; 665 } 666 } catch (RemoteException e) { 667 Log.e(TAG, "RemoteException in getObjectInfo", e); 668 } finally { 669 if (c != null) { 670 c.close(); 671 } 672 } 673 return false; 674 } 675 676 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) { 677 Log.d(TAG, "getObjectFilePath: " + handle); 678 if (handle == 0) { 679 // special case root directory 680 mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0); 681 outFilePath[mMediaStoragePath.length()] = 0; 682 outFileLengthFormat[0] = 0; 683 outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION; 684 return MtpConstants.RESPONSE_OK; 685 } 686 Cursor c = null; 687 try { 688 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, 689 ID_WHERE, new String[] { Integer.toString(handle) }, null); 690 if (c != null && c.moveToNext()) { 691 String path = c.getString(1); 692 path.getChars(0, path.length(), outFilePath, 0); 693 outFilePath[path.length()] = 0; 694 outFileLengthFormat[0] = c.getLong(2); 695 outFileLengthFormat[1] = c.getLong(3); 696 return MtpConstants.RESPONSE_OK; 697 } else { 698 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 699 } 700 } catch (RemoteException e) { 701 Log.e(TAG, "RemoteException in getObjectFilePath", e); 702 return MtpConstants.RESPONSE_GENERAL_ERROR; 703 } finally { 704 if (c != null) { 705 c.close(); 706 } 707 } 708 } 709 710 private int deleteFile(int handle) { 711 Log.d(TAG, "deleteFile: " + handle); 712 mDatabaseModified = true; 713 String path = null; 714 int format = 0; 715 716 Cursor c = null; 717 try { 718 c = mMediaProvider.query(mObjectsUri, PATH_SIZE_FORMAT_PROJECTION, 719 ID_WHERE, new String[] { Integer.toString(handle) }, null); 720 if (c != null && c.moveToNext()) { 721 // don't convert to media path here, since we will be matching 722 // against paths in the database matching /data/media 723 path = c.getString(1); 724 format = c.getInt(3); 725 } else { 726 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 727 } 728 729 if (path == null || format == 0) { 730 return MtpConstants.RESPONSE_GENERAL_ERROR; 731 } 732 733 if (format == MtpConstants.FORMAT_ASSOCIATION) { 734 // recursive case - delete all children first 735 Uri uri = Files.getMtpObjectsUri(mVolumeName); 736 int count = mMediaProvider.delete(uri, "_data LIKE ?", 737 new String[] { path + "/%"}); 738 } 739 740 Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); 741 if (mMediaProvider.delete(uri, null, null) > 0) { 742 return MtpConstants.RESPONSE_OK; 743 } else { 744 return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; 745 } 746 } catch (RemoteException e) { 747 Log.e(TAG, "RemoteException in deleteFile", e); 748 return MtpConstants.RESPONSE_GENERAL_ERROR; 749 } finally { 750 if (c != null) { 751 c.close(); 752 } 753 } 754 } 755 756 private int[] getObjectReferences(int handle) { 757 Log.d(TAG, "getObjectReferences for: " + handle); 758 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); 759 Cursor c = null; 760 try { 761 c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null); 762 if (c == null) { 763 return null; 764 } 765 int count = c.getCount(); 766 if (count > 0) { 767 int[] result = new int[count]; 768 for (int i = 0; i < count; i++) { 769 c.moveToNext(); 770 result[i] = c.getInt(0); 771 } 772 return result; 773 } 774 } catch (RemoteException e) { 775 Log.e(TAG, "RemoteException in getObjectList", e); 776 } finally { 777 if (c != null) { 778 c.close(); 779 } 780 } 781 return null; 782 } 783 784 private int setObjectReferences(int handle, int[] references) { 785 mDatabaseModified = true; 786 Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); 787 int count = references.length; 788 ContentValues[] valuesList = new ContentValues[count]; 789 for (int i = 0; i < count; i++) { 790 ContentValues values = new ContentValues(); 791 values.put(Files.FileColumns._ID, references[i]); 792 valuesList[i] = values; 793 } 794 try { 795 if (mMediaProvider.bulkInsert(uri, valuesList) > 0) { 796 return MtpConstants.RESPONSE_OK; 797 } 798 } catch (RemoteException e) { 799 Log.e(TAG, "RemoteException in setObjectReferences", e); 800 } 801 return MtpConstants.RESPONSE_GENERAL_ERROR; 802 } 803 804 private void sessionStarted() { 805 Log.d(TAG, "sessionStarted"); 806 mDatabaseModified = false; 807 } 808 809 private void sessionEnded() { 810 Log.d(TAG, "sessionEnded"); 811 if (mDatabaseModified) { 812 Log.d(TAG, "sending ACTION_MTP_SESSION_END"); 813 mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END)); 814 mDatabaseModified = false; 815 } 816 } 817 818 // used by the JNI code 819 private int mNativeContext; 820 821 private native final void native_setup(); 822 private native final void native_finalize(); 823} 824