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