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 17package android.provider; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.content.ContentProviderClient; 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.UriPermission; 28import android.database.Cursor; 29import android.database.DatabaseUtils; 30import android.database.sqlite.SQLiteException; 31import android.graphics.Bitmap; 32import android.graphics.BitmapFactory; 33import android.graphics.Matrix; 34import android.media.MiniThumbFile; 35import android.media.ThumbnailUtils; 36import android.net.Uri; 37import android.os.Bundle; 38import android.os.Environment; 39import android.os.ParcelFileDescriptor; 40import android.os.RemoteException; 41import android.service.media.CameraPrewarmService; 42import android.util.Log; 43 44import libcore.io.IoUtils; 45 46import java.io.File; 47import java.io.FileInputStream; 48import java.io.FileNotFoundException; 49import java.io.IOException; 50import java.io.InputStream; 51import java.io.OutputStream; 52import java.util.Arrays; 53import 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 */ 59public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 { 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 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 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 */ 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 */ 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 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 */ 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 */ 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 { 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1449 public static Uri getContentUri(String volumeName) { 1450 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + 1451 "/audio/media"); 1452 } 1453 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 */ 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 */ 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 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 */ 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 { 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 */ 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 */ 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 { 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 */ 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. 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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