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