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