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 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 */ 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 */ 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 { 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 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 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 */ 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 */ 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 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 */ 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 */ 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 { 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 * A non human readable key calculated from the ARTIST, used for 904 * searching, sorting and grouping 905 * <P>Type: TEXT</P> 906 */ 907 public static final String ARTIST_KEY = "artist_key"; 908 909 /** 910 * The composer of the audio file, if any 911 * <P>Type: TEXT</P> 912 */ 913 public static final String COMPOSER = "composer"; 914 915 /** 916 * The id of the album the audio file is from, if any 917 * <P>Type: INTEGER (long)</P> 918 */ 919 public static final String ALBUM_ID = "album_id"; 920 921 /** 922 * The album the audio file is from, if any 923 * <P>Type: TEXT</P> 924 */ 925 public static final String ALBUM = "album"; 926 927 /** 928 * A non human readable key calculated from the ALBUM, used for 929 * searching, sorting and grouping 930 * <P>Type: TEXT</P> 931 */ 932 public static final String ALBUM_KEY = "album_key"; 933 934 /** 935 * A URI to the album art, if any 936 * <P>Type: TEXT</P> 937 */ 938 public static final String ALBUM_ART = "album_art"; 939 940 /** 941 * The track number of this song on the album, if any. 942 * This number encodes both the track number and the 943 * disc number. For multi-disc sets, this number will 944 * be 1xxx for tracks on the first disc, 2xxx for tracks 945 * on the second disc, etc. 946 * <P>Type: INTEGER</P> 947 */ 948 public static final String TRACK = "track"; 949 950 /** 951 * The year the audio file was recorded, if any 952 * <P>Type: INTEGER</P> 953 */ 954 public static final String YEAR = "year"; 955 956 /** 957 * Non-zero if the audio file is music 958 * <P>Type: INTEGER (boolean)</P> 959 */ 960 public static final String IS_MUSIC = "is_music"; 961 962 /** 963 * Non-zero if the audio file is a podcast 964 * <P>Type: INTEGER (boolean)</P> 965 */ 966 public static final String IS_PODCAST = "is_podcast"; 967 968 /** 969 * Non-zero id the audio file may be a ringtone 970 * <P>Type: INTEGER (boolean)</P> 971 */ 972 public static final String IS_RINGTONE = "is_ringtone"; 973 974 /** 975 * Non-zero id the audio file may be an alarm 976 * <P>Type: INTEGER (boolean)</P> 977 */ 978 public static final String IS_ALARM = "is_alarm"; 979 980 /** 981 * Non-zero id the audio file may be a notification sound 982 * <P>Type: INTEGER (boolean)</P> 983 */ 984 public static final String IS_NOTIFICATION = "is_notification"; 985 } 986 987 /** 988 * Converts a name to a "key" that can be used for grouping, sorting 989 * and searching. 990 * The rules that govern this conversion are: 991 * - remove 'special' characters like ()[]'!?., 992 * - remove leading/trailing spaces 993 * - convert everything to lowercase 994 * - remove leading "the ", "an " and "a " 995 * - remove trailing ", the|an|a" 996 * - remove accents. This step leaves us with CollationKey data, 997 * which is not human readable 998 * 999 * @param name The artist or album name to convert 1000 * @return The "key" for the given name. 1001 */ 1002 public static String keyFor(String name) { 1003 if (name != null) { 1004 boolean sortfirst = false; 1005 if (name.equals(UNKNOWN_STRING)) { 1006 return "\001"; 1007 } 1008 // Check if the first character is \001. We use this to 1009 // force sorting of certain special files, like the silent ringtone. 1010 if (name.startsWith("\001")) { 1011 sortfirst = true; 1012 } 1013 name = name.trim().toLowerCase(); 1014 if (name.startsWith("the ")) { 1015 name = name.substring(4); 1016 } 1017 if (name.startsWith("an ")) { 1018 name = name.substring(3); 1019 } 1020 if (name.startsWith("a ")) { 1021 name = name.substring(2); 1022 } 1023 if (name.endsWith(", the") || name.endsWith(",the") || 1024 name.endsWith(", an") || name.endsWith(",an") || 1025 name.endsWith(", a") || name.endsWith(",a")) { 1026 name = name.substring(0, name.lastIndexOf(',')); 1027 } 1028 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim(); 1029 if (name.length() > 0) { 1030 // Insert a separator between the characters to avoid 1031 // matches on a partial character. If we ever change 1032 // to start-of-word-only matches, this can be removed. 1033 StringBuilder b = new StringBuilder(); 1034 b.append('.'); 1035 int nl = name.length(); 1036 for (int i = 0; i < nl; i++) { 1037 b.append(name.charAt(i)); 1038 b.append('.'); 1039 } 1040 name = b.toString(); 1041 String key = DatabaseUtils.getCollationKey(name); 1042 if (sortfirst) { 1043 key = "\001" + key; 1044 } 1045 return key; 1046 } else { 1047 return ""; 1048 } 1049 } 1050 return null; 1051 } 1052 1053 public static final class Media implements AudioColumns { 1054 /** 1055 * Get the content:// style URI for the audio media table on the 1056 * given volume. 1057 * 1058 * @param volumeName the name of the volume to get the URI for 1059 * @return the URI to the audio media table on the given volume 1060 */ 1061 public static Uri getContentUri(String volumeName) { 1062 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1063 "/audio/media"); 1064 } 1065 1066 public static Uri getContentUriForPath(String path) { 1067 return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ? 1068 EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI); 1069 } 1070 1071 /** 1072 * The content:// style URI for the internal storage. 1073 */ 1074 public static final Uri INTERNAL_CONTENT_URI = 1075 getContentUri("internal"); 1076 1077 /** 1078 * The content:// style URI for the "primary" external storage 1079 * volume. 1080 */ 1081 public static final Uri EXTERNAL_CONTENT_URI = 1082 getContentUri("external"); 1083 1084 /** 1085 * The MIME type for this table. 1086 */ 1087 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 1088 1089 /** 1090 * The default sort order for this table 1091 */ 1092 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 1093 1094 /** 1095 * Activity Action: Start SoundRecorder application. 1096 * <p>Input: nothing. 1097 * <p>Output: An uri to the recorded sound stored in the Media Library 1098 * if the recording was successful. 1099 * May also contain the extra EXTRA_MAX_BYTES. 1100 * @see #EXTRA_MAX_BYTES 1101 */ 1102 public static final String RECORD_SOUND_ACTION = 1103 "android.provider.MediaStore.RECORD_SOUND"; 1104 1105 /** 1106 * The name of the Intent-extra used to define a maximum file size for 1107 * a recording made by the SoundRecorder application. 1108 * 1109 * @see #RECORD_SOUND_ACTION 1110 */ 1111 public static final String EXTRA_MAX_BYTES = 1112 "android.provider.MediaStore.extra.MAX_BYTES"; 1113 } 1114 1115 /** 1116 * Columns representing an audio genre 1117 */ 1118 public interface GenresColumns { 1119 /** 1120 * The name of the genre 1121 * <P>Type: TEXT</P> 1122 */ 1123 public static final String NAME = "name"; 1124 } 1125 1126 /** 1127 * Contains all genres for audio files 1128 */ 1129 public static final class Genres implements BaseColumns, GenresColumns { 1130 /** 1131 * Get the content:// style URI for the audio genres table on the 1132 * given volume. 1133 * 1134 * @param volumeName the name of the volume to get the URI for 1135 * @return the URI to the audio genres table on the given volume 1136 */ 1137 public static Uri getContentUri(String volumeName) { 1138 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1139 "/audio/genres"); 1140 } 1141 1142 /** 1143 * The content:// style URI for the internal storage. 1144 */ 1145 public static final Uri INTERNAL_CONTENT_URI = 1146 getContentUri("internal"); 1147 1148 /** 1149 * The content:// style URI for the "primary" external storage 1150 * volume. 1151 */ 1152 public static final Uri EXTERNAL_CONTENT_URI = 1153 getContentUri("external"); 1154 1155 /** 1156 * The MIME type for this table. 1157 */ 1158 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre"; 1159 1160 /** 1161 * The MIME type for entries in this table. 1162 */ 1163 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre"; 1164 1165 /** 1166 * The default sort order for this table 1167 */ 1168 public static final String DEFAULT_SORT_ORDER = NAME; 1169 1170 /** 1171 * Sub-directory of each genre containing all members. 1172 */ 1173 public static final class Members implements AudioColumns { 1174 1175 public static final Uri getContentUri(String volumeName, 1176 long genreId) { 1177 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1178 + "/audio/genres/" + genreId + "/members"); 1179 } 1180 1181 /** 1182 * A subdirectory of each genre containing all member audio files. 1183 */ 1184 public static final String CONTENT_DIRECTORY = "members"; 1185 1186 /** 1187 * The default sort order for this table 1188 */ 1189 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 1190 1191 /** 1192 * The ID of the audio file 1193 * <P>Type: INTEGER (long)</P> 1194 */ 1195 public static final String AUDIO_ID = "audio_id"; 1196 1197 /** 1198 * The ID of the genre 1199 * <P>Type: INTEGER (long)</P> 1200 */ 1201 public static final String GENRE_ID = "genre_id"; 1202 } 1203 } 1204 1205 /** 1206 * Columns representing a playlist 1207 */ 1208 public interface PlaylistsColumns { 1209 /** 1210 * The name of the playlist 1211 * <P>Type: TEXT</P> 1212 */ 1213 public static final String NAME = "name"; 1214 1215 /** 1216 * The data stream for the playlist file 1217 * <P>Type: DATA STREAM</P> 1218 */ 1219 public static final String DATA = "_data"; 1220 1221 /** 1222 * The time the file was added to the media provider 1223 * Units are seconds since 1970. 1224 * <P>Type: INTEGER (long)</P> 1225 */ 1226 public static final String DATE_ADDED = "date_added"; 1227 1228 /** 1229 * The time the file was last modified 1230 * Units are seconds since 1970. 1231 * NOTE: This is for internal use by the media scanner. Do not modify this field. 1232 * <P>Type: INTEGER (long)</P> 1233 */ 1234 public static final String DATE_MODIFIED = "date_modified"; 1235 } 1236 1237 /** 1238 * Contains playlists for audio files 1239 */ 1240 public static final class Playlists implements BaseColumns, 1241 PlaylistsColumns { 1242 /** 1243 * Get the content:// style URI for the audio playlists table on the 1244 * given volume. 1245 * 1246 * @param volumeName the name of the volume to get the URI for 1247 * @return the URI to the audio playlists table on the given volume 1248 */ 1249 public static Uri getContentUri(String volumeName) { 1250 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1251 "/audio/playlists"); 1252 } 1253 1254 /** 1255 * The content:// style URI for the internal storage. 1256 */ 1257 public static final Uri INTERNAL_CONTENT_URI = 1258 getContentUri("internal"); 1259 1260 /** 1261 * The content:// style URI for the "primary" external storage 1262 * volume. 1263 */ 1264 public static final Uri EXTERNAL_CONTENT_URI = 1265 getContentUri("external"); 1266 1267 /** 1268 * The MIME type for this table. 1269 */ 1270 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist"; 1271 1272 /** 1273 * The MIME type for entries in this table. 1274 */ 1275 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist"; 1276 1277 /** 1278 * The default sort order for this table 1279 */ 1280 public static final String DEFAULT_SORT_ORDER = NAME; 1281 1282 /** 1283 * Sub-directory of each playlist containing all members. 1284 */ 1285 public static final class Members implements AudioColumns { 1286 public static final Uri getContentUri(String volumeName, 1287 long playlistId) { 1288 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1289 + "/audio/playlists/" + playlistId + "/members"); 1290 } 1291 1292 /** 1293 * Convenience method to move a playlist item to a new location 1294 * @param res The content resolver to use 1295 * @param playlistId The numeric id of the playlist 1296 * @param from The position of the item to move 1297 * @param to The position to move the item to 1298 * @return true on success 1299 */ 1300 public static final boolean moveItem(ContentResolver res, 1301 long playlistId, int from, int to) { 1302 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 1303 playlistId) 1304 .buildUpon() 1305 .appendEncodedPath(String.valueOf(from)) 1306 .appendQueryParameter("move", "true") 1307 .build(); 1308 ContentValues values = new ContentValues(); 1309 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to); 1310 return res.update(uri, values, null, null) != 0; 1311 } 1312 1313 /** 1314 * The ID within the playlist. 1315 */ 1316 public static final String _ID = "_id"; 1317 1318 /** 1319 * A subdirectory of each playlist containing all member audio 1320 * files. 1321 */ 1322 public static final String CONTENT_DIRECTORY = "members"; 1323 1324 /** 1325 * The ID of the audio file 1326 * <P>Type: INTEGER (long)</P> 1327 */ 1328 public static final String AUDIO_ID = "audio_id"; 1329 1330 /** 1331 * The ID of the playlist 1332 * <P>Type: INTEGER (long)</P> 1333 */ 1334 public static final String PLAYLIST_ID = "playlist_id"; 1335 1336 /** 1337 * The order of the songs in the playlist 1338 * <P>Type: INTEGER (long)></P> 1339 */ 1340 public static final String PLAY_ORDER = "play_order"; 1341 1342 /** 1343 * The default sort order for this table 1344 */ 1345 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER; 1346 } 1347 } 1348 1349 /** 1350 * Columns representing an artist 1351 */ 1352 public interface ArtistColumns { 1353 /** 1354 * The artist who created the audio file, if any 1355 * <P>Type: TEXT</P> 1356 */ 1357 public static final String ARTIST = "artist"; 1358 1359 /** 1360 * A non human readable key calculated from the ARTIST, used for 1361 * searching, sorting and grouping 1362 * <P>Type: TEXT</P> 1363 */ 1364 public static final String ARTIST_KEY = "artist_key"; 1365 1366 /** 1367 * The number of albums in the database for this artist 1368 */ 1369 public static final String NUMBER_OF_ALBUMS = "number_of_albums"; 1370 1371 /** 1372 * The number of albums in the database for this artist 1373 */ 1374 public static final String NUMBER_OF_TRACKS = "number_of_tracks"; 1375 } 1376 1377 /** 1378 * Contains artists for audio files 1379 */ 1380 public static final class Artists implements BaseColumns, ArtistColumns { 1381 /** 1382 * Get the content:// style URI for the artists table on the 1383 * given volume. 1384 * 1385 * @param volumeName the name of the volume to get the URI for 1386 * @return the URI to the audio artists table on the given volume 1387 */ 1388 public static Uri getContentUri(String volumeName) { 1389 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1390 "/audio/artists"); 1391 } 1392 1393 /** 1394 * The content:// style URI for the internal storage. 1395 */ 1396 public static final Uri INTERNAL_CONTENT_URI = 1397 getContentUri("internal"); 1398 1399 /** 1400 * The content:// style URI for the "primary" external storage 1401 * volume. 1402 */ 1403 public static final Uri EXTERNAL_CONTENT_URI = 1404 getContentUri("external"); 1405 1406 /** 1407 * The MIME type for this table. 1408 */ 1409 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists"; 1410 1411 /** 1412 * The MIME type for entries in this table. 1413 */ 1414 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist"; 1415 1416 /** 1417 * The default sort order for this table 1418 */ 1419 public static final String DEFAULT_SORT_ORDER = ARTIST_KEY; 1420 1421 /** 1422 * Sub-directory of each artist containing all albums on which 1423 * a song by the artist appears. 1424 */ 1425 public static final class Albums implements AlbumColumns { 1426 public static final Uri getContentUri(String volumeName, 1427 long artistId) { 1428 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName 1429 + "/audio/artists/" + artistId + "/albums"); 1430 } 1431 } 1432 } 1433 1434 /** 1435 * Columns representing an album 1436 */ 1437 public interface AlbumColumns { 1438 1439 /** 1440 * The id for the album 1441 * <P>Type: INTEGER</P> 1442 */ 1443 public static final String ALBUM_ID = "album_id"; 1444 1445 /** 1446 * The album on which the audio file appears, if any 1447 * <P>Type: TEXT</P> 1448 */ 1449 public static final String ALBUM = "album"; 1450 1451 /** 1452 * The artist whose songs appear on this album 1453 * <P>Type: TEXT</P> 1454 */ 1455 public static final String ARTIST = "artist"; 1456 1457 /** 1458 * The number of songs on this album 1459 * <P>Type: INTEGER</P> 1460 */ 1461 public static final String NUMBER_OF_SONGS = "numsongs"; 1462 1463 /** 1464 * This column is available when getting album info via artist, 1465 * and indicates the number of songs on the album by the given 1466 * artist. 1467 * <P>Type: INTEGER</P> 1468 */ 1469 public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; 1470 1471 /** 1472 * The year in which the earliest songs 1473 * on this album were released. This will often 1474 * be the same as {@link #LAST_YEAR}, but for compilation albums 1475 * they might differ. 1476 * <P>Type: INTEGER</P> 1477 */ 1478 public static final String FIRST_YEAR = "minyear"; 1479 1480 /** 1481 * The year in which the latest songs 1482 * on this album were released. This will often 1483 * be the same as {@link #FIRST_YEAR}, but for compilation albums 1484 * they might differ. 1485 * <P>Type: INTEGER</P> 1486 */ 1487 public static final String LAST_YEAR = "maxyear"; 1488 1489 /** 1490 * A non human readable key calculated from the ALBUM, used for 1491 * searching, sorting and grouping 1492 * <P>Type: TEXT</P> 1493 */ 1494 public static final String ALBUM_KEY = "album_key"; 1495 1496 /** 1497 * Cached album art. 1498 * <P>Type: TEXT</P> 1499 */ 1500 public static final String ALBUM_ART = "album_art"; 1501 } 1502 1503 /** 1504 * Contains artists for audio files 1505 */ 1506 public static final class Albums implements BaseColumns, AlbumColumns { 1507 /** 1508 * Get the content:// style URI for the albums table on the 1509 * given volume. 1510 * 1511 * @param volumeName the name of the volume to get the URI for 1512 * @return the URI to the audio albums table on the given volume 1513 */ 1514 public static Uri getContentUri(String volumeName) { 1515 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1516 "/audio/albums"); 1517 } 1518 1519 /** 1520 * The content:// style URI for the internal storage. 1521 */ 1522 public static final Uri INTERNAL_CONTENT_URI = 1523 getContentUri("internal"); 1524 1525 /** 1526 * The content:// style URI for the "primary" external storage 1527 * volume. 1528 */ 1529 public static final Uri EXTERNAL_CONTENT_URI = 1530 getContentUri("external"); 1531 1532 /** 1533 * The MIME type for this table. 1534 */ 1535 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums"; 1536 1537 /** 1538 * The MIME type for entries in this table. 1539 */ 1540 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album"; 1541 1542 /** 1543 * The default sort order for this table 1544 */ 1545 public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; 1546 } 1547 } 1548 1549 public static final class Video { 1550 1551 /** 1552 * The default sort order for this table. 1553 */ 1554 public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME; 1555 1556 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 1557 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 1558 } 1559 1560 public interface VideoColumns extends MediaColumns { 1561 1562 /** 1563 * The duration of the video file, in ms 1564 * <P>Type: INTEGER (long)</P> 1565 */ 1566 public static final String DURATION = "duration"; 1567 1568 /** 1569 * The artist who created the video file, if any 1570 * <P>Type: TEXT</P> 1571 */ 1572 public static final String ARTIST = "artist"; 1573 1574 /** 1575 * The album the video file is from, if any 1576 * <P>Type: TEXT</P> 1577 */ 1578 public static final String ALBUM = "album"; 1579 1580 /** 1581 * The resolution of the video file, formatted as "XxY" 1582 * <P>Type: TEXT</P> 1583 */ 1584 public static final String RESOLUTION = "resolution"; 1585 1586 /** 1587 * The description of the video recording 1588 * <P>Type: TEXT</P> 1589 */ 1590 public static final String DESCRIPTION = "description"; 1591 1592 /** 1593 * Whether the video should be published as public or private 1594 * <P>Type: INTEGER</P> 1595 */ 1596 public static final String IS_PRIVATE = "isprivate"; 1597 1598 /** 1599 * The user-added tags associated with a video 1600 * <P>Type: TEXT</P> 1601 */ 1602 public static final String TAGS = "tags"; 1603 1604 /** 1605 * The YouTube category of the video 1606 * <P>Type: TEXT</P> 1607 */ 1608 public static final String CATEGORY = "category"; 1609 1610 /** 1611 * The language of the video 1612 * <P>Type: TEXT</P> 1613 */ 1614 public static final String LANGUAGE = "language"; 1615 1616 /** 1617 * The latitude where the image was captured. 1618 * <P>Type: DOUBLE</P> 1619 */ 1620 public static final String LATITUDE = "latitude"; 1621 1622 /** 1623 * The longitude where the image was captured. 1624 * <P>Type: DOUBLE</P> 1625 */ 1626 public static final String LONGITUDE = "longitude"; 1627 1628 /** 1629 * The date & time that the image was taken in units 1630 * of milliseconds since jan 1, 1970. 1631 * <P>Type: INTEGER</P> 1632 */ 1633 public static final String DATE_TAKEN = "datetaken"; 1634 1635 /** 1636 * The mini thumb id. 1637 * <P>Type: INTEGER</P> 1638 */ 1639 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 1640 1641 /** 1642 * The bucket id of the video. This is a read-only property that 1643 * is automatically computed from the DATA column. 1644 * <P>Type: TEXT</P> 1645 */ 1646 public static final String BUCKET_ID = "bucket_id"; 1647 1648 /** 1649 * The bucket display name 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_DISPLAY_NAME = "bucket_display_name"; 1654 1655 /** 1656 * The bookmark for the video. Time in ms. Represents the location in the video that the 1657 * video should start playing at the next time it is opened. If the value is null or 1658 * out of the range 0..DURATION-1 then the video should start playing from the 1659 * beginning. 1660 * <P>Type: INTEGER</P> 1661 */ 1662 public static final String BOOKMARK = "bookmark"; 1663 } 1664 1665 public static final class Media implements VideoColumns { 1666 /** 1667 * Get the content:// style URI for the video media table on the 1668 * given volume. 1669 * 1670 * @param volumeName the name of the volume to get the URI for 1671 * @return the URI to the video media table on the given volume 1672 */ 1673 public static Uri getContentUri(String volumeName) { 1674 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1675 "/video/media"); 1676 } 1677 1678 /** 1679 * The content:// style URI for the internal storage. 1680 */ 1681 public static final Uri INTERNAL_CONTENT_URI = 1682 getContentUri("internal"); 1683 1684 /** 1685 * The content:// style URI for the "primary" external storage 1686 * volume. 1687 */ 1688 public static final Uri EXTERNAL_CONTENT_URI = 1689 getContentUri("external"); 1690 1691 /** 1692 * The MIME type for this table. 1693 */ 1694 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 1695 1696 /** 1697 * The default sort order for this table 1698 */ 1699 public static final String DEFAULT_SORT_ORDER = TITLE; 1700 } 1701 1702 /** 1703 * This class allows developers to query and get two kinds of thumbnails: 1704 * MINI_KIND: 512 x 384 thumbnail 1705 * MICRO_KIND: 96 x 96 thumbnail 1706 * 1707 */ 1708 public static class Thumbnails implements BaseColumns { 1709 /** 1710 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 1711 * interrupted and return immediately. Only the original process which made the getThumbnail 1712 * requests can cancel their own requests. 1713 * 1714 * @param cr ContentResolver 1715 * @param origId original video id 1716 */ 1717 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 1718 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, 1719 InternalThumbnails.DEFAULT_GROUP_ID); 1720 } 1721 1722 /** 1723 * This method checks if the thumbnails of the specified image (origId) has been created. 1724 * It will be blocked until the thumbnails are generated. 1725 * 1726 * @param cr ContentResolver used to dispatch queries to MediaProvider. 1727 * @param origId Original image id associated with thumbnail of interest. 1728 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. 1729 * @param options this is only used for MINI_KIND when decoding the Bitmap 1730 * @return A Bitmap instance. It could be null if the original image 1731 * associated with origId doesn't exist or memory is not enough. 1732 */ 1733 public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, 1734 BitmapFactory.Options options) { 1735 return InternalThumbnails.getThumbnail(cr, origId, 1736 InternalThumbnails.DEFAULT_GROUP_ID, kind, options, 1737 EXTERNAL_CONTENT_URI, true); 1738 } 1739 1740 /** 1741 * This method checks if the thumbnails of the specified image (origId) has been created. 1742 * It will be blocked until the thumbnails are generated. 1743 * 1744 * @param cr ContentResolver used to dispatch queries to MediaProvider. 1745 * @param origId Original image id associated with thumbnail of interest. 1746 * @param groupId the id of group to which this request belongs 1747 * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND 1748 * @param options this is only used for MINI_KIND when decoding the Bitmap 1749 * @return A Bitmap instance. It could be null if the original image associated with 1750 * origId doesn't exist or memory is not enough. 1751 */ 1752 public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, 1753 int kind, BitmapFactory.Options options) { 1754 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options, 1755 EXTERNAL_CONTENT_URI, true); 1756 } 1757 1758 /** 1759 * This method cancels the thumbnail request so clients waiting for getThumbnail will be 1760 * interrupted and return immediately. Only the original process which made the getThumbnail 1761 * requests can cancel their own requests. 1762 * 1763 * @param cr ContentResolver 1764 * @param origId original video id 1765 * @param groupId the same groupId used in getThumbnail. 1766 */ 1767 public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { 1768 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId); 1769 } 1770 1771 /** 1772 * Get the content:// style URI for the image media table on the 1773 * given volume. 1774 * 1775 * @param volumeName the name of the volume to get the URI for 1776 * @return the URI to the image media table on the given volume 1777 */ 1778 public static Uri getContentUri(String volumeName) { 1779 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1780 "/video/thumbnails"); 1781 } 1782 1783 /** 1784 * The content:// style URI for the internal storage. 1785 */ 1786 public static final Uri INTERNAL_CONTENT_URI = 1787 getContentUri("internal"); 1788 1789 /** 1790 * The content:// style URI for the "primary" external storage 1791 * volume. 1792 */ 1793 public static final Uri EXTERNAL_CONTENT_URI = 1794 getContentUri("external"); 1795 1796 /** 1797 * The default sort order for this table 1798 */ 1799 public static final String DEFAULT_SORT_ORDER = "video_id ASC"; 1800 1801 /** 1802 * The data stream for the thumbnail 1803 * <P>Type: DATA STREAM</P> 1804 */ 1805 public static final String DATA = "_data"; 1806 1807 /** 1808 * The original image for the thumbnal 1809 * <P>Type: INTEGER (ID from Video table)</P> 1810 */ 1811 public static final String VIDEO_ID = "video_id"; 1812 1813 /** 1814 * The kind of the thumbnail 1815 * <P>Type: INTEGER (One of the values below)</P> 1816 */ 1817 public static final String KIND = "kind"; 1818 1819 public static final int MINI_KIND = 1; 1820 public static final int FULL_SCREEN_KIND = 2; 1821 public static final int MICRO_KIND = 3; 1822 1823 /** 1824 * The width of the thumbnal 1825 * <P>Type: INTEGER (long)</P> 1826 */ 1827 public static final String WIDTH = "width"; 1828 1829 /** 1830 * The height of the thumbnail 1831 * <P>Type: INTEGER (long)</P> 1832 */ 1833 public static final String HEIGHT = "height"; 1834 } 1835 } 1836 1837 /** 1838 * Uri for querying the state of the media scanner. 1839 */ 1840 public static Uri getMediaScannerUri() { 1841 return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner"); 1842 } 1843 1844 /** 1845 * Name of current volume being scanned by the media scanner. 1846 */ 1847 public static final String MEDIA_SCANNER_VOLUME = "volume"; 1848 1849 /** 1850 * Name of the file signaling the media scanner to ignore media in the containing directory 1851 * and its subdirectories. Developers should use this to avoid application graphics showing 1852 * up in the Gallery and likewise prevent application sounds and music from showing up in 1853 * the Music app. 1854 */ 1855 public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; 1856 } 1857