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