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