1 /* 2 * Copyright (C) 2007 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 17 package android.provider; 18 19 import android.annotation.SdkConstant; 20 import android.annotation.SdkConstant.SdkConstantType; 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 23 import android.content.ContentUris; 24 import android.content.Context; 25 import android.database.Cursor; 26 import android.database.DatabaseUtils; 27 import android.database.sqlite.SQLiteException; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.graphics.Matrix; 31 import android.media.MiniThumbFile; 32 import android.media.ThumbnailUtils; 33 import android.net.Uri; 34 import android.os.Environment; 35 import android.os.ParcelFileDescriptor; 36 import android.util.Log; 37 38 import java.io.FileInputStream; 39 import java.io.FileNotFoundException; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.OutputStream; 43 44 /** 45 * The Media provider contains meta data for all available media on both internal 46 * and external storage devices. 47 */ 48 public final class MediaStore { 49 private final static String TAG = "MediaStore"; 50 51 public static final String AUTHORITY = "media"; 52 53 private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; 54 55 /** 56 * Broadcast Action: A broadcast to indicate the end of an MTP session with the host. 57 * This broadcast is only sent if MTP activity has modified the media database during the 58 * most recent MTP session. 59 * 60 * @hide 61 */ 62 public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END"; 63 64 /** 65 * Activity Action: Launch a music player. 66 * The activity should be able to play, browse, or manipulate music files stored on the device. 67 * 68 * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead. 69 */ 70 @Deprecated 71 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 72 public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER"; 73 74 /** 75 * Activity Action: Perform a search for media. 76 * Contains at least the {@link android.app.SearchManager#QUERY} extra. 77 * May also contain any combination of the following extras: 78 * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS 79 * 80 * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST 81 * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM 82 * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE 83 * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS 84 */ 85 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 86 public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH"; 87 88 /** 89 * An intent to perform a search for music media and automatically play content from the 90 * result when possible. This can be fired, for example, by the result of a voice recognition 91 * command to listen to music. 92 * <p> 93 * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string 94 * that can contain any type of unstructured music search, like the name of an artist, 95 * an album, a song, a genre, or any combination of these. 96 * <p> 97 * Because this intent includes an open-ended unstructured search string, it makes the most 98 * sense for apps that can support large-scale search of music, such as services connected 99 * to an online database of music which can be streamed and played on the device. 100 */ 101 public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = 102 "android.media.action.MEDIA_PLAY_FROM_SEARCH"; 103 104 /** 105 * The name of the Intent-extra used to define the artist 106 */ 107 public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist"; 108 /** 109 * The name of the Intent-extra used to define the album 110 */ 111 public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album"; 112 /** 113 * The name of the Intent-extra used to define the song title 114 */ 115 public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title"; 116 /** 117 * The name of the Intent-extra used to define the search focus. The search focus 118 * indicates whether the search should be for things related to the artist, album 119 * or song that is identified by the other extras. 120 */ 121 public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus"; 122 123 /** 124 * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView. 125 * This is an int property that overrides the activity's requestedOrientation. 126 * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 127 */ 128 public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation"; 129 130 /** 131 * The name of an Intent-extra used to control the UI of a ViewImage. 132 * This is a boolean property that overrides the activity's default fullscreen state. 133 */ 134 public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; 135 136 /** 137 * The name of an Intent-extra used to control the UI of a ViewImage. 138 * This is a boolean property that specifies whether or not to show action icons. 139 */ 140 public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons"; 141 142 /** 143 * The name of the Intent-extra used to control the onCompletion behavior of a MovieView. 144 * This is a boolean property that specifies whether or not to finish the MovieView activity 145 * when the movie completes playing. The default value is true, which means to automatically 146 * exit the movie player activity when the movie completes playing. 147 */ 148 public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion"; 149 150 /** 151 * The name of the Intent action used to launch a camera in still image mode. 152 */ 153 public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; 154 155 /** 156 * The name of the Intent action used to launch a camera in video mode. 157 */ 158 public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA"; 159 160 /** 161 * Standard Intent action that can be sent to have the camera application 162 * capture an image and return it. 163 * <p> 164 * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. 165 * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap 166 * object in the extra field. This is useful for applications that only need a small image. 167 * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri 168 * value of EXTRA_OUTPUT. 169 * @see #EXTRA_OUTPUT 170 */ 171 public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; 172 173 /** 174 * Standard Intent action that can be sent to have the camera application 175 * capture an video and return it. 176 * <p> 177 * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality. 178 * <p> 179 * The caller may pass in an extra EXTRA_OUTPUT to control 180 * where the video is written. If EXTRA_OUTPUT is not present the video will be 181 * written to the standard location for videos, and the Uri of that location will be 182 * returned in the data field of the Uri. 183 * @see #EXTRA_OUTPUT 184 * @see #EXTRA_VIDEO_QUALITY 185 * @see #EXTRA_SIZE_LIMIT 186 * @see #EXTRA_DURATION_LIMIT 187 */ 188 public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; 189 190 /** 191 * The name of the Intent-extra used to control the quality of a recorded video. This is an 192 * integer property. Currently value 0 means low quality, suitable for MMS messages, and 193 * value 1 means high quality. In the future other quality levels may be added. 194 */ 195 public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; 196 197 /** 198 * Specify the maximum allowed size. 199 */ 200 public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit"; 201 202 /** 203 * Specify the maximum allowed recording duration in seconds. 204 */ 205 public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit"; 206 207 /** 208 * The name of the Intent-extra used to indicate a content resolver Uri to be used to 209 * store the requested image or video. 210 */ 211 public final static String EXTRA_OUTPUT = "output"; 212 213 /** 214 * The string that is used when a media attribute is not known. For example, 215 * if an audio file does not have any meta data, the artist and album columns 216 * will be set to this value. 217 */ 218 public static final String UNKNOWN_STRING = "<unknown>"; 219 220 /** 221 * Common fields for most MediaProvider tables 222 */ 223 224 public interface MediaColumns extends BaseColumns { 225 /** 226 * The data stream for the file 227 * <P>Type: DATA STREAM</P> 228 */ 229 public static final String DATA = "_data"; 230 231 /** 232 * The size of the file in bytes 233 * <P>Type: INTEGER (long)</P> 234 */ 235 public static final String SIZE = "_size"; 236 237 /** 238 * The display name of the file 239 * <P>Type: TEXT</P> 240 */ 241 public static final String DISPLAY_NAME = "_display_name"; 242 243 /** 244 * The title of the content 245 * <P>Type: TEXT</P> 246 */ 247 public static final String TITLE = "title"; 248 249 /** 250 * The time the file was added to the media provider 251 * Units are seconds since 1970. 252 * <P>Type: INTEGER (long)</P> 253 */ 254 public static final String DATE_ADDED = "date_added"; 255 256 /** 257 * The time the file was last modified 258 * Units are seconds since 1970. 259 * NOTE: This is for internal use by the media scanner. Do not modify this field. 260 * <P>Type: INTEGER (long)</P> 261 */ 262 public static final String DATE_MODIFIED = "date_modified"; 263 264 /** 265 * The MIME type of the file 266 * <P>Type: TEXT</P> 267 */ 268 public static final String MIME_TYPE = "mime_type"; 269 270 /** 271 * The MTP object handle of a newly transfered file. 272 * Used to pass the new file's object handle through the media scanner 273 * from MTP to the media provider 274 * For internal use only by MTP, media scanner and media provider. 275 * <P>Type: INTEGER</P> 276 * @hide 277 */ 278 public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id"; 279 280 /** 281 * Non-zero if the media file is drm-protected 282 * <P>Type: INTEGER (boolean)</P> 283 * @hide 284 */ 285 public static final String IS_DRM = "is_drm"; 286 287 /** 288 * The width of the image/video in pixels. 289 * @hide 290 */ 291 public static final String WIDTH = "width"; 292 293 /** 294 * The height of the image/video in pixels. 295 * @hide 296 */ 297 public static final String HEIGHT = "height"; 298 } 299 300 /** 301 * Media provider table containing an index of all files in the media storage, 302 * including non-media files. This should be used by applications that work with 303 * non-media file types (text, HTML, PDF, etc) as well as applications that need to 304 * work with multiple media file types in a single query. 305 */ 306 public static final class Files { 307 308 /** 309 * Get the content:// style URI for the files table on the 310 * given volume. 311 * 312 * @param volumeName the name of the volume to get the URI for 313 * @return the URI to the files table on the given volume 314 */ 315 public static Uri getContentUri(String volumeName) { 316 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 317 "/file"); 318 } 319 320 /** 321 * Get the content:// style URI for a single row in the files table on the 322 * given volume. 323 * 324 * @param volumeName the name of the volume to get the URI for 325 * @param rowId the file to get the URI for 326 * @return the URI to the files table on the given volume 327 */ 328 public static final Uri getContentUri(String volumeName, 329 long rowId) { 330 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 331 + "/file/" + rowId); 332 } 333 334 /** 335 * For use only by the MTP implementation. 336 * @hide 337 */ 338 public static Uri getMtpObjectsUri(String volumeName) { 339 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 340 "/object"); 341 } 342 343 /** 344 * For use only by the MTP implementation. 345 * @hide 346 */ 347 public static final Uri getMtpObjectsUri(String volumeName, 348 long fileId) { 349 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 350 + "/object/" + fileId); 351 } 352 353 /** 354 * Used to implement the MTP GetObjectReferences and SetObjectReferences commands. 355 * @hide 356 */ 357 public static final Uri getMtpReferencesUri(String volumeName, 358 long fileId) { 359 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 360 + "/object/" + fileId + "/references"); 361 } 362 363 /** 364 * Fields for master table for all media files. 365 * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED. 366 */ 367 public interface FileColumns extends MediaColumns { 368 /** 369 * The MTP storage ID of the file 370 * <P>Type: INTEGER</P> 371 * @hide 372 */ 373 public static final String STORAGE_ID = "storage_id"; 374 375 /** 376 * The MTP format code of the file 377 * <P>Type: INTEGER</P> 378 * @hide 379 */ 380 public static final String FORMAT = "format"; 381 382 /** 383 * The index of the parent directory of the file 384 * <P>Type: INTEGER</P> 385 */ 386 public static final String PARENT = "parent"; 387 388 /** 389 * The MIME type of the file 390 * <P>Type: TEXT</P> 391 */ 392 public static final String MIME_TYPE = "mime_type"; 393 394 /** 395 * The title of the content 396 * <P>Type: TEXT</P> 397 */ 398 public static final String TITLE = "title"; 399 400 /** 401 * The media type (audio, video, image or playlist) 402 * of the file, or 0 for not a media file 403 * <P>Type: TEXT</P> 404 */ 405 public static final String MEDIA_TYPE = "media_type"; 406 407 /** 408 * Constant for the {@link #MEDIA_TYPE} column indicating that file 409 * is not an audio, image, video or playlist file. 410 */ 411 public static final int MEDIA_TYPE_NONE = 0; 412 413 /** 414 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an image file. 415 */ 416 public static final int MEDIA_TYPE_IMAGE = 1; 417 418 /** 419 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an audio file. 420 */ 421 public static final int MEDIA_TYPE_AUDIO = 2; 422 423 /** 424 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an video file. 425 */ 426 public static final int MEDIA_TYPE_VIDEO = 3; 427 428 /** 429 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an playlist file. 430 */ 431 public static final int MEDIA_TYPE_PLAYLIST = 4; 432 } 433 } 434 435 /** 436 * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended 437 * to be accessed elsewhere. 438 */ 439 private static class InternalThumbnails implements BaseColumns { 440 private static final int MINI_KIND = 1; 441 private static final int FULL_SCREEN_KIND = 2; 442 private static final int MICRO_KIND = 3; 443 private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA}; 444 static final int DEFAULT_GROUP_ID = 0; 445 private static final Object sThumbBufLock = new Object(); 446 private static byte[] sThumbBuf; 447 448 private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { 449 Bitmap bitmap = null; 450 Uri thumbUri = null; 451 try { 452 long thumbId = c.getLong(0); 453 String filePath = c.getString(1); 454 thumbUri = ContentUris.withAppendedId(baseUri, thumbId); 455 ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r"); 456 bitmap = BitmapFactory.decodeFileDescriptor( 457 pfdInput.getFileDescriptor(), null, options); 458 pfdInput.close(); 459 } catch (FileNotFoundException ex) { 460 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); 461 } catch (IOException ex) { 462 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); 463 } catch (OutOfMemoryError ex) { 464 Log.e(TAG, "failed to allocate memory for thumbnail " 465 + thumbUri + "; " + ex); 466 } 467 return bitmap; 468 } 469 470 /** 471 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 472 * interrupted and return immediately. Only the original process which made the getThumbnail 473 * requests can cancel their own requests. 474 * 475 * @param cr ContentResolver 476 * @param origId original image or video id. use -1 to cancel all requests. 477 * @param groupId the same groupId used in getThumbnail 478 * @param baseUri the base URI of requested thumbnails 479 */ 480 static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri, 481 long groupId) { 482 Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1") 483 .appendQueryParameter("orig_id", String.valueOf(origId)) 484 .appendQueryParameter("group_id", String.valueOf(groupId)).build(); 485 Cursor c = null; 486 try { 487 c = cr.query(cancelUri, PROJECTION, null, null, null); 488 } 489 finally { 490 if (c != null) c.close(); 491 } 492 } 493 /** 494 * This method ensure thumbnails associated with origId are generated and decode the byte 495 * stream from database (MICRO_KIND) or file (MINI_KIND). 496 * 497 * Special optimization has been done to avoid further IPC communication for MICRO_KIND 498 * thumbnails. 499 * 500 * @param cr ContentResolver 501 * @param origId original image or video id 502 * @param kind could be MINI_KIND or MICRO_KIND 503 * @param options this is only used for MINI_KIND when decoding the Bitmap 504 * @param baseUri the base URI of requested thumbnails 505 * @param groupId the id of group to which this request belongs 506 * @return Bitmap bitmap of specified thumbnail kind 507 */ 508 static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind, 509 BitmapFactory.Options options, Uri baseUri, boolean isVideo) { 510 Bitmap bitmap = null; 511 String filePath = null; 512 // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo); 513 // If the magic is non-zero, we simply return thumbnail if it does exist. 514 // querying MediaProvider and simply return thumbnail. 515 MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI 516 : Images.Media.EXTERNAL_CONTENT_URI); 517 Cursor c = null; 518 try { 519 long magic = thumbFile.getMagic(origId); 520 if (magic != 0) { 521 if (kind == MICRO_KIND) { 522 synchronized (sThumbBufLock) { 523 if (sThumbBuf == null) { 524 sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; 525 } 526 if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { 527 bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); 528 if (bitmap == null) { 529 Log.w(TAG, "couldn't decode byte array."); 530 } 531 } 532 } 533 return bitmap; 534 } else if (kind == MINI_KIND) { 535 String column = isVideo ? "video_id=" : "image_id="; 536 c = cr.query(baseUri, PROJECTION, column + origId, null, null); 537 if (c != null && c.moveToFirst()) { 538 bitmap = getMiniThumbFromFile(c, baseUri, cr, options); 539 if (bitmap != null) { 540 return bitmap; 541 } 542 } 543 } 544 } 545 546 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1") 547 .appendQueryParameter("orig_id", String.valueOf(origId)) 548 .appendQueryParameter("group_id", String.valueOf(groupId)).build(); 549 if (c != null) c.close(); 550 c = cr.query(blockingUri, PROJECTION, null, null, null); 551 // This happens when original image/video doesn't exist. 552 if (c == null) return null; 553 554 // Assuming thumbnail has been generated, at least original image exists. 555 if (kind == MICRO_KIND) { 556 synchronized (sThumbBufLock) { 557 if (sThumbBuf == null) { 558 sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; 559 } 560 if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { 561 bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); 562 if (bitmap == null) { 563 Log.w(TAG, "couldn't decode byte array."); 564 } 565 } 566 } 567 } else if (kind == MINI_KIND) { 568 if (c.moveToFirst()) { 569 bitmap = getMiniThumbFromFile(c, baseUri, cr, options); 570 } 571 } else { 572 throw new IllegalArgumentException("Unsupported kind: " + kind); 573 } 574 575 // We probably run out of space, so create the thumbnail in memory. 576 if (bitmap == null) { 577 Log.v(TAG, "Create the thumbnail in memory: origId=" + origId 578 + ", kind=" + kind + ", isVideo="+isVideo); 579 Uri uri = Uri.parse( 580 baseUri.buildUpon().appendPath(String.valueOf(origId)) 581 .toString().replaceFirst("thumbnails", "media")); 582 if (filePath == null) { 583 if (c != null) c.close(); 584 c = cr.query(uri, PROJECTION, null, null, null); 585 if (c == null || !c.moveToFirst()) { 586 return null; 587 } 588 filePath = c.getString(1); 589 } 590 if (isVideo) { 591 bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind); 592 } else { 593 bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind); 594 } 595 } 596 } catch (SQLiteException ex) { 597 Log.w(TAG, ex); 598 } finally { 599 if (c != null) c.close(); 600 // To avoid file descriptor leak in application process. 601 thumbFile.deactivate(); 602 thumbFile = null; 603 } 604 return bitmap; 605 } 606 } 607 608 /** 609 * Contains meta data for all available images. 610 */ 611 public static final class Images { 612 public interface ImageColumns extends MediaColumns { 613 /** 614 * The description of the image 615 * <P>Type: TEXT</P> 616 */ 617 public static final String DESCRIPTION = "description"; 618 619 /** 620 * The picasa id of the image 621 * <P>Type: TEXT</P> 622 */ 623 public static final String PICASA_ID = "picasa_id"; 624 625 /** 626 * Whether the video should be published as public or private 627 * <P>Type: INTEGER</P> 628 */ 629 public static final String IS_PRIVATE = "isprivate"; 630 631 /** 632 * The latitude where the image was captured. 633 * <P>Type: DOUBLE</P> 634 */ 635 public static final String LATITUDE = "latitude"; 636 637 /** 638 * The longitude where the image was captured. 639 * <P>Type: DOUBLE</P> 640 */ 641 public static final String LONGITUDE = "longitude"; 642 643 /** 644 * The date & time that the image was taken in units 645 * of milliseconds since jan 1, 1970. 646 * <P>Type: INTEGER</P> 647 */ 648 public static final String DATE_TAKEN = "datetaken"; 649 650 /** 651 * The orientation for the image expressed as degrees. 652 * Only degrees 0, 90, 180, 270 will work. 653 * <P>Type: INTEGER</P> 654 */ 655 public static final String ORIENTATION = "orientation"; 656 657 /** 658 * The mini thumb id. 659 * <P>Type: INTEGER</P> 660 */ 661 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 662 663 /** 664 * The bucket id of the image. This is a read-only property that 665 * is automatically computed from the DATA column. 666 * <P>Type: TEXT</P> 667 */ 668 public static final String BUCKET_ID = "bucket_id"; 669 670 /** 671 * The bucket display name of the image. This is a read-only property that 672 * is automatically computed from the DATA column. 673 * <P>Type: TEXT</P> 674 */ 675 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 676 } 677 678 public static final class Media implements ImageColumns { 679 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 680 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 681 } 682 683 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 684 String where, String orderBy) { 685 return cr.query(uri, projection, where, 686 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 687 } 688 689 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 690 String selection, String [] selectionArgs, String orderBy) { 691 return cr.query(uri, projection, selection, 692 selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 693 } 694 695 /** 696 * Retrieves an image for the given url as a {@link Bitmap}. 697 * 698 * @param cr The content resolver to use 699 * @param url The url of the image 700 * @throws FileNotFoundException 701 * @throws IOException 702 */ 703 public static final Bitmap getBitmap(ContentResolver cr, Uri url) 704 throws FileNotFoundException, IOException { 705 InputStream input = cr.openInputStream(url); 706 Bitmap bitmap = BitmapFactory.decodeStream(input); 707 input.close(); 708 return bitmap; 709 } 710 711 /** 712 * Insert an image and create a thumbnail for it. 713 * 714 * @param cr The content resolver to use 715 * @param imagePath The path to the image to insert 716 * @param name The name of the image 717 * @param description The description of the image 718 * @return The URL to the newly created image 719 * @throws FileNotFoundException 720 */ 721 public static final String insertImage(ContentResolver cr, String imagePath, 722 String name, String description) throws FileNotFoundException { 723 // Check if file exists with a FileInputStream 724 FileInputStream stream = new FileInputStream(imagePath); 725 try { 726 Bitmap bm = BitmapFactory.decodeFile(imagePath); 727 String ret = insertImage(cr, bm, name, description); 728 bm.recycle(); 729 return ret; 730 } finally { 731 try { 732 stream.close(); 733 } catch (IOException e) { 734 } 735 } 736 } 737 738 private static final Bitmap StoreThumbnail( 739 ContentResolver cr, 740 Bitmap source, 741 long id, 742 float width, float height, 743 int kind) { 744 // create the matrix to scale it 745 Matrix matrix = new Matrix(); 746 747 float scaleX = width / source.getWidth(); 748 float scaleY = height / source.getHeight(); 749 750 matrix.setScale(scaleX, scaleY); 751 752 Bitmap thumb = Bitmap.createBitmap(source, 0, 0, 753 source.getWidth(), 754 source.getHeight(), matrix, 755 true); 756 757 ContentValues values = new ContentValues(4); 758 values.put(Images.Thumbnails.KIND, kind); 759 values.put(Images.Thumbnails.IMAGE_ID, (int)id); 760 values.put(Images.Thumbnails.HEIGHT, thumb.getHeight()); 761 values.put(Images.Thumbnails.WIDTH, thumb.getWidth()); 762 763 Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values); 764 765 try { 766 OutputStream thumbOut = cr.openOutputStream(url); 767 768 thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut); 769 thumbOut.close(); 770 return thumb; 771 } 772 catch (FileNotFoundException ex) { 773 return null; 774 } 775 catch (IOException ex) { 776 return null; 777 } 778 } 779 780 /** 781 * Insert an image and create a thumbnail for it. 782 * 783 * @param cr The content resolver to use 784 * @param source The stream to use for the image 785 * @param title The name of the image 786 * @param description The description of the image 787 * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored 788 * for any reason. 789 */ 790 public static final String insertImage(ContentResolver cr, Bitmap source, 791 String title, String description) { 792 ContentValues values = new ContentValues(); 793 values.put(Images.Media.TITLE, title); 794 values.put(Images.Media.DESCRIPTION, description); 795 values.put(Images.Media.MIME_TYPE, "image/jpeg"); 796 797 Uri url = null; 798 String stringUrl = null; /* value to be returned */ 799 800 try { 801 url = cr.insert(EXTERNAL_CONTENT_URI, values); 802 803 if (source != null) { 804 OutputStream imageOut = cr.openOutputStream(url); 805 try { 806 source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut); 807 } finally { 808 imageOut.close(); 809 } 810 811 long id = ContentUris.parseId(url); 812 // Wait until MINI_KIND thumbnail is generated. 813 Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id, 814 Images.Thumbnails.MINI_KIND, null); 815 // This is for backward compatibility. 816 Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F, 817 Images.Thumbnails.MICRO_KIND); 818 } else { 819 Log.e(TAG, "Failed to create thumbnail, removing original"); 820 cr.delete(url, null, null); 821 url = null; 822 } 823 } catch (Exception e) { 824 Log.e(TAG, "Failed to insert image", e); 825 if (url != null) { 826 cr.delete(url, null, null); 827 url = null; 828 } 829 } 830 831 if (url != null) { 832 stringUrl = url.toString(); 833 } 834 835 return stringUrl; 836 } 837 838 /** 839 * Get the content:// style URI for the image media table on the 840 * given volume. 841 * 842 * @param volumeName the name of the volume to get the URI for 843 * @return the URI to the image media table on the given volume 844 */ 845 public static Uri getContentUri(String volumeName) { 846 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 847 "/images/media"); 848 } 849 850 /** 851 * The content:// style URI for the internal storage. 852 */ 853 public static final Uri INTERNAL_CONTENT_URI = 854 getContentUri("internal"); 855 856 /** 857 * The content:// style URI for the "primary" external storage 858 * volume. 859 */ 860 public static final Uri EXTERNAL_CONTENT_URI = 861 getContentUri("external"); 862 863 /** 864 * The MIME type of of this directory of 865 * images. Note that each entry in this directory will have a standard 866 * image MIME type as appropriate -- for example, image/jpeg. 867 */ 868 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image"; 869 870 /** 871 * The default sort order for this table 872 */ 873 public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME; 874 } 875 876 /** 877 * This class allows developers to query and get two kinds of thumbnails: 878 * MINI_KIND: 512 x 384 thumbnail 879 * MICRO_KIND: 96 x 96 thumbnail 880 */ 881 public static class Thumbnails implements BaseColumns { 882 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 883 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 884 } 885 886 public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, 887 String[] projection) { 888 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER); 889 } 890 891 public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, 892 String[] projection) { 893 return cr.query(EXTERNAL_CONTENT_URI, projection, 894 IMAGE_ID + " = " + origId + " AND " + KIND + " = " + 895 kind, null, null); 896 } 897 898 /** 899 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 900 * interrupted and return immediately. Only the original process which made the getThumbnail 901 * requests can cancel their own requests. 902 * 903 * @param cr ContentResolver 904 * @param origId original image id 905 */ 906 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 907 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, 908 InternalThumbnails.DEFAULT_GROUP_ID); 909 } 910 911 /** 912 * This method checks if the thumbnails of the specified image (origId) has been created. 913 * It will be blocked until the thumbnails are generated. 914 * 915 * @param cr ContentResolver used to dispatch queries to MediaProvider. 916 * @param origId Original image id associated with thumbnail of interest. 917 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. 918 * @param options this is only used for MINI_KIND when decoding the Bitmap 919 * @return A Bitmap instance. It could be null if the original image 920 * associated with origId doesn't exist or memory is not enough. 921 */ 922 public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, 923 BitmapFactory.Options options) { 924 return InternalThumbnails.getThumbnail(cr, origId, 925 InternalThumbnails.DEFAULT_GROUP_ID, kind, options, 926 EXTERNAL_CONTENT_URI, false); 927 } 928 929 /** 930 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 931 * interrupted and return immediately. Only the original process which made the getThumbnail 932 * requests can cancel their own requests. 933 * 934 * @param cr ContentResolver 935 * @param origId original image id 936 * @param groupId the same groupId used in getThumbnail. 937 */ 938 public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { 939 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId); 940 } 941 942 /** 943 * This method checks if the thumbnails of the specified image (origId) has been created. 944 * It will be blocked until the thumbnails are generated. 945 * 946 * @param cr ContentResolver used to dispatch queries to MediaProvider. 947 * @param origId Original image id associated with thumbnail of interest. 948 * @param groupId the id of group to which this request belongs 949 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. 950 * @param options this is only used for MINI_KIND when decoding the Bitmap 951 * @return A Bitmap instance. It could be null if the original image 952 * associated with origId doesn't exist or memory is not enough. 953 */ 954 public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, 955 int kind, BitmapFactory.Options options) { 956 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options, 957 EXTERNAL_CONTENT_URI, false); 958 } 959 960 /** 961 * Get the content:// style URI for the image media table on the 962 * given volume. 963 * 964 * @param volumeName the name of the volume to get the URI for 965 * @return the URI to the image media table on the given volume 966 */ 967 public static Uri getContentUri(String volumeName) { 968 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 969 "/images/thumbnails"); 970 } 971 972 /** 973 * The content:// style URI for the internal storage. 974 */ 975 public static final Uri INTERNAL_CONTENT_URI = 976 getContentUri("internal"); 977 978 /** 979 * The content:// style URI for the "primary" external storage 980 * volume. 981 */ 982 public static final Uri EXTERNAL_CONTENT_URI = 983 getContentUri("external"); 984 985 /** 986 * The default sort order for this table 987 */ 988 public static final String DEFAULT_SORT_ORDER = "image_id ASC"; 989 990 /** 991 * The data stream for the thumbnail 992 * <P>Type: DATA STREAM</P> 993 */ 994 public static final String DATA = "_data"; 995 996 /** 997 * The original image for the thumbnal 998 * <P>Type: INTEGER (ID from Images table)</P> 999 */ 1000 public static final String IMAGE_ID = "image_id"; 1001 1002 /** 1003 * The kind of the thumbnail 1004 * <P>Type: INTEGER (One of the values below)</P> 1005 */ 1006 public static final String KIND = "kind"; 1007 1008 public static final int MINI_KIND = 1; 1009 public static final int FULL_SCREEN_KIND = 2; 1010 public static final int MICRO_KIND = 3; 1011 /** 1012 * The blob raw data of thumbnail 1013 * <P>Type: DATA STREAM</P> 1014 */ 1015 public static final String THUMB_DATA = "thumb_data"; 1016 1017 /** 1018 * The width of the thumbnal 1019 * <P>Type: INTEGER (long)</P> 1020 */ 1021 public static final String WIDTH = "width"; 1022 1023 /** 1024 * The height of the thumbnail 1025 * <P>Type: INTEGER (long)</P> 1026 */ 1027 public static final String HEIGHT = "height"; 1028 } 1029 } 1030 1031 /** 1032 * Container for all audio content. 1033 */ 1034 public static final class Audio { 1035 /** 1036 * Columns for audio file that show up in multiple tables. 1037 */ 1038 public interface AudioColumns extends MediaColumns { 1039 1040 /** 1041 * A non human readable key calculated from the TITLE, used for 1042 * searching, sorting and grouping 1043 * <P>Type: TEXT</P> 1044 */ 1045 public static final String TITLE_KEY = "title_key"; 1046 1047 /** 1048 * The duration of the audio file, in ms 1049 * <P>Type: INTEGER (long)</P> 1050 */ 1051 public static final String DURATION = "duration"; 1052 1053 /** 1054 * The position, in ms, playback was at when playback for this file 1055 * was last stopped. 1056 * <P>Type: INTEGER (long)</P> 1057 */ 1058 public static final String BOOKMARK = "bookmark"; 1059 1060 /** 1061 * The id of the artist who created the audio file, if any 1062 * <P>Type: INTEGER (long)</P> 1063 */ 1064 public static final String ARTIST_ID = "artist_id"; 1065 1066 /** 1067 * The artist who created the audio file, if any 1068 * <P>Type: TEXT</P> 1069 */ 1070 public static final String ARTIST = "artist"; 1071 1072 /** 1073 * The artist credited for the album that contains the audio file 1074 * <P>Type: TEXT</P> 1075 * @hide 1076 */ 1077 public static final String ALBUM_ARTIST = "album_artist"; 1078 1079 /** 1080 * Whether the song is part of a compilation 1081 * <P>Type: TEXT</P> 1082 * @hide 1083 */ 1084 public static final String COMPILATION = "compilation"; 1085 1086 /** 1087 * A non human readable key calculated from the ARTIST, used for 1088 * searching, sorting and grouping 1089 * <P>Type: TEXT</P> 1090 */ 1091 public static final String ARTIST_KEY = "artist_key"; 1092 1093 /** 1094 * The composer of the audio file, if any 1095 * <P>Type: TEXT</P> 1096 */ 1097 public static final String COMPOSER = "composer"; 1098 1099 /** 1100 * The id of the album the audio file is from, if any 1101 * <P>Type: INTEGER (long)</P> 1102 */ 1103 public static final String ALBUM_ID = "album_id"; 1104 1105 /** 1106 * The album the audio file is from, if any 1107 * <P>Type: TEXT</P> 1108 */ 1109 public static final String ALBUM = "album"; 1110 1111 /** 1112 * A non human readable key calculated from the ALBUM, used for 1113 * searching, sorting and grouping 1114 * <P>Type: TEXT</P> 1115 */ 1116 public static final String ALBUM_KEY = "album_key"; 1117 1118 /** 1119 * The track number of this song on the album, if any. 1120 * This number encodes both the track number and the 1121 * disc number. For multi-disc sets, this number will 1122 * be 1xxx for tracks on the first disc, 2xxx for tracks 1123 * on the second disc, etc. 1124 * <P>Type: INTEGER</P> 1125 */ 1126 public static final String TRACK = "track"; 1127 1128 /** 1129 * The year the audio file was recorded, if any 1130 * <P>Type: INTEGER</P> 1131 */ 1132 public static final String YEAR = "year"; 1133 1134 /** 1135 * Non-zero if the audio file is music 1136 * <P>Type: INTEGER (boolean)</P> 1137 */ 1138 public static final String IS_MUSIC = "is_music"; 1139 1140 /** 1141 * Non-zero if the audio file is a podcast 1142 * <P>Type: INTEGER (boolean)</P> 1143 */ 1144 public static final String IS_PODCAST = "is_podcast"; 1145 1146 /** 1147 * Non-zero if the audio file may be a ringtone 1148 * <P>Type: INTEGER (boolean)</P> 1149 */ 1150 public static final String IS_RINGTONE = "is_ringtone"; 1151 1152 /** 1153 * Non-zero if the audio file may be an alarm 1154 * <P>Type: INTEGER (boolean)</P> 1155 */ 1156 public static final String IS_ALARM = "is_alarm"; 1157 1158 /** 1159 * Non-zero if the audio file may be a notification sound 1160 * <P>Type: INTEGER (boolean)</P> 1161 */ 1162 public static final String IS_NOTIFICATION = "is_notification"; 1163 1164 /** 1165 * The genre of the audio file, if any 1166 * <P>Type: TEXT</P> 1167 * Does not exist in the database - only used by the media scanner for inserts. 1168 * @hide 1169 */ 1170 public static final String GENRE = "genre"; 1171 } 1172 1173 /** 1174 * Converts a name to a "key" that can be used for grouping, sorting 1175 * and searching. 1176 * The rules that govern this conversion are: 1177 * - remove 'special' characters like ()[]'!?., 1178 * - remove leading/trailing spaces 1179 * - convert everything to lowercase 1180 * - remove leading "the ", "an " and "a " 1181 * - remove trailing ", the|an|a" 1182 * - remove accents. This step leaves us with CollationKey data, 1183 * which is not human readable 1184 * 1185 * @param name The artist or album name to convert 1186 * @return The "key" for the given name. 1187 */ 1188 public static String keyFor(String name) { 1189 if (name != null) { 1190 boolean sortfirst = false; 1191 if (name.equals(UNKNOWN_STRING)) { 1192 return "\001"; 1193 } 1194 // Check if the first character is \001. We use this to 1195 // force sorting of certain special files, like the silent ringtone. 1196 if (name.startsWith("\001")) { 1197 sortfirst = true; 1198 } 1199 name = name.trim().toLowerCase(); 1200 if (name.startsWith("the ")) { 1201 name = name.substring(4); 1202 } 1203 if (name.startsWith("an ")) { 1204 name = name.substring(3); 1205 } 1206 if (name.startsWith("a ")) { 1207 name = name.substring(2); 1208 } 1209 if (name.endsWith(", the") || name.endsWith(",the") || 1210 name.endsWith(", an") || name.endsWith(",an") || 1211 name.endsWith(", a") || name.endsWith(",a")) { 1212 name = name.substring(0, name.lastIndexOf(',')); 1213 } 1214 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim(); 1215 if (name.length() > 0) { 1216 // Insert a separator between the characters to avoid 1217 // matches on a partial character. If we ever change 1218 // to start-of-word-only matches, this can be removed. 1219 StringBuilder b = new StringBuilder(); 1220 b.append('.'); 1221 int nl = name.length(); 1222 for (int i = 0; i < nl; i++) { 1223 b.append(name.charAt(i)); 1224 b.append('.'); 1225 } 1226 name = b.toString(); 1227 String key = DatabaseUtils.getCollationKey(name); 1228 if (sortfirst) { 1229 key = "\001" + key; 1230 } 1231 return key; 1232 } else { 1233 return ""; 1234 } 1235 } 1236 return null; 1237 } 1238 1239 public static final class Media implements AudioColumns { 1240 /** 1241 * Get the content:// style URI for the audio media table on the 1242 * given volume. 1243 * 1244 * @param volumeName the name of the volume to get the URI for 1245 * @return the URI to the audio media table on the given volume 1246 */ 1247 public static Uri getContentUri(String volumeName) { 1248 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1249 "/audio/media"); 1250 } 1251 1252 public static Uri getContentUriForPath(String path) { 1253 return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ? 1254 EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI); 1255 } 1256 1257 /** 1258 * The content:// style URI for the internal storage. 1259 */ 1260 public static final Uri INTERNAL_CONTENT_URI = 1261 getContentUri("internal"); 1262 1263 /** 1264 * The content:// style URI for the "primary" external storage 1265 * volume. 1266 */ 1267 public static final Uri EXTERNAL_CONTENT_URI = 1268 getContentUri("external"); 1269 1270 /** 1271 * The MIME type for this table. 1272 */ 1273 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 1274 1275 /** 1276 * The default sort order for this table 1277 */ 1278 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 1279 1280 /** 1281 * Activity Action: Start SoundRecorder application. 1282 * <p>Input: nothing. 1283 * <p>Output: An uri to the recorded sound stored in the Media Library 1284 * if the recording was successful. 1285 * May also contain the extra EXTRA_MAX_BYTES. 1286 * @see #EXTRA_MAX_BYTES 1287 */ 1288 public static final String RECORD_SOUND_ACTION = 1289 "android.provider.MediaStore.RECORD_SOUND"; 1290 1291 /** 1292 * The name of the Intent-extra used to define a maximum file size for 1293 * a recording made by the SoundRecorder application. 1294 * 1295 * @see #RECORD_SOUND_ACTION 1296 */ 1297 public static final String EXTRA_MAX_BYTES = 1298 "android.provider.MediaStore.extra.MAX_BYTES"; 1299 } 1300 1301 /** 1302 * Columns representing an audio genre 1303 */ 1304 public interface GenresColumns { 1305 /** 1306 * The name of the genre 1307 * <P>Type: TEXT</P> 1308 */ 1309 public static final String NAME = "name"; 1310 } 1311 1312 /** 1313 * Contains all genres for audio files 1314 */ 1315 public static final class Genres implements BaseColumns, GenresColumns { 1316 /** 1317 * Get the content:// style URI for the audio genres table on the 1318 * given volume. 1319 * 1320 * @param volumeName the name of the volume to get the URI for 1321 * @return the URI to the audio genres table on the given volume 1322 */ 1323 public static Uri getContentUri(String volumeName) { 1324 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1325 "/audio/genres"); 1326 } 1327 1328 /** 1329 * Get the content:// style URI for querying the genres of an audio file. 1330 * 1331 * @param volumeName the name of the volume to get the URI for 1332 * @param audioId the ID of the audio file for which to retrieve the genres 1333 * @return the URI to for querying the genres for the audio file 1334 * with the given the volume and audioID 1335 */ 1336 public static Uri getContentUriForAudioId(String volumeName, int audioId) { 1337 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1338 "/audio/media/" + audioId + "/genres"); 1339 } 1340 1341 /** 1342 * The content:// style URI for the internal storage. 1343 */ 1344 public static final Uri INTERNAL_CONTENT_URI = 1345 getContentUri("internal"); 1346 1347 /** 1348 * The content:// style URI for the "primary" external storage 1349 * volume. 1350 */ 1351 public static final Uri EXTERNAL_CONTENT_URI = 1352 getContentUri("external"); 1353 1354 /** 1355 * The MIME type for this table. 1356 */ 1357 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre"; 1358 1359 /** 1360 * The MIME type for entries in this table. 1361 */ 1362 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre"; 1363 1364 /** 1365 * The default sort order for this table 1366 */ 1367 public static final String DEFAULT_SORT_ORDER = NAME; 1368 1369 /** 1370 * Sub-directory of each genre containing all members. 1371 */ 1372 public static final class Members implements AudioColumns { 1373 1374 public static final Uri getContentUri(String volumeName, 1375 long genreId) { 1376 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1377 + "/audio/genres/" + genreId + "/members"); 1378 } 1379 1380 /** 1381 * A subdirectory of each genre containing all member audio files. 1382 */ 1383 public static final String CONTENT_DIRECTORY = "members"; 1384 1385 /** 1386 * The default sort order for this table 1387 */ 1388 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 1389 1390 /** 1391 * The ID of the audio file 1392 * <P>Type: INTEGER (long)</P> 1393 */ 1394 public static final String AUDIO_ID = "audio_id"; 1395 1396 /** 1397 * The ID of the genre 1398 * <P>Type: INTEGER (long)</P> 1399 */ 1400 public static final String GENRE_ID = "genre_id"; 1401 } 1402 } 1403 1404 /** 1405 * Columns representing a playlist 1406 */ 1407 public interface PlaylistsColumns { 1408 /** 1409 * The name of the playlist 1410 * <P>Type: TEXT</P> 1411 */ 1412 public static final String NAME = "name"; 1413 1414 /** 1415 * The data stream for the playlist file 1416 * <P>Type: DATA STREAM</P> 1417 */ 1418 public static final String DATA = "_data"; 1419 1420 /** 1421 * The time the file was added to the media provider 1422 * Units are seconds since 1970. 1423 * <P>Type: INTEGER (long)</P> 1424 */ 1425 public static final String DATE_ADDED = "date_added"; 1426 1427 /** 1428 * The time the file was last modified 1429 * Units are seconds since 1970. 1430 * NOTE: This is for internal use by the media scanner. Do not modify this field. 1431 * <P>Type: INTEGER (long)</P> 1432 */ 1433 public static final String DATE_MODIFIED = "date_modified"; 1434 } 1435 1436 /** 1437 * Contains playlists for audio files 1438 */ 1439 public static final class Playlists implements BaseColumns, 1440 PlaylistsColumns { 1441 /** 1442 * Get the content:// style URI for the audio playlists table on the 1443 * given volume. 1444 * 1445 * @param volumeName the name of the volume to get the URI for 1446 * @return the URI to the audio playlists table on the given volume 1447 */ 1448 public static Uri getContentUri(String volumeName) { 1449 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1450 "/audio/playlists"); 1451 } 1452 1453 /** 1454 * The content:// style URI for the internal storage. 1455 */ 1456 public static final Uri INTERNAL_CONTENT_URI = 1457 getContentUri("internal"); 1458 1459 /** 1460 * The content:// style URI for the "primary" external storage 1461 * volume. 1462 */ 1463 public static final Uri EXTERNAL_CONTENT_URI = 1464 getContentUri("external"); 1465 1466 /** 1467 * The MIME type for this table. 1468 */ 1469 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist"; 1470 1471 /** 1472 * The MIME type for entries in this table. 1473 */ 1474 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist"; 1475 1476 /** 1477 * The default sort order for this table 1478 */ 1479 public static final String DEFAULT_SORT_ORDER = NAME; 1480 1481 /** 1482 * Sub-directory of each playlist containing all members. 1483 */ 1484 public static final class Members implements AudioColumns { 1485 public static final Uri getContentUri(String volumeName, 1486 long playlistId) { 1487 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1488 + "/audio/playlists/" + playlistId + "/members"); 1489 } 1490 1491 /** 1492 * Convenience method to move a playlist item to a new location 1493 * @param res The content resolver to use 1494 * @param playlistId The numeric id of the playlist 1495 * @param from The position of the item to move 1496 * @param to The position to move the item to 1497 * @return true on success 1498 */ 1499 public static final boolean moveItem(ContentResolver res, 1500 long playlistId, int from, int to) { 1501 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 1502 playlistId) 1503 .buildUpon() 1504 .appendEncodedPath(String.valueOf(from)) 1505 .appendQueryParameter("move", "true") 1506 .build(); 1507 ContentValues values = new ContentValues(); 1508 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to); 1509 return res.update(uri, values, null, null) != 0; 1510 } 1511 1512 /** 1513 * The ID within the playlist. 1514 */ 1515 public static final String _ID = "_id"; 1516 1517 /** 1518 * A subdirectory of each playlist containing all member audio 1519 * files. 1520 */ 1521 public static final String CONTENT_DIRECTORY = "members"; 1522 1523 /** 1524 * The ID of the audio file 1525 * <P>Type: INTEGER (long)</P> 1526 */ 1527 public static final String AUDIO_ID = "audio_id"; 1528 1529 /** 1530 * The ID of the playlist 1531 * <P>Type: INTEGER (long)</P> 1532 */ 1533 public static final String PLAYLIST_ID = "playlist_id"; 1534 1535 /** 1536 * The order of the songs in the playlist 1537 * <P>Type: INTEGER (long)></P> 1538 */ 1539 public static final String PLAY_ORDER = "play_order"; 1540 1541 /** 1542 * The default sort order for this table 1543 */ 1544 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER; 1545 } 1546 } 1547 1548 /** 1549 * Columns representing an artist 1550 */ 1551 public interface ArtistColumns { 1552 /** 1553 * The artist who created the audio file, if any 1554 * <P>Type: TEXT</P> 1555 */ 1556 public static final String ARTIST = "artist"; 1557 1558 /** 1559 * A non human readable key calculated from the ARTIST, used for 1560 * searching, sorting and grouping 1561 * <P>Type: TEXT</P> 1562 */ 1563 public static final String ARTIST_KEY = "artist_key"; 1564 1565 /** 1566 * The number of albums in the database for this artist 1567 */ 1568 public static final String NUMBER_OF_ALBUMS = "number_of_albums"; 1569 1570 /** 1571 * The number of albums in the database for this artist 1572 */ 1573 public static final String NUMBER_OF_TRACKS = "number_of_tracks"; 1574 } 1575 1576 /** 1577 * Contains artists for audio files 1578 */ 1579 public static final class Artists implements BaseColumns, ArtistColumns { 1580 /** 1581 * Get the content:// style URI for the artists table on the 1582 * given volume. 1583 * 1584 * @param volumeName the name of the volume to get the URI for 1585 * @return the URI to the audio artists table on the given volume 1586 */ 1587 public static Uri getContentUri(String volumeName) { 1588 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1589 "/audio/artists"); 1590 } 1591 1592 /** 1593 * The content:// style URI for the internal storage. 1594 */ 1595 public static final Uri INTERNAL_CONTENT_URI = 1596 getContentUri("internal"); 1597 1598 /** 1599 * The content:// style URI for the "primary" external storage 1600 * volume. 1601 */ 1602 public static final Uri EXTERNAL_CONTENT_URI = 1603 getContentUri("external"); 1604 1605 /** 1606 * The MIME type for this table. 1607 */ 1608 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists"; 1609 1610 /** 1611 * The MIME type for entries in this table. 1612 */ 1613 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist"; 1614 1615 /** 1616 * The default sort order for this table 1617 */ 1618 public static final String DEFAULT_SORT_ORDER = ARTIST_KEY; 1619 1620 /** 1621 * Sub-directory of each artist containing all albums on which 1622 * a song by the artist appears. 1623 */ 1624 public static final class Albums implements AlbumColumns { 1625 public static final Uri getContentUri(String volumeName, 1626 long artistId) { 1627 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1628 + "/audio/artists/" + artistId + "/albums"); 1629 } 1630 } 1631 } 1632 1633 /** 1634 * Columns representing an album 1635 */ 1636 public interface AlbumColumns { 1637 1638 /** 1639 * The id for the album 1640 * <P>Type: INTEGER</P> 1641 */ 1642 public static final String ALBUM_ID = "album_id"; 1643 1644 /** 1645 * The album on which the audio file appears, if any 1646 * <P>Type: TEXT</P> 1647 */ 1648 public static final String ALBUM = "album"; 1649 1650 /** 1651 * The artist whose songs appear on this album 1652 * <P>Type: TEXT</P> 1653 */ 1654 public static final String ARTIST = "artist"; 1655 1656 /** 1657 * The number of songs on this album 1658 * <P>Type: INTEGER</P> 1659 */ 1660 public static final String NUMBER_OF_SONGS = "numsongs"; 1661 1662 /** 1663 * This column is available when getting album info via artist, 1664 * and indicates the number of songs on the album by the given 1665 * artist. 1666 * <P>Type: INTEGER</P> 1667 */ 1668 public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; 1669 1670 /** 1671 * The year in which the earliest songs 1672 * on this album were released. This will often 1673 * be the same as {@link #LAST_YEAR}, but for compilation albums 1674 * they might differ. 1675 * <P>Type: INTEGER</P> 1676 */ 1677 public static final String FIRST_YEAR = "minyear"; 1678 1679 /** 1680 * The year in which the latest songs 1681 * on this album were released. This will often 1682 * be the same as {@link #FIRST_YEAR}, but for compilation albums 1683 * they might differ. 1684 * <P>Type: INTEGER</P> 1685 */ 1686 public static final String LAST_YEAR = "maxyear"; 1687 1688 /** 1689 * A non human readable key calculated from the ALBUM, used for 1690 * searching, sorting and grouping 1691 * <P>Type: TEXT</P> 1692 */ 1693 public static final String ALBUM_KEY = "album_key"; 1694 1695 /** 1696 * Cached album art. 1697 * <P>Type: TEXT</P> 1698 */ 1699 public static final String ALBUM_ART = "album_art"; 1700 } 1701 1702 /** 1703 * Contains artists for audio files 1704 */ 1705 public static final class Albums implements BaseColumns, AlbumColumns { 1706 /** 1707 * Get the content:// style URI for the albums table on the 1708 * given volume. 1709 * 1710 * @param volumeName the name of the volume to get the URI for 1711 * @return the URI to the audio albums table on the given volume 1712 */ 1713 public static Uri getContentUri(String volumeName) { 1714 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1715 "/audio/albums"); 1716 } 1717 1718 /** 1719 * The content:// style URI for the internal storage. 1720 */ 1721 public static final Uri INTERNAL_CONTENT_URI = 1722 getContentUri("internal"); 1723 1724 /** 1725 * The content:// style URI for the "primary" external storage 1726 * volume. 1727 */ 1728 public static final Uri EXTERNAL_CONTENT_URI = 1729 getContentUri("external"); 1730 1731 /** 1732 * The MIME type for this table. 1733 */ 1734 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums"; 1735 1736 /** 1737 * The MIME type for entries in this table. 1738 */ 1739 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album"; 1740 1741 /** 1742 * The default sort order for this table 1743 */ 1744 public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; 1745 } 1746 } 1747 1748 public static final class Video { 1749 1750 /** 1751 * The default sort order for this table. 1752 */ 1753 public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME; 1754 1755 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 1756 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 1757 } 1758 1759 public interface VideoColumns extends MediaColumns { 1760 1761 /** 1762 * The duration of the video file, in ms 1763 * <P>Type: INTEGER (long)</P> 1764 */ 1765 public static final String DURATION = "duration"; 1766 1767 /** 1768 * The artist who created the video file, if any 1769 * <P>Type: TEXT</P> 1770 */ 1771 public static final String ARTIST = "artist"; 1772 1773 /** 1774 * The album the video file is from, if any 1775 * <P>Type: TEXT</P> 1776 */ 1777 public static final String ALBUM = "album"; 1778 1779 /** 1780 * The resolution of the video file, formatted as "XxY" 1781 * <P>Type: TEXT</P> 1782 */ 1783 public static final String RESOLUTION = "resolution"; 1784 1785 /** 1786 * The description of the video recording 1787 * <P>Type: TEXT</P> 1788 */ 1789 public static final String DESCRIPTION = "description"; 1790 1791 /** 1792 * Whether the video should be published as public or private 1793 * <P>Type: INTEGER</P> 1794 */ 1795 public static final String IS_PRIVATE = "isprivate"; 1796 1797 /** 1798 * The user-added tags associated with a video 1799 * <P>Type: TEXT</P> 1800 */ 1801 public static final String TAGS = "tags"; 1802 1803 /** 1804 * The YouTube category of the video 1805 * <P>Type: TEXT</P> 1806 */ 1807 public static final String CATEGORY = "category"; 1808 1809 /** 1810 * The language of the video 1811 * <P>Type: TEXT</P> 1812 */ 1813 public static final String LANGUAGE = "language"; 1814 1815 /** 1816 * The latitude where the image was captured. 1817 * <P>Type: DOUBLE</P> 1818 */ 1819 public static final String LATITUDE = "latitude"; 1820 1821 /** 1822 * The longitude where the image was captured. 1823 * <P>Type: DOUBLE</P> 1824 */ 1825 public static final String LONGITUDE = "longitude"; 1826 1827 /** 1828 * The date & time that the image was taken in units 1829 * of milliseconds since jan 1, 1970. 1830 * <P>Type: INTEGER</P> 1831 */ 1832 public static final String DATE_TAKEN = "datetaken"; 1833 1834 /** 1835 * The mini thumb id. 1836 * <P>Type: INTEGER</P> 1837 */ 1838 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 1839 1840 /** 1841 * The bucket id of the video. This is a read-only property that 1842 * is automatically computed from the DATA column. 1843 * <P>Type: TEXT</P> 1844 */ 1845 public static final String BUCKET_ID = "bucket_id"; 1846 1847 /** 1848 * The bucket display name of the video. This is a read-only property that 1849 * is automatically computed from the DATA column. 1850 * <P>Type: TEXT</P> 1851 */ 1852 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1853 1854 /** 1855 * The bookmark for the video. Time in ms. Represents the location in the video that the 1856 * video should start playing at the next time it is opened. If the value is null or 1857 * out of the range 0..DURATION-1 then the video should start playing from the 1858 * beginning. 1859 * <P>Type: INTEGER</P> 1860 */ 1861 public static final String BOOKMARK = "bookmark"; 1862 } 1863 1864 public static final class Media implements VideoColumns { 1865 /** 1866 * Get the content:// style URI for the video media table on the 1867 * given volume. 1868 * 1869 * @param volumeName the name of the volume to get the URI for 1870 * @return the URI to the video media table on the given volume 1871 */ 1872 public static Uri getContentUri(String volumeName) { 1873 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1874 "/video/media"); 1875 } 1876 1877 /** 1878 * The content:// style URI for the internal storage. 1879 */ 1880 public static final Uri INTERNAL_CONTENT_URI = 1881 getContentUri("internal"); 1882 1883 /** 1884 * The content:// style URI for the "primary" external storage 1885 * volume. 1886 */ 1887 public static final Uri EXTERNAL_CONTENT_URI = 1888 getContentUri("external"); 1889 1890 /** 1891 * The MIME type for this table. 1892 */ 1893 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 1894 1895 /** 1896 * The default sort order for this table 1897 */ 1898 public static final String DEFAULT_SORT_ORDER = TITLE; 1899 } 1900 1901 /** 1902 * This class allows developers to query and get two kinds of thumbnails: 1903 * MINI_KIND: 512 x 384 thumbnail 1904 * MICRO_KIND: 96 x 96 thumbnail 1905 * 1906 */ 1907 public static class Thumbnails implements BaseColumns { 1908 /** 1909 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 1910 * interrupted and return immediately. Only the original process which made the getThumbnail 1911 * requests can cancel their own requests. 1912 * 1913 * @param cr ContentResolver 1914 * @param origId original video id 1915 */ 1916 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 1917 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, 1918 InternalThumbnails.DEFAULT_GROUP_ID); 1919 } 1920 1921 /** 1922 * This method checks if the thumbnails of the specified image (origId) has been created. 1923 * It will be blocked until the thumbnails are generated. 1924 * 1925 * @param cr ContentResolver used to dispatch queries to MediaProvider. 1926 * @param origId Original image id associated with thumbnail of interest. 1927 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. 1928 * @param options this is only used for MINI_KIND when decoding the Bitmap 1929 * @return A Bitmap instance. It could be null if the original image 1930 * associated with origId doesn't exist or memory is not enough. 1931 */ 1932 public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, 1933 BitmapFactory.Options options) { 1934 return InternalThumbnails.getThumbnail(cr, origId, 1935 InternalThumbnails.DEFAULT_GROUP_ID, kind, options, 1936 EXTERNAL_CONTENT_URI, true); 1937 } 1938 1939 /** 1940 * This method checks if the thumbnails of the specified image (origId) has been created. 1941 * It will be blocked until the thumbnails are generated. 1942 * 1943 * @param cr ContentResolver used to dispatch queries to MediaProvider. 1944 * @param origId Original image id associated with thumbnail of interest. 1945 * @param groupId the id of group to which this request belongs 1946 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND 1947 * @param options this is only used for MINI_KIND when decoding the Bitmap 1948 * @return A Bitmap instance. It could be null if the original image associated with 1949 * origId doesn't exist or memory is not enough. 1950 */ 1951 public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, 1952 int kind, BitmapFactory.Options options) { 1953 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options, 1954 EXTERNAL_CONTENT_URI, true); 1955 } 1956 1957 /** 1958 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 1959 * interrupted and return immediately. Only the original process which made the getThumbnail 1960 * requests can cancel their own requests. 1961 * 1962 * @param cr ContentResolver 1963 * @param origId original video id 1964 * @param groupId the same groupId used in getThumbnail. 1965 */ 1966 public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { 1967 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId); 1968 } 1969 1970 /** 1971 * Get the content:// style URI for the image media table on the 1972 * given volume. 1973 * 1974 * @param volumeName the name of the volume to get the URI for 1975 * @return the URI to the image media table on the given volume 1976 */ 1977 public static Uri getContentUri(String volumeName) { 1978 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1979 "/video/thumbnails"); 1980 } 1981 1982 /** 1983 * The content:// style URI for the internal storage. 1984 */ 1985 public static final Uri INTERNAL_CONTENT_URI = 1986 getContentUri("internal"); 1987 1988 /** 1989 * The content:// style URI for the "primary" external storage 1990 * volume. 1991 */ 1992 public static final Uri EXTERNAL_CONTENT_URI = 1993 getContentUri("external"); 1994 1995 /** 1996 * The default sort order for this table 1997 */ 1998 public static final String DEFAULT_SORT_ORDER = "video_id ASC"; 1999 2000 /** 2001 * The data stream for the thumbnail 2002 * <P>Type: DATA STREAM</P> 2003 */ 2004 public static final String DATA = "_data"; 2005 2006 /** 2007 * The original image for the thumbnal 2008 * <P>Type: INTEGER (ID from Video table)</P> 2009 */ 2010 public static final String VIDEO_ID = "video_id"; 2011 2012 /** 2013 * The kind of the thumbnail 2014 * <P>Type: INTEGER (One of the values below)</P> 2015 */ 2016 public static final String KIND = "kind"; 2017 2018 public static final int MINI_KIND = 1; 2019 public static final int FULL_SCREEN_KIND = 2; 2020 public static final int MICRO_KIND = 3; 2021 2022 /** 2023 * The width of the thumbnal 2024 * <P>Type: INTEGER (long)</P> 2025 */ 2026 public static final String WIDTH = "width"; 2027 2028 /** 2029 * The height of the thumbnail 2030 * <P>Type: INTEGER (long)</P> 2031 */ 2032 public static final String HEIGHT = "height"; 2033 } 2034 } 2035 2036 /** 2037 * Uri for querying the state of the media scanner. 2038 */ 2039 public static Uri getMediaScannerUri() { 2040 return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner"); 2041 } 2042 2043 /** 2044 * Name of current volume being scanned by the media scanner. 2045 */ 2046 public static final String MEDIA_SCANNER_VOLUME = "volume"; 2047 2048 /** 2049 * Name of the file signaling the media scanner to ignore media in the containing directory 2050 * and its subdirectories. Developers should use this to avoid application graphics showing 2051 * up in the Gallery and likewise prevent application sounds and music from showing up in 2052 * the Music app. 2053 */ 2054 public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; 2055 2056 /** 2057 * Get the media provider's version. 2058 * Applications that import data from the media provider into their own caches 2059 * can use this to detect that the media provider changed, and reimport data 2060 * as needed. No other assumptions should be made about the meaning of the version. 2061 * @param context Context to use for performing the query. 2062 * @return A version string, or null if the version could not be determined. 2063 */ 2064 public static String getVersion(Context context) { 2065 Cursor c = context.getContentResolver().query( 2066 Uri.parse(CONTENT_AUTHORITY_SLASH + "none/version"), 2067 null, null, null, null); 2068 if (c != null) { 2069 try { 2070 if (c.moveToFirst()) { 2071 return c.getString(0); 2072 } 2073 } finally { 2074 c.close(); 2075 } 2076 } 2077 return null; 2078 } 2079 2080 } 2081