1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.provider; 18 19 import android.annotation.BytesLong; 20 import android.annotation.CurrentTimeMillisLong; 21 import android.annotation.CurrentTimeSecondsLong; 22 import android.annotation.DurationMillisLong; 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SdkConstant; 27 import android.annotation.SdkConstant.SdkConstantType; 28 import android.annotation.SuppressLint; 29 import android.annotation.SystemApi; 30 import android.annotation.TestApi; 31 import android.annotation.WorkerThread; 32 import android.app.Activity; 33 import android.app.PendingIntent; 34 import android.compat.annotation.UnsupportedAppUsage; 35 import android.content.ClipData; 36 import android.content.ContentProviderClient; 37 import android.content.ContentResolver; 38 import android.content.ContentUris; 39 import android.content.ContentValues; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.UriPermission; 43 import android.database.Cursor; 44 import android.graphics.Bitmap; 45 import android.graphics.BitmapFactory; 46 import android.graphics.ImageDecoder; 47 import android.graphics.PostProcessor; 48 import android.media.ExifInterface; 49 import android.media.MediaFormat; 50 import android.media.MediaMetadataRetriever; 51 import android.net.Uri; 52 import android.os.Bundle; 53 import android.os.CancellationSignal; 54 import android.os.Environment; 55 import android.os.OperationCanceledException; 56 import android.os.RemoteException; 57 import android.os.storage.StorageManager; 58 import android.os.storage.StorageVolume; 59 import android.text.TextUtils; 60 import android.util.ArrayMap; 61 import android.util.ArraySet; 62 import android.util.Log; 63 import android.util.Size; 64 65 import java.io.File; 66 import java.io.FileNotFoundException; 67 import java.io.IOException; 68 import java.io.InputStream; 69 import java.io.OutputStream; 70 import java.lang.annotation.Retention; 71 import java.lang.annotation.RetentionPolicy; 72 import java.text.Collator; 73 import java.util.ArrayList; 74 import java.util.Collection; 75 import java.util.Iterator; 76 import java.util.List; 77 import java.util.Locale; 78 import java.util.Objects; 79 import java.util.Set; 80 import java.util.regex.Pattern; 81 82 /** 83 * The contract between the media provider and applications. Contains 84 * definitions for the supported URIs and columns. 85 * <p> 86 * The media provider provides an indexed collection of common media types, such 87 * as {@link Audio}, {@link Video}, and {@link Images}, from any attached 88 * storage devices. Each collection is organized based on the primary MIME type 89 * of the underlying content; for example, {@code image/*} content is indexed 90 * under {@link Images}. The {@link Files} collection provides a broad view 91 * across all collections, and does not filter by MIME type. 92 */ 93 public final class MediaStore { 94 private final static String TAG = "MediaStore"; 95 96 /** The authority for the media provider */ 97 public static final String AUTHORITY = "media"; 98 /** A content:// style uri to the authority for the media provider */ 99 public static final @NonNull Uri AUTHORITY_URI = 100 Uri.parse("content://" + AUTHORITY); 101 102 /** 103 * The authority for a legacy instance of the media provider, before it was 104 * converted into a Mainline module. When initializing for the first time, 105 * the Mainline module will connect to this legacy instance to migrate 106 * important user settings, such as {@link BaseColumns#_ID}, 107 * {@link MediaColumns#IS_FAVORITE}, and more. 108 * <p> 109 * The legacy instance is expected to meet the exact same API contract 110 * expressed here in {@link MediaStore}, to facilitate smooth data 111 * migrations. Interactions that would normally interact with 112 * {@link #AUTHORITY} can be redirected to work with the legacy instance 113 * using {@link #rewriteToLegacy(Uri)}. 114 * 115 * @hide 116 */ 117 @SystemApi 118 public static final String AUTHORITY_LEGACY = "media_legacy"; 119 /** 120 * @see #AUTHORITY_LEGACY 121 * @hide 122 */ 123 @SystemApi 124 public static final @NonNull Uri AUTHORITY_LEGACY_URI = 125 Uri.parse("content://" + AUTHORITY_LEGACY); 126 127 /** 128 * Synthetic volume name that provides a view of all content across the 129 * "internal" storage of the device. 130 * <p> 131 * This synthetic volume provides a merged view of all media distributed 132 * with the device, such as built-in ringtones and wallpapers. 133 * <p> 134 * Because this is a synthetic volume, you can't insert new content into 135 * this volume. 136 */ 137 public static final String VOLUME_INTERNAL = "internal"; 138 139 /** 140 * Synthetic volume name that provides a view of all content across the 141 * "external" storage of the device. 142 * <p> 143 * This synthetic volume provides a merged view of all media across all 144 * currently attached external storage devices. 145 * <p> 146 * Because this is a synthetic volume, you can't insert new content into 147 * this volume. Instead, you can insert content into a specific storage 148 * volume obtained from {@link #getExternalVolumeNames(Context)}. 149 */ 150 public static final String VOLUME_EXTERNAL = "external"; 151 152 /** 153 * Specific volume name that represents the primary external storage device 154 * at {@link Environment#getExternalStorageDirectory()}. 155 * <p> 156 * This volume may not always be available, such as when the user has 157 * ejected the device. You can find a list of all specific volume names 158 * using {@link #getExternalVolumeNames(Context)}. 159 */ 160 public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary"; 161 162 /** {@hide} */ 163 public static final String VOLUME_DEMO = "demo"; 164 165 /** {@hide} */ 166 public static final String RESOLVE_PLAYLIST_MEMBERS_CALL = "resolve_playlist_members"; 167 /** {@hide} */ 168 public static final String RUN_IDLE_MAINTENANCE_CALL = "run_idle_maintenance"; 169 /** {@hide} */ 170 public static final String WAIT_FOR_IDLE_CALL = "wait_for_idle"; 171 /** {@hide} */ 172 public static final String SCAN_FILE_CALL = "scan_file"; 173 /** {@hide} */ 174 public static final String SCAN_VOLUME_CALL = "scan_volume"; 175 /** {@hide} */ 176 public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request"; 177 /** {@hide} */ 178 public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request"; 179 /** {@hide} */ 180 public static final String CREATE_FAVORITE_REQUEST_CALL = "create_favorite_request"; 181 /** {@hide} */ 182 public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request"; 183 184 /** {@hide} */ 185 public static final String GET_VERSION_CALL = "get_version"; 186 /** {@hide} */ 187 public static final String GET_GENERATION_CALL = "get_generation"; 188 189 /** {@hide} */ 190 public static final String START_LEGACY_MIGRATION_CALL = "start_legacy_migration"; 191 /** {@hide} */ 192 public static final String FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration"; 193 194 /** {@hide} */ 195 @Deprecated 196 public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = 197 "com.android.externalstorage.documents"; 198 199 /** {@hide} */ 200 public static final String GET_DOCUMENT_URI_CALL = "get_document_uri"; 201 /** {@hide} */ 202 public static final String GET_MEDIA_URI_CALL = "get_media_uri"; 203 204 /** {@hide} */ 205 public static final String EXTRA_URI = "uri"; 206 /** {@hide} */ 207 public static final String EXTRA_URI_PERMISSIONS = "uriPermissions"; 208 209 /** {@hide} */ 210 public static final String EXTRA_CLIP_DATA = "clip_data"; 211 /** {@hide} */ 212 public static final String EXTRA_CONTENT_VALUES = "content_values"; 213 /** {@hide} */ 214 public static final String EXTRA_RESULT = "result"; 215 216 /** 217 * This is for internal use by the media scanner only. 218 * Name of the (optional) Uri parameter that determines whether to skip deleting 219 * the file pointed to by the _data column, when deleting the database entry. 220 * The only appropriate value for this parameter is "false", in which case the 221 * delete will be skipped. Note especially that setting this to true, or omitting 222 * the parameter altogether, will perform the default action, which is different 223 * for different types of media. 224 * @hide 225 */ 226 public static final String PARAM_DELETE_DATA = "deletedata"; 227 228 /** {@hide} */ 229 public static final String PARAM_INCLUDE_PENDING = "includePending"; 230 /** {@hide} */ 231 public static final String PARAM_PROGRESS = "progress"; 232 /** {@hide} */ 233 public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal"; 234 /** {@hide} */ 235 public static final String PARAM_LIMIT = "limit"; 236 237 /** 238 * Activity Action: Launch a music player. 239 * The activity should be able to play, browse, or manipulate music files stored on the device. 240 * 241 * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead. 242 */ 243 @Deprecated 244 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 245 public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER"; 246 247 /** 248 * Activity Action: Perform a search for media. 249 * Contains at least the {@link android.app.SearchManager#QUERY} extra. 250 * May also contain any combination of the following extras: 251 * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS 252 * 253 * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST 254 * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM 255 * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE 256 * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS 257 */ 258 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 259 public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH"; 260 261 /** 262 * An intent to perform a search for music media and automatically play content from the 263 * result when possible. This can be fired, for example, by the result of a voice recognition 264 * command to listen to music. 265 * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} 266 * and {@link android.app.SearchManager#QUERY} extras. The 267 * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and 268 * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode. 269 * For more information about the search modes for this intent, see 270 * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based 271 * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common 272 * Intents</a>.</p> 273 * 274 * <p>This intent makes the most sense for apps that can support large-scale search of music, 275 * such as services connected to an online database of music which can be streamed and played 276 * on the device.</p> 277 */ 278 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 279 public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = 280 "android.media.action.MEDIA_PLAY_FROM_SEARCH"; 281 282 /** 283 * An intent to perform a search for readable media and automatically play content from the 284 * result when possible. This can be fired, for example, by the result of a voice recognition 285 * command to read a book or magazine. 286 * <p> 287 * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can 288 * contain any type of unstructured text search, like the name of a book or magazine, an author 289 * a genre, a publisher, or any combination of these. 290 * <p> 291 * Because this intent includes an open-ended unstructured search string, it makes the most 292 * sense for apps that can support large-scale search of text media, such as services connected 293 * to an online database of books and/or magazines which can be read on the device. 294 */ 295 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 296 public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH = 297 "android.media.action.TEXT_OPEN_FROM_SEARCH"; 298 299 /** 300 * An intent to perform a search for video media and automatically play content from the 301 * result when possible. This can be fired, for example, by the result of a voice recognition 302 * command to play movies. 303 * <p> 304 * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can 305 * contain any type of unstructured video search, like the name of a movie, one or more actors, 306 * a genre, or any combination of these. 307 * <p> 308 * Because this intent includes an open-ended unstructured search string, it makes the most 309 * sense for apps that can support large-scale search of video, such as services connected to an 310 * online database of videos which can be streamed and played on the device. 311 */ 312 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 313 public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH = 314 "android.media.action.VIDEO_PLAY_FROM_SEARCH"; 315 316 /** 317 * The name of the Intent-extra used to define the artist 318 */ 319 public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist"; 320 /** 321 * The name of the Intent-extra used to define the album 322 */ 323 public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album"; 324 /** 325 * The name of the Intent-extra used to define the song title 326 */ 327 public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title"; 328 /** 329 * The name of the Intent-extra used to define the genre. 330 */ 331 public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre"; 332 /** 333 * The name of the Intent-extra used to define the playlist. 334 */ 335 public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist"; 336 /** 337 * The name of the Intent-extra used to define the radio channel. 338 */ 339 public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel"; 340 /** 341 * The name of the Intent-extra used to define the search focus. The search focus 342 * indicates whether the search should be for things related to the artist, album 343 * or song that is identified by the other extras. 344 */ 345 public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus"; 346 347 /** 348 * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView. 349 * This is an int property that overrides the activity's requestedOrientation. 350 * @see android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED 351 */ 352 public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation"; 353 354 /** 355 * The name of an Intent-extra used to control the UI of a ViewImage. 356 * This is a boolean property that overrides the activity's default fullscreen state. 357 */ 358 public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; 359 360 /** 361 * The name of an Intent-extra used to control the UI of a ViewImage. 362 * This is a boolean property that specifies whether or not to show action icons. 363 */ 364 public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons"; 365 366 /** 367 * The name of the Intent-extra used to control the onCompletion behavior of a MovieView. 368 * This is a boolean property that specifies whether or not to finish the MovieView activity 369 * when the movie completes playing. The default value is true, which means to automatically 370 * exit the movie player activity when the movie completes playing. 371 */ 372 public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion"; 373 374 /** 375 * The name of the Intent action used to launch a camera in still image mode. 376 */ 377 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 378 public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; 379 380 /** 381 * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or 382 * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm 383 * service. 384 * <p> 385 * This meta-data should reference the fully qualified class name of the prewarm service 386 * extending {@code CameraPrewarmService}. 387 * <p> 388 * The prewarm service will get bound and receive a prewarm signal 389 * {@code CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent. 390 * An application implementing a prewarm service should do the absolute minimum amount of work 391 * to initialize the camera in order to reduce startup time in likely case that shortly after a 392 * camera launch intent would be sent. 393 */ 394 public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = 395 "android.media.still_image_camera_preview_service"; 396 397 /** 398 * Name under which an activity handling {@link #ACTION_REVIEW} or 399 * {@link #ACTION_REVIEW_SECURE} publishes the service name for its prewarm 400 * service. 401 * <p> 402 * This meta-data should reference the fully qualified class name of the prewarm service 403 * <p> 404 * The prewarm service can be bound before starting {@link #ACTION_REVIEW} or 405 * {@link #ACTION_REVIEW_SECURE}. 406 * An application implementing this prewarm service should do the absolute minimum amount of 407 * work to initialize its resources to efficiently handle an {@link #ACTION_REVIEW} or 408 * {@link #ACTION_REVIEW_SECURE} in the near future. 409 */ 410 public static final java.lang.String META_DATA_REVIEW_GALLERY_PREWARM_SERVICE = 411 "android.media.review_gallery_prewarm_service"; 412 413 /** 414 * The name of the Intent action used to launch a camera in still image mode 415 * for use when the device is secured (e.g. with a pin, password, pattern, 416 * or face unlock). Applications responding to this intent must not expose 417 * any personal content like existing photos or videos on the device. The 418 * applications should be careful not to share any photo or video with other 419 * applications or internet. The activity should use {@link 420 * Activity#setShowWhenLocked} to display 421 * on top of the lock screen while secured. There is no activity stack when 422 * this flag is used, so launching more than one activity is strongly 423 * discouraged. 424 */ 425 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 426 public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 427 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 428 429 /** 430 * The name of the Intent action used to launch a camera in video mode. 431 */ 432 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 433 public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA"; 434 435 /** 436 * Standard Intent action that can be sent to have the camera application 437 * capture an image and return it. 438 * <p> 439 * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. 440 * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap 441 * object in the extra field. This is useful for applications that only need a small image. 442 * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri 443 * value of EXTRA_OUTPUT. 444 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 445 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 446 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 447 * If you don't set a ClipData, it will be copied there for you when calling 448 * {@link Context#startActivity(Intent)}. 449 * 450 * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above 451 * and declares as using the {@link android.Manifest.permission#CAMERA} permission which 452 * is not granted, then attempting to use this action will result in a {@link 453 * java.lang.SecurityException}. 454 * 455 * @see #EXTRA_OUTPUT 456 */ 457 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 458 public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; 459 460 /** 461 * Intent action that can be sent to have the camera application capture an image and return 462 * it when the device is secured (e.g. with a pin, password, pattern, or face unlock). 463 * Applications responding to this intent must not expose any personal content like existing 464 * photos or videos on the device. The applications should be careful not to share any photo 465 * or video with other applications or Internet. The activity should use {@link 466 * Activity#setShowWhenLocked} to display on top of the 467 * lock screen while secured. There is no activity stack when this flag is used, so 468 * launching more than one activity is strongly discouraged. 469 * <p> 470 * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. 471 * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap 472 * object in the extra field. This is useful for applications that only need a small image. 473 * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri 474 * value of EXTRA_OUTPUT. 475 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 476 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 477 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 478 * If you don't set a ClipData, it will be copied there for you when calling 479 * {@link Context#startActivity(Intent)}. 480 * 481 * @see #ACTION_IMAGE_CAPTURE 482 * @see #EXTRA_OUTPUT 483 */ 484 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 485 public static final String ACTION_IMAGE_CAPTURE_SECURE = 486 "android.media.action.IMAGE_CAPTURE_SECURE"; 487 488 /** 489 * Standard Intent action that can be sent to have the camera application 490 * capture a video and return it. 491 * <p> 492 * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality. 493 * <p> 494 * The caller may pass in an extra EXTRA_OUTPUT to control 495 * where the video is written. If EXTRA_OUTPUT is not present the video will be 496 * written to the standard location for videos, and the Uri of that location will be 497 * returned in the data field of the Uri. 498 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 499 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 500 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 501 * If you don't set a ClipData, it will be copied there for you when calling 502 * {@link Context#startActivity(Intent)}. 503 * 504 * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above 505 * and declares as using the {@link android.Manifest.permission#CAMERA} permission which 506 * is not granted, then atempting to use this action will result in a {@link 507 * java.lang.SecurityException}. 508 * 509 * @see #EXTRA_OUTPUT 510 * @see #EXTRA_VIDEO_QUALITY 511 * @see #EXTRA_SIZE_LIMIT 512 * @see #EXTRA_DURATION_LIMIT 513 */ 514 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 515 public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; 516 517 /** 518 * Standard action that can be sent to review the given media file. 519 * <p> 520 * The launched application is expected to provide a large-scale view of the 521 * given media file, while allowing the user to quickly access other 522 * recently captured media files. 523 * <p> 524 * Input: {@link Intent#getData} is URI of the primary media item to 525 * initially display. 526 * 527 * @see #ACTION_REVIEW_SECURE 528 * @see #EXTRA_BRIGHTNESS 529 */ 530 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 531 public final static String ACTION_REVIEW = "android.provider.action.REVIEW"; 532 533 /** 534 * Standard action that can be sent to review the given media file when the 535 * device is secured (e.g. with a pin, password, pattern, or face unlock). 536 * The applications should be careful not to share any media with other 537 * applications or Internet. The activity should use 538 * {@link Activity#setShowWhenLocked} to display on top of the lock screen 539 * while secured. There is no activity stack when this flag is used, so 540 * launching more than one activity is strongly discouraged. 541 * <p> 542 * The launched application is expected to provide a large-scale view of the 543 * given primary media file, while only allowing the user to quickly access 544 * other media from an explicit secondary list. 545 * <p> 546 * Input: {@link Intent#getData} is URI of the primary media item to 547 * initially display. {@link Intent#getClipData} is the limited list of 548 * secondary media items that the user is allowed to review. If 549 * {@link Intent#getClipData} is undefined, then no other media access 550 * should be allowed. 551 * 552 * @see #EXTRA_BRIGHTNESS 553 */ 554 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 555 public final static String ACTION_REVIEW_SECURE = "android.provider.action.REVIEW_SECURE"; 556 557 /** 558 * When defined, the launched application is requested to set the given 559 * brightness value via 560 * {@link android.view.WindowManager.LayoutParams#screenBrightness} to help 561 * ensure a smooth transition when launching {@link #ACTION_REVIEW} or 562 * {@link #ACTION_REVIEW_SECURE} intents. 563 */ 564 public final static String EXTRA_BRIGHTNESS = "android.provider.extra.BRIGHTNESS"; 565 566 /** 567 * The name of the Intent-extra used to control the quality of a recorded video. This is an 568 * integer property. Currently value 0 means low quality, suitable for MMS messages, and 569 * value 1 means high quality. In the future other quality levels may be added. 570 */ 571 public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; 572 573 /** 574 * Specify the maximum allowed size. 575 */ 576 public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit"; 577 578 /** 579 * Specify the maximum allowed recording duration in seconds. 580 */ 581 public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit"; 582 583 /** 584 * The name of the Intent-extra used to indicate a content resolver Uri to be used to 585 * store the requested image or video. 586 */ 587 public final static String EXTRA_OUTPUT = "output"; 588 589 /** 590 * The string that is used when a media attribute is not known. For example, 591 * if an audio file does not have any meta data, the artist and album columns 592 * will be set to this value. 593 */ 594 public static final String UNKNOWN_STRING = "<unknown>"; 595 596 /** 597 * Specify a {@link Uri} that is "related" to the current operation being 598 * performed. 599 * <p> 600 * This is typically used to allow an operation that may normally be 601 * rejected, such as making a copy of a pre-existing image located under a 602 * {@link MediaColumns#RELATIVE_PATH} where new images are not allowed. 603 * <p> 604 * It's strongly recommended that when making a copy of pre-existing content 605 * that you define the "original document ID" GUID as defined by the <em>XMP 606 * Media Management</em> standard. 607 * <p> 608 * This key can be placed in a {@link Bundle} of extras and passed to 609 * {@link ContentResolver#insert}. 610 */ 611 public static final String QUERY_ARG_RELATED_URI = "android:query-arg-related-uri"; 612 613 /** 614 * Flag that can be used to enable movement of media items on disk through 615 * {@link ContentResolver#update} calls. This is typically true for 616 * third-party apps, but false for system components. 617 * 618 * @hide 619 */ 620 public static final String QUERY_ARG_ALLOW_MOVEMENT = "android:query-arg-allow-movement"; 621 622 /** 623 * Specify how {@link MediaColumns#IS_PENDING} items should be filtered when 624 * performing a {@link MediaStore} operation. 625 * <p> 626 * This key can be placed in a {@link Bundle} of extras and passed to 627 * {@link ContentResolver#query}, {@link ContentResolver#update}, or 628 * {@link ContentResolver#delete}. 629 * <p> 630 * By default, pending items are filtered away from operations. 631 */ 632 @Match 633 public static final String QUERY_ARG_MATCH_PENDING = "android:query-arg-match-pending"; 634 635 /** 636 * Specify how {@link MediaColumns#IS_TRASHED} items should be filtered when 637 * performing a {@link MediaStore} operation. 638 * <p> 639 * This key can be placed in a {@link Bundle} of extras and passed to 640 * {@link ContentResolver#query}, {@link ContentResolver#update}, or 641 * {@link ContentResolver#delete}. 642 * <p> 643 * By default, trashed items are filtered away from operations. 644 * 645 * @see MediaColumns#IS_TRASHED 646 * @see MediaStore#QUERY_ARG_MATCH_TRASHED 647 * @see MediaStore#createTrashRequest 648 */ 649 @Match 650 public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed"; 651 652 /** 653 * Specify how {@link MediaColumns#IS_FAVORITE} items should be filtered 654 * when performing a {@link MediaStore} operation. 655 * <p> 656 * This key can be placed in a {@link Bundle} of extras and passed to 657 * {@link ContentResolver#query}, {@link ContentResolver#update}, or 658 * {@link ContentResolver#delete}. 659 * <p> 660 * By default, favorite items are <em>not</em> filtered away from 661 * operations. 662 * 663 * @see MediaColumns#IS_FAVORITE 664 * @see MediaStore#QUERY_ARG_MATCH_FAVORITE 665 * @see MediaStore#createFavoriteRequest 666 */ 667 @Match 668 public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite"; 669 670 /** @hide */ 671 @IntDef(flag = true, prefix = { "MATCH_" }, value = { 672 MATCH_DEFAULT, 673 MATCH_INCLUDE, 674 MATCH_EXCLUDE, 675 MATCH_ONLY, 676 }) 677 @Retention(RetentionPolicy.SOURCE) 678 public @interface Match {} 679 680 /** 681 * Value indicating that the default matching behavior should be used, as 682 * defined by the key documentation. 683 */ 684 public static final int MATCH_DEFAULT = 0; 685 686 /** 687 * Value indicating that operations should include items matching the 688 * criteria defined by this key. 689 * <p> 690 * Note that items <em>not</em> matching the criteria <em>may</em> also be 691 * included depending on the default behavior documented by the key. If you 692 * want to operate exclusively on matching items, use {@link #MATCH_ONLY}. 693 */ 694 public static final int MATCH_INCLUDE = 1; 695 696 /** 697 * Value indicating that operations should exclude items matching the 698 * criteria defined by this key. 699 */ 700 public static final int MATCH_EXCLUDE = 2; 701 702 /** 703 * Value indicating that operations should only operate on items explicitly 704 * matching the criteria defined by this key. 705 */ 706 public static final int MATCH_ONLY = 3; 707 708 /** 709 * Update the given {@link Uri} to also include any pending media items from 710 * calls such as 711 * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}. 712 * By default no pending items are returned. 713 * 714 * @see MediaColumns#IS_PENDING 715 * @deprecated consider migrating to {@link #QUERY_ARG_MATCH_PENDING} which 716 * is more expressive. 717 */ 718 @Deprecated setIncludePending(@onNull Uri uri)719 public static @NonNull Uri setIncludePending(@NonNull Uri uri) { 720 return setIncludePending(uri.buildUpon()).build(); 721 } 722 723 /** @hide */ 724 @Deprecated setIncludePending(@onNull Uri.Builder uriBuilder)725 public static @NonNull Uri.Builder setIncludePending(@NonNull Uri.Builder uriBuilder) { 726 return uriBuilder.appendQueryParameter(PARAM_INCLUDE_PENDING, "1"); 727 } 728 729 /** @hide */ 730 @Deprecated getIncludePending(@onNull Uri uri)731 public static boolean getIncludePending(@NonNull Uri uri) { 732 return uri.getBooleanQueryParameter(MediaStore.PARAM_INCLUDE_PENDING, false); 733 } 734 735 /** 736 * Update the given {@link Uri} to indicate that the caller requires the 737 * original file contents when calling 738 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 739 * <p> 740 * This can be useful when the caller wants to ensure they're backing up the 741 * exact bytes of the underlying media, without any Exif redaction being 742 * performed. 743 * <p> 744 * If the original file contents cannot be provided, a 745 * {@link UnsupportedOperationException} will be thrown when the returned 746 * {@link Uri} is used, such as when the caller doesn't hold 747 * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}. 748 * 749 * @see MediaStore#getRequireOriginal(Uri) 750 */ setRequireOriginal(@onNull Uri uri)751 public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) { 752 return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build(); 753 } 754 755 /** 756 * Return if the caller requires the original file contents when calling 757 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 758 * 759 * @see MediaStore#setRequireOriginal(Uri) 760 */ getRequireOriginal(@onNull Uri uri)761 public static boolean getRequireOriginal(@NonNull Uri uri) { 762 return uri.getBooleanQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL, false); 763 } 764 765 /** 766 * Rewrite the given {@link Uri} to point at 767 * {@link MediaStore#AUTHORITY_LEGACY}. 768 * 769 * @see #AUTHORITY_LEGACY 770 * @hide 771 */ 772 @SystemApi rewriteToLegacy(@onNull Uri uri)773 public static @NonNull Uri rewriteToLegacy(@NonNull Uri uri) { 774 return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build(); 775 } 776 777 /** 778 * Called by the Mainline module to signal to {@link #AUTHORITY_LEGACY} that 779 * data migration is starting. 780 * 781 * @hide 782 */ startLegacyMigration(@onNull ContentResolver resolver, @NonNull String volumeName)783 public static void startLegacyMigration(@NonNull ContentResolver resolver, 784 @NonNull String volumeName) { 785 try { 786 resolver.call(AUTHORITY_LEGACY, START_LEGACY_MIGRATION_CALL, volumeName, null); 787 } catch (Exception e) { 788 Log.wtf(TAG, "Failed to deliver legacy migration event", e); 789 } 790 } 791 792 /** 793 * Called by the Mainline module to signal to {@link #AUTHORITY_LEGACY} that 794 * data migration is finished. The legacy provider may choose to perform 795 * clean-up operations at this point, such as deleting databases. 796 * 797 * @hide 798 */ finishLegacyMigration(@onNull ContentResolver resolver, @NonNull String volumeName)799 public static void finishLegacyMigration(@NonNull ContentResolver resolver, 800 @NonNull String volumeName) { 801 try { 802 resolver.call(AUTHORITY_LEGACY, FINISH_LEGACY_MIGRATION_CALL, volumeName, null); 803 } catch (Exception e) { 804 Log.wtf(TAG, "Failed to deliver legacy migration event", e); 805 } 806 } 807 createRequest(@onNull ContentResolver resolver, @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values)808 private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver, 809 @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) { 810 Objects.requireNonNull(resolver); 811 Objects.requireNonNull(uris); 812 813 final Iterator<Uri> it = uris.iterator(); 814 final ClipData clipData = ClipData.newRawUri(null, it.next()); 815 while (it.hasNext()) { 816 clipData.addItem(new ClipData.Item(it.next())); 817 } 818 819 final Bundle extras = new Bundle(); 820 extras.putParcelable(EXTRA_CLIP_DATA, clipData); 821 extras.putParcelable(EXTRA_CONTENT_VALUES, values); 822 return resolver.call(AUTHORITY, method, null, extras).getParcelable(EXTRA_RESULT); 823 } 824 825 /** 826 * Create a {@link PendingIntent} that will prompt the user to grant your 827 * app write access for the requested media items. 828 * <p> 829 * This call only generates the request for a prompt; to display the prompt, 830 * call {@link Activity#startIntentSenderForResult} with 831 * {@link PendingIntent#getIntentSender()}. You can then determine if the 832 * user granted your request by testing for {@link Activity#RESULT_OK} in 833 * {@link Activity#onActivityResult}. The requested operation will have 834 * completely finished before this activity result is delivered. 835 * <p> 836 * Permissions granted through this mechanism are tied to the lifecycle of 837 * the {@link Activity} that requests them. If you need to retain 838 * longer-term access for background actions, you can place items into a 839 * {@link ClipData} or {@link Intent} which can then be passed to 840 * {@link Context#startService} or 841 * {@link android.app.job.JobInfo.Builder#setClipData}. Be sure to include 842 * any relevant access modes you want to retain, such as 843 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. 844 * <p> 845 * The displayed prompt will reflect all the media items you're requesting, 846 * including those for which you already hold write access. If you want to 847 * determine if you already hold write access before requesting access, use 848 * {@code ContentResolver#checkUriPermission(Uri, int, int)} with 849 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. 850 * <p> 851 * For security and performance reasons this method does not support 852 * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or 853 * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}. 854 * <p> 855 * The write access granted through this request is general-purpose, and 856 * once obtained you can directly {@link ContentResolver#update} columns 857 * like {@link MediaColumns#IS_FAVORITE}, {@link MediaColumns#IS_TRASHED}, 858 * or {@link ContentResolver#delete}. 859 * 860 * @param resolver Used to connect with {@link MediaStore#AUTHORITY}. 861 * Typically this value is {@link Context#getContentResolver()}, 862 * but if you need more explicit lifecycle controls, you can 863 * obtain a {@link ContentProviderClient} and wrap it using 864 * {@link ContentResolver#wrap(ContentProviderClient)}. 865 * @param uris The set of media items to include in this request. Each item 866 * must be hosted by {@link MediaStore#AUTHORITY} and must 867 * reference a specific media item by {@link BaseColumns#_ID}. 868 */ createWriteRequest(@onNull ContentResolver resolver, @NonNull Collection<Uri> uris)869 public static @NonNull PendingIntent createWriteRequest(@NonNull ContentResolver resolver, 870 @NonNull Collection<Uri> uris) { 871 return createRequest(resolver, CREATE_WRITE_REQUEST_CALL, uris, null); 872 } 873 874 /** 875 * Create a {@link PendingIntent} that will prompt the user to trash the 876 * requested media items. When the user approves this request, 877 * {@link MediaColumns#IS_TRASHED} is set on these items. 878 * <p> 879 * This call only generates the request for a prompt; to display the prompt, 880 * call {@link Activity#startIntentSenderForResult} with 881 * {@link PendingIntent#getIntentSender()}. You can then determine if the 882 * user granted your request by testing for {@link Activity#RESULT_OK} in 883 * {@link Activity#onActivityResult}. The requested operation will have 884 * completely finished before this activity result is delivered. 885 * <p> 886 * The displayed prompt will reflect all the media items you're requesting, 887 * including those for which you already hold write access. If you want to 888 * determine if you already hold write access before requesting access, use 889 * {@code ContentResolver#checkUriPermission(Uri, int, int)} with 890 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. 891 * 892 * @param resolver Used to connect with {@link MediaStore#AUTHORITY}. 893 * Typically this value is {@link Context#getContentResolver()}, 894 * but if you need more explicit lifecycle controls, you can 895 * obtain a {@link ContentProviderClient} and wrap it using 896 * {@link ContentResolver#wrap(ContentProviderClient)}. 897 * @param uris The set of media items to include in this request. Each item 898 * must be hosted by {@link MediaStore#AUTHORITY} and must 899 * reference a specific media item by {@link BaseColumns#_ID}. 900 * @param value The {@link MediaColumns#IS_TRASHED} value to apply. 901 * @see MediaColumns#IS_TRASHED 902 * @see MediaStore#QUERY_ARG_MATCH_TRASHED 903 */ createTrashRequest(@onNull ContentResolver resolver, @NonNull Collection<Uri> uris, boolean value)904 public static @NonNull PendingIntent createTrashRequest(@NonNull ContentResolver resolver, 905 @NonNull Collection<Uri> uris, boolean value) { 906 final ContentValues values = new ContentValues(); 907 if (value) { 908 values.put(MediaColumns.IS_TRASHED, 1); 909 } else { 910 values.put(MediaColumns.IS_TRASHED, 0); 911 } 912 return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values); 913 } 914 915 /** 916 * Create a {@link PendingIntent} that will prompt the user to favorite the 917 * requested media items. When the user approves this request, 918 * {@link MediaColumns#IS_FAVORITE} is set on these items. 919 * <p> 920 * This call only generates the request for a prompt; to display the prompt, 921 * call {@link Activity#startIntentSenderForResult} with 922 * {@link PendingIntent#getIntentSender()}. You can then determine if the 923 * user granted your request by testing for {@link Activity#RESULT_OK} in 924 * {@link Activity#onActivityResult}. The requested operation will have 925 * completely finished before this activity result is delivered. 926 * <p> 927 * The displayed prompt will reflect all the media items you're requesting, 928 * including those for which you already hold write access. If you want to 929 * determine if you already hold write access before requesting access, use 930 * {@code ContentResolver#checkUriPermission(Uri, int, int)} with 931 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. 932 * 933 * @param resolver Used to connect with {@link MediaStore#AUTHORITY}. 934 * Typically this value is {@link Context#getContentResolver()}, 935 * but if you need more explicit lifecycle controls, you can 936 * obtain a {@link ContentProviderClient} and wrap it using 937 * {@link ContentResolver#wrap(ContentProviderClient)}. 938 * @param uris The set of media items to include in this request. Each item 939 * must be hosted by {@link MediaStore#AUTHORITY} and must 940 * reference a specific media item by {@link BaseColumns#_ID}. 941 * @param value The {@link MediaColumns#IS_FAVORITE} value to apply. 942 * @see MediaColumns#IS_FAVORITE 943 * @see MediaStore#QUERY_ARG_MATCH_FAVORITE 944 */ createFavoriteRequest(@onNull ContentResolver resolver, @NonNull Collection<Uri> uris, boolean value)945 public static @NonNull PendingIntent createFavoriteRequest(@NonNull ContentResolver resolver, 946 @NonNull Collection<Uri> uris, boolean value) { 947 final ContentValues values = new ContentValues(); 948 if (value) { 949 values.put(MediaColumns.IS_FAVORITE, 1); 950 } else { 951 values.put(MediaColumns.IS_FAVORITE, 0); 952 } 953 return createRequest(resolver, CREATE_FAVORITE_REQUEST_CALL, uris, values); 954 } 955 956 /** 957 * Create a {@link PendingIntent} that will prompt the user to permanently 958 * delete the requested media items. When the user approves this request, 959 * {@link ContentResolver#delete} will be called on these items. 960 * <p> 961 * This call only generates the request for a prompt; to display the prompt, 962 * call {@link Activity#startIntentSenderForResult} with 963 * {@link PendingIntent#getIntentSender()}. You can then determine if the 964 * user granted your request by testing for {@link Activity#RESULT_OK} in 965 * {@link Activity#onActivityResult}. The requested operation will have 966 * completely finished before this activity result is delivered. 967 * <p> 968 * The displayed prompt will reflect all the media items you're requesting, 969 * including those for which you already hold write access. If you want to 970 * determine if you already hold write access before requesting access, use 971 * {@code ContentResolver#checkUriPermission(Uri, int, int)} with 972 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. 973 * 974 * @param resolver Used to connect with {@link MediaStore#AUTHORITY}. 975 * Typically this value is {@link Context#getContentResolver()}, 976 * but if you need more explicit lifecycle controls, you can 977 * obtain a {@link ContentProviderClient} and wrap it using 978 * {@link ContentResolver#wrap(ContentProviderClient)}. 979 * @param uris The set of media items to include in this request. Each item 980 * must be hosted by {@link MediaStore#AUTHORITY} and must 981 * reference a specific media item by {@link BaseColumns#_ID}. 982 */ createDeleteRequest(@onNull ContentResolver resolver, @NonNull Collection<Uri> uris)983 public static @NonNull PendingIntent createDeleteRequest(@NonNull ContentResolver resolver, 984 @NonNull Collection<Uri> uris) { 985 return createRequest(resolver, CREATE_DELETE_REQUEST_CALL, uris, null); 986 } 987 988 /** 989 * Common media metadata columns. 990 */ 991 public interface MediaColumns extends BaseColumns { 992 /** 993 * Absolute filesystem path to the media item on disk. 994 * <p> 995 * On Android 11, you can use this value when you access an existing 996 * file using direct file paths. That's because this value has a valid 997 * file path. However, don't assume that the file is always available. 998 * Be prepared to handle any file-based I/O errors that could occur. 999 * <p> 1000 * Don't use this value when you create or update a media file, even 1001 * if you're on Android 11 and are using direct file paths. Instead, 1002 * use the values of the {@link #DISPLAY_NAME} and 1003 * {@link #RELATIVE_PATH} columns. 1004 * <p> 1005 * Note that apps may not have filesystem permissions to directly access 1006 * this path. Instead of trying to open this path directly, apps should 1007 * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 1008 * access. 1009 * 1010 * @deprecated Apps may not have filesystem permissions to directly 1011 * access this path. Instead of trying to open this path 1012 * directly, apps should use 1013 * {@link ContentResolver#openFileDescriptor(Uri, String)} 1014 * to gain access. 1015 */ 1016 @Deprecated 1017 @Column(Cursor.FIELD_TYPE_STRING) 1018 public static final String DATA = "_data"; 1019 1020 /** 1021 * Indexed value of {@link File#length()} extracted from this media 1022 * item. 1023 */ 1024 @BytesLong 1025 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1026 public static final String SIZE = "_size"; 1027 1028 /** 1029 * The display name of the media item. 1030 * <p> 1031 * For example, an item stored at 1032 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 1033 * display name of {@code IMG1024.JPG}. 1034 */ 1035 @Column(Cursor.FIELD_TYPE_STRING) 1036 public static final String DISPLAY_NAME = "_display_name"; 1037 1038 /** 1039 * The time the media item was first added. 1040 */ 1041 @CurrentTimeSecondsLong 1042 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1043 public static final String DATE_ADDED = "date_added"; 1044 1045 /** 1046 * Indexed value of {@link File#lastModified()} extracted from this 1047 * media item. 1048 */ 1049 @CurrentTimeSecondsLong 1050 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1051 public static final String DATE_MODIFIED = "date_modified"; 1052 1053 /** 1054 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DATE} or 1055 * {@link ExifInterface#TAG_DATETIME_ORIGINAL} extracted from this media 1056 * item. 1057 * <p> 1058 * Note that images must define both 1059 * {@link ExifInterface#TAG_DATETIME_ORIGINAL} and 1060 * {@code ExifInterface#TAG_OFFSET_TIME_ORIGINAL} to reliably determine 1061 * this value in relation to the epoch. 1062 */ 1063 @CurrentTimeMillisLong 1064 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1065 public static final String DATE_TAKEN = "datetaken"; 1066 1067 /** 1068 * The MIME type of the media item. 1069 * <p> 1070 * This is typically defined based on the file extension of the media 1071 * item. However, it may be the value of the {@code format} attribute 1072 * defined by the <em>Dublin Core Media Initiative</em> standard, 1073 * extracted from any XMP metadata contained within this media item. 1074 * <p class="note"> 1075 * Note: the {@code format} attribute may be ignored if the top-level 1076 * MIME type disagrees with the file extension. For example, it's 1077 * reasonable for an {@code image/jpeg} file to declare a {@code format} 1078 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 1079 * {@code format} of {@code audio/ogg} would be ignored. 1080 * <p> 1081 * This is a read-only column that is automatically computed. 1082 */ 1083 @Column(Cursor.FIELD_TYPE_STRING) 1084 public static final String MIME_TYPE = "mime_type"; 1085 1086 /** 1087 * Flag indicating if a media item is DRM protected. 1088 */ 1089 @Column(Cursor.FIELD_TYPE_INTEGER) 1090 public static final String IS_DRM = "is_drm"; 1091 1092 /** 1093 * Flag indicating if a media item is pending, and still being inserted 1094 * by its owner. While this flag is set, only the owner of the item can 1095 * open the underlying file; requests from other apps will be rejected. 1096 * <p> 1097 * Pending items are retained either until they are published by setting 1098 * the field to {@code 0}, or until they expire as defined by 1099 * {@link #DATE_EXPIRES}. 1100 * 1101 * @see MediaStore#QUERY_ARG_MATCH_PENDING 1102 */ 1103 @Column(Cursor.FIELD_TYPE_INTEGER) 1104 public static final String IS_PENDING = "is_pending"; 1105 1106 /** 1107 * Flag indicating if a media item is trashed. 1108 * <p> 1109 * Trashed items are retained until they expire as defined by 1110 * {@link #DATE_EXPIRES}. 1111 * 1112 * @see MediaColumns#IS_TRASHED 1113 * @see MediaStore#QUERY_ARG_MATCH_TRASHED 1114 * @see MediaStore#createTrashRequest 1115 */ 1116 @Column(Cursor.FIELD_TYPE_INTEGER) 1117 public static final String IS_TRASHED = "is_trashed"; 1118 1119 /** 1120 * The time the media item should be considered expired. Typically only 1121 * meaningful in the context of {@link #IS_PENDING} or 1122 * {@link #IS_TRASHED}. 1123 * <p> 1124 * The value stored in this column is automatically calculated when 1125 * {@link #IS_PENDING} or {@link #IS_TRASHED} is changed. The default 1126 * pending expiration is typically 7 days, and the default trashed 1127 * expiration is typically 30 days. 1128 * <p> 1129 * Expired media items are automatically deleted once their expiration 1130 * time has passed, typically during during the next device idle period. 1131 */ 1132 @CurrentTimeSecondsLong 1133 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1134 public static final String DATE_EXPIRES = "date_expires"; 1135 1136 /** 1137 * Indexed value of 1138 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_WIDTH}, 1139 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_WIDTH} or 1140 * {@link ExifInterface#TAG_IMAGE_WIDTH} extracted from this media item. 1141 */ 1142 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1143 public static final String WIDTH = "width"; 1144 1145 /** 1146 * Indexed value of 1147 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_HEIGHT}, 1148 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_HEIGHT} or 1149 * {@link ExifInterface#TAG_IMAGE_LENGTH} extracted from this media 1150 * item. 1151 */ 1152 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1153 public static final String HEIGHT = "height"; 1154 1155 /** 1156 * Calculated value that combines {@link #WIDTH} and {@link #HEIGHT} 1157 * into a user-presentable string. 1158 */ 1159 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1160 public static final String RESOLUTION = "resolution"; 1161 1162 /** 1163 * Package name that contributed this media. The value may be 1164 * {@code NULL} if ownership cannot be reliably determined. 1165 */ 1166 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1167 public static final String OWNER_PACKAGE_NAME = "owner_package_name"; 1168 1169 /** 1170 * Volume name of the specific storage device where this media item is 1171 * persisted. The value is typically one of the volume names returned 1172 * from {@link MediaStore#getExternalVolumeNames(Context)}. 1173 * <p> 1174 * This is a read-only column that is automatically computed. 1175 */ 1176 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1177 public static final String VOLUME_NAME = "volume_name"; 1178 1179 /** 1180 * Relative path of this media item within the storage device where it 1181 * is persisted. For example, an item stored at 1182 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 1183 * path of {@code DCIM/Vacation/}. 1184 * <p> 1185 * This value should only be used for organizational purposes, and you 1186 * should not attempt to construct or access a raw filesystem path using 1187 * this value. If you need to open a media item, use an API like 1188 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 1189 * <p> 1190 * When this value is set to {@code NULL} during an 1191 * {@link ContentResolver#insert} operation, the newly created item will 1192 * be placed in a relevant default location based on the type of media 1193 * being inserted. For example, a {@code image/jpeg} item will be placed 1194 * under {@link Environment#DIRECTORY_PICTURES}. 1195 * <p> 1196 * You can modify this column during an {@link ContentResolver#update} 1197 * call, which will move the underlying file on disk. 1198 * <p> 1199 * In both cases above, content must be placed under a top-level 1200 * directory that is relevant to the media type. For example, attempting 1201 * to place a {@code audio/mpeg} file under 1202 * {@link Environment#DIRECTORY_PICTURES} will be rejected. 1203 */ 1204 @Column(Cursor.FIELD_TYPE_STRING) 1205 public static final String RELATIVE_PATH = "relative_path"; 1206 1207 /** 1208 * The primary bucket ID of this media item. This can be useful to 1209 * present the user a first-level clustering of related media items. 1210 * This is a read-only column that is automatically computed. 1211 */ 1212 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1213 public static final String BUCKET_ID = "bucket_id"; 1214 1215 /** 1216 * The primary bucket display name of this media item. This can be 1217 * useful to present the user a first-level clustering of related 1218 * media items. This is a read-only column that is automatically 1219 * computed. 1220 */ 1221 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1222 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1223 1224 /** 1225 * The group ID of this media item. This can be useful to present 1226 * the user a grouping of related media items, such a burst of 1227 * images, or a {@code JPG} and {@code DNG} version of the same 1228 * image. 1229 * <p> 1230 * This is a read-only column that is automatically computed based 1231 * on the first portion of the filename. For example, 1232 * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG} 1233 * will have the same {@link #GROUP_ID} because the first portion of 1234 * their filenames is identical. 1235 * 1236 * @removed 1237 */ 1238 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1239 @Deprecated 1240 public static final String GROUP_ID = "group_id"; 1241 1242 /** 1243 * The "document ID" GUID as defined by the <em>XMP Media 1244 * Management</em> standard, extracted from any XMP metadata contained 1245 * within this media item. The value is {@code null} when no metadata 1246 * was found. 1247 * <p> 1248 * Each "document ID" is created once for each new resource. Different 1249 * renditions of that resource are expected to have different IDs. 1250 */ 1251 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1252 public static final String DOCUMENT_ID = "document_id"; 1253 1254 /** 1255 * The "instance ID" GUID as defined by the <em>XMP Media 1256 * Management</em> standard, extracted from any XMP metadata contained 1257 * within this media item. The value is {@code null} when no metadata 1258 * was found. 1259 * <p> 1260 * This "instance ID" changes with each save operation of a specific 1261 * "document ID". 1262 */ 1263 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1264 public static final String INSTANCE_ID = "instance_id"; 1265 1266 /** 1267 * The "original document ID" GUID as defined by the <em>XMP Media 1268 * Management</em> standard, extracted from any XMP metadata contained 1269 * within this media item. 1270 * <p> 1271 * This "original document ID" links a resource to its original source. 1272 * For example, when you save a PSD document as a JPEG, then convert the 1273 * JPEG to GIF format, the "original document ID" of both the JPEG and 1274 * GIF files is the "document ID" of the original PSD file. 1275 */ 1276 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1277 public static final String ORIGINAL_DOCUMENT_ID = "original_document_id"; 1278 1279 /** 1280 * Indexed value of 1281 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_ROTATION}, 1282 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_ROTATION}, or 1283 * {@link ExifInterface#TAG_ORIENTATION} extracted from this media item. 1284 * <p> 1285 * For consistency the indexed value is expressed in degrees, such as 0, 1286 * 90, 180, or 270. 1287 */ 1288 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1289 public static final String ORIENTATION = "orientation"; 1290 1291 /** 1292 * Flag indicating if the media item has been marked as being a 1293 * "favorite" by the user. 1294 * 1295 * @see MediaColumns#IS_FAVORITE 1296 * @see MediaStore#QUERY_ARG_MATCH_FAVORITE 1297 * @see MediaStore#createFavoriteRequest 1298 */ 1299 @Column(Cursor.FIELD_TYPE_INTEGER) 1300 public static final String IS_FAVORITE = "is_favorite"; 1301 1302 /** 1303 * Flag indicating if the media item has been marked as being part of 1304 * the {@link Downloads} collection. 1305 */ 1306 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1307 public static final String IS_DOWNLOAD = "is_download"; 1308 1309 /** 1310 * Generation number at which metadata for this media item was first 1311 * inserted. This is useful for apps that are attempting to quickly 1312 * identify exactly which media items have been added since a previous 1313 * point in time. Generation numbers are monotonically increasing over 1314 * time, and can be safely arithmetically compared. 1315 * <p> 1316 * Detecting media additions using generation numbers is more robust 1317 * than using {@link #DATE_ADDED}, since those values may change in 1318 * unexpected ways when apps use {@link File#setLastModified(long)} or 1319 * when the system clock is set incorrectly. 1320 * <p> 1321 * Note that before comparing these detailed generation values, you 1322 * should first confirm that the overall version hasn't changed by 1323 * checking {@link MediaStore#getVersion(Context, String)}, since that 1324 * indicates when a more radical change has occurred. If the overall 1325 * version changes, you should assume that generation numbers have been 1326 * reset and perform a full synchronization pass. 1327 * 1328 * @see MediaStore#getGeneration(Context, String) 1329 */ 1330 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1331 public static final String GENERATION_ADDED = "generation_added"; 1332 1333 /** 1334 * Generation number at which metadata for this media item was last 1335 * changed. This is useful for apps that are attempting to quickly 1336 * identify exactly which media items have changed since a previous 1337 * point in time. Generation numbers are monotonically increasing over 1338 * time, and can be safely arithmetically compared. 1339 * <p> 1340 * Detecting media changes using generation numbers is more robust than 1341 * using {@link #DATE_MODIFIED}, since those values may change in 1342 * unexpected ways when apps use {@link File#setLastModified(long)} or 1343 * when the system clock is set incorrectly. 1344 * <p> 1345 * Note that before comparing these detailed generation values, you 1346 * should first confirm that the overall version hasn't changed by 1347 * checking {@link MediaStore#getVersion(Context, String)}, since that 1348 * indicates when a more radical change has occurred. If the overall 1349 * version changes, you should assume that generation numbers have been 1350 * reset and perform a full synchronization pass. 1351 * 1352 * @see MediaStore#getGeneration(Context, String) 1353 */ 1354 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1355 public static final String GENERATION_MODIFIED = "generation_modified"; 1356 1357 /** 1358 * Indexed XMP metadata extracted from this media item. 1359 * <p> 1360 * The structure of this metadata is defined by the <a href= 1361 * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform"><em>XMP 1362 * Media Management</em> standard</a>, published as ISO 16684-1:2012. 1363 * <p> 1364 * This metadata is typically extracted from a 1365 * {@link ExifInterface#TAG_XMP} contained inside an image file or from 1366 * a {@code XMP_} box contained inside an ISO/IEC base media file format 1367 * (MPEG-4 Part 12). 1368 * <p> 1369 * Note that any location details are redacted from this metadata for 1370 * privacy reasons. 1371 */ 1372 @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true) 1373 public static final String XMP = "xmp"; 1374 1375 // ======================================= 1376 // ==== MediaMetadataRetriever values ==== 1377 // ======================================= 1378 1379 /** 1380 * Indexed value of 1381 * {@link MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER} extracted 1382 * from this media item. 1383 */ 1384 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1385 public static final String CD_TRACK_NUMBER = "cd_track_number"; 1386 1387 /** 1388 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUM} 1389 * extracted from this media item. 1390 */ 1391 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1392 public static final String ALBUM = "album"; 1393 1394 /** 1395 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ARTIST} 1396 * or {@link ExifInterface#TAG_ARTIST} extracted from this media item. 1397 */ 1398 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1399 public static final String ARTIST = "artist"; 1400 1401 /** 1402 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_AUTHOR} 1403 * extracted from this media item. 1404 */ 1405 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1406 public static final String AUTHOR = "author"; 1407 1408 /** 1409 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPOSER} 1410 * extracted from this media item. 1411 */ 1412 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1413 public static final String COMPOSER = "composer"; 1414 1415 // METADATA_KEY_DATE is DATE_TAKEN 1416 1417 /** 1418 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_GENRE} 1419 * extracted from this media item. 1420 */ 1421 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1422 public static final String GENRE = "genre"; 1423 1424 /** 1425 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_TITLE} 1426 * extracted from this media item. 1427 */ 1428 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1429 public static final String TITLE = "title"; 1430 1431 /** 1432 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_YEAR} 1433 * extracted from this media item. 1434 */ 1435 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1436 public static final String YEAR = "year"; 1437 1438 /** 1439 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DURATION} 1440 * extracted from this media item. 1441 */ 1442 @DurationMillisLong 1443 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1444 public static final String DURATION = "duration"; 1445 1446 /** 1447 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_NUM_TRACKS} 1448 * extracted from this media item. 1449 */ 1450 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1451 public static final String NUM_TRACKS = "num_tracks"; 1452 1453 /** 1454 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_WRITER} 1455 * extracted from this media item. 1456 */ 1457 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1458 public static final String WRITER = "writer"; 1459 1460 // METADATA_KEY_MIMETYPE is MIME_TYPE 1461 1462 /** 1463 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST} 1464 * extracted from this media item. 1465 */ 1466 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1467 public static final String ALBUM_ARTIST = "album_artist"; 1468 1469 /** 1470 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER} 1471 * extracted from this media item. 1472 */ 1473 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1474 public static final String DISC_NUMBER = "disc_number"; 1475 1476 /** 1477 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPILATION} 1478 * extracted from this media item. 1479 */ 1480 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1481 public static final String COMPILATION = "compilation"; 1482 1483 // HAS_AUDIO is ignored 1484 // HAS_VIDEO is ignored 1485 // VIDEO_WIDTH is WIDTH 1486 // VIDEO_HEIGHT is HEIGHT 1487 1488 /** 1489 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_BITRATE} 1490 * extracted from this media item. 1491 */ 1492 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1493 public static final String BITRATE = "bitrate"; 1494 1495 // TIMED_TEXT_LANGUAGES is ignored 1496 // IS_DRM is ignored 1497 // LOCATION is LATITUDE and LONGITUDE 1498 // VIDEO_ROTATION is ORIENTATION 1499 1500 /** 1501 * Indexed value of 1502 * {@link MediaMetadataRetriever#METADATA_KEY_CAPTURE_FRAMERATE} 1503 * extracted from this media item. 1504 */ 1505 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1506 public static final String CAPTURE_FRAMERATE = "capture_framerate"; 1507 1508 // HAS_IMAGE is ignored 1509 // IMAGE_COUNT is ignored 1510 // IMAGE_PRIMARY is ignored 1511 // IMAGE_WIDTH is WIDTH 1512 // IMAGE_HEIGHT is HEIGHT 1513 // IMAGE_ROTATION is ORIENTATION 1514 // VIDEO_FRAME_COUNT is ignored 1515 // EXIF_OFFSET is ignored 1516 // EXIF_LENGTH is ignored 1517 // COLOR_STANDARD is ignored 1518 // COLOR_TRANSFER is ignored 1519 // COLOR_RANGE is ignored 1520 // SAMPLERATE is ignored 1521 // BITS_PER_SAMPLE is ignored 1522 } 1523 1524 /** 1525 * Media provider table containing an index of all files in the media storage, 1526 * including non-media files. This should be used by applications that work with 1527 * non-media file types (text, HTML, PDF, etc) as well as applications that need to 1528 * work with multiple media file types in a single query. 1529 */ 1530 public static final class Files { 1531 /** @hide */ 1532 public static final String TABLE = "files"; 1533 1534 /** @hide */ 1535 public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL); 1536 1537 /** 1538 * Get the content:// style URI for the files table on the 1539 * given volume. 1540 * 1541 * @param volumeName the name of the volume to get the URI for 1542 * @return the URI to the files table on the given volume 1543 */ getContentUri(String volumeName)1544 public static Uri getContentUri(String volumeName) { 1545 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("file").build(); 1546 } 1547 1548 /** 1549 * Get the content:// style URI for a single row in the files table on the 1550 * given volume. 1551 * 1552 * @param volumeName the name of the volume to get the URI for 1553 * @param rowId the file to get the URI for 1554 * @return the URI to the files table on the given volume 1555 */ getContentUri(String volumeName, long rowId)1556 public static final Uri getContentUri(String volumeName, 1557 long rowId) { 1558 return ContentUris.withAppendedId(getContentUri(volumeName), rowId); 1559 } 1560 1561 /** {@hide} */ 1562 @UnsupportedAppUsage getMtpObjectsUri(@onNull String volumeName)1563 public static Uri getMtpObjectsUri(@NonNull String volumeName) { 1564 return MediaStore.Files.getContentUri(volumeName); 1565 } 1566 1567 /** {@hide} */ 1568 @UnsupportedAppUsage getMtpObjectsUri(@onNull String volumeName, long fileId)1569 public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) { 1570 return MediaStore.Files.getContentUri(volumeName, fileId); 1571 } 1572 1573 /** {@hide} */ 1574 @UnsupportedAppUsage getMtpReferencesUri(@onNull String volumeName, long fileId)1575 public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) { 1576 return MediaStore.Files.getContentUri(volumeName, fileId); 1577 } 1578 1579 /** 1580 * Used to trigger special logic for directories. 1581 * @hide 1582 */ getDirectoryUri(String volumeName)1583 public static final Uri getDirectoryUri(String volumeName) { 1584 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build(); 1585 } 1586 1587 /** @hide */ getContentUriForPath(String path)1588 public static final Uri getContentUriForPath(String path) { 1589 return getContentUri(getVolumeName(new File(path))); 1590 } 1591 1592 /** 1593 * File metadata columns. 1594 */ 1595 public interface FileColumns extends MediaColumns { 1596 /** 1597 * The MTP storage ID of the file 1598 * @hide 1599 */ 1600 @UnsupportedAppUsage 1601 @Deprecated 1602 // @Column(Cursor.FIELD_TYPE_INTEGER) 1603 public static final String STORAGE_ID = "storage_id"; 1604 1605 /** 1606 * The MTP format code of the file 1607 * @hide 1608 */ 1609 @UnsupportedAppUsage 1610 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1611 public static final String FORMAT = "format"; 1612 1613 /** 1614 * The index of the parent directory of the file 1615 */ 1616 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1617 public static final String PARENT = "parent"; 1618 1619 /** 1620 * The MIME type of the media item. 1621 * <p> 1622 * This is typically defined based on the file extension of the media 1623 * item. However, it may be the value of the {@code format} attribute 1624 * defined by the <em>Dublin Core Media Initiative</em> standard, 1625 * extracted from any XMP metadata contained within this media item. 1626 * <p class="note"> 1627 * Note: the {@code format} attribute may be ignored if the top-level 1628 * MIME type disagrees with the file extension. For example, it's 1629 * reasonable for an {@code image/jpeg} file to declare a {@code format} 1630 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 1631 * {@code format} of {@code audio/ogg} would be ignored. 1632 * <p> 1633 * This is a read-only column that is automatically computed. 1634 */ 1635 @Column(Cursor.FIELD_TYPE_STRING) 1636 public static final String MIME_TYPE = "mime_type"; 1637 1638 /** @removed promoted to parent interface */ 1639 public static final String TITLE = "title"; 1640 1641 /** 1642 * The media type (audio, video, image, document, playlist or subtitle) 1643 * of the file, or 0 for not a media file 1644 */ 1645 @Column(Cursor.FIELD_TYPE_INTEGER) 1646 public static final String MEDIA_TYPE = "media_type"; 1647 1648 /** 1649 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1650 * is not an audio, image, video, document, playlist, or subtitles file. 1651 */ 1652 public static final int MEDIA_TYPE_NONE = 0; 1653 1654 /** 1655 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1656 * is an image file. 1657 */ 1658 public static final int MEDIA_TYPE_IMAGE = 1; 1659 1660 /** 1661 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1662 * is an audio file. 1663 */ 1664 public static final int MEDIA_TYPE_AUDIO = 2; 1665 1666 /** 1667 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1668 * is a video file. 1669 */ 1670 public static final int MEDIA_TYPE_VIDEO = 3; 1671 1672 /** 1673 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1674 * is a playlist file. 1675 */ 1676 public static final int MEDIA_TYPE_PLAYLIST = 4; 1677 1678 /** 1679 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1680 * is a subtitles or lyrics file. 1681 */ 1682 public static final int MEDIA_TYPE_SUBTITLE = 5; 1683 1684 /** 1685 * Constant for the {@link #MEDIA_TYPE} column indicating that file is a document file. 1686 */ 1687 public static final int MEDIA_TYPE_DOCUMENT = 6; 1688 } 1689 } 1690 1691 /** @hide */ 1692 public static class ThumbnailConstants { 1693 public static final int MINI_KIND = 1; 1694 public static final int FULL_SCREEN_KIND = 2; 1695 public static final int MICRO_KIND = 3; 1696 1697 public static final Size MINI_SIZE = new Size(512, 384); 1698 public static final Size FULL_SCREEN_SIZE = new Size(1024, 786); 1699 public static final Size MICRO_SIZE = new Size(96, 96); 1700 getKindSize(int kind)1701 public static @NonNull Size getKindSize(int kind) { 1702 if (kind == ThumbnailConstants.MICRO_KIND) { 1703 return ThumbnailConstants.MICRO_SIZE; 1704 } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { 1705 return ThumbnailConstants.FULL_SCREEN_SIZE; 1706 } else if (kind == ThumbnailConstants.MINI_KIND) { 1707 return ThumbnailConstants.MINI_SIZE; 1708 } else { 1709 throw new IllegalArgumentException("Unsupported kind: " + kind); 1710 } 1711 } 1712 } 1713 1714 /** 1715 * Download metadata columns. 1716 */ 1717 public interface DownloadColumns extends MediaColumns { 1718 /** 1719 * Uri indicating where the item has been downloaded from. 1720 */ 1721 @Column(Cursor.FIELD_TYPE_STRING) 1722 String DOWNLOAD_URI = "download_uri"; 1723 1724 /** 1725 * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}. 1726 */ 1727 @Column(Cursor.FIELD_TYPE_STRING) 1728 String REFERER_URI = "referer_uri"; 1729 1730 /** 1731 * The description of the download. 1732 * 1733 * @removed 1734 */ 1735 @Deprecated 1736 @Column(Cursor.FIELD_TYPE_STRING) 1737 String DESCRIPTION = "description"; 1738 } 1739 1740 /** 1741 * Collection of downloaded items. 1742 */ 1743 public static final class Downloads implements DownloadColumns { Downloads()1744 private Downloads() {} 1745 1746 /** 1747 * The content:// style URI for the internal storage. 1748 */ 1749 @NonNull 1750 public static final Uri INTERNAL_CONTENT_URI = 1751 getContentUri("internal"); 1752 1753 /** 1754 * The content:// style URI for the "primary" external storage 1755 * volume. 1756 */ 1757 @NonNull 1758 public static final Uri EXTERNAL_CONTENT_URI = 1759 getContentUri("external"); 1760 1761 /** 1762 * The MIME type for this table. 1763 */ 1764 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download"; 1765 1766 /** 1767 * Get the content:// style URI for the downloads table on the 1768 * given volume. 1769 * 1770 * @param volumeName the name of the volume to get the URI for 1771 * @return the URI to the image media table on the given volume 1772 */ getContentUri(@onNull String volumeName)1773 public static @NonNull Uri getContentUri(@NonNull String volumeName) { 1774 return AUTHORITY_URI.buildUpon().appendPath(volumeName) 1775 .appendPath("downloads").build(); 1776 } 1777 1778 /** 1779 * Get the content:// style URI for a single row in the downloads table 1780 * on the given volume. 1781 * 1782 * @param volumeName the name of the volume to get the URI for 1783 * @param id the download to get the URI for 1784 * @return the URI to the downloads table on the given volume 1785 */ getContentUri(@onNull String volumeName, long id)1786 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 1787 return ContentUris.withAppendedId(getContentUri(volumeName), id); 1788 } 1789 1790 /** @hide */ getContentUriForPath(@onNull String path)1791 public static @NonNull Uri getContentUriForPath(@NonNull String path) { 1792 return getContentUri(getVolumeName(new File(path))); 1793 } 1794 } 1795 1796 /** 1797 * @deprecated since this method doesn't have a {@link Context}, we can't 1798 * find the actual {@link StorageVolume} for the given path, so 1799 * only a vague guess is returned. Callers should use 1800 * {@link StorageManager#getStorageVolume(File)} instead. 1801 * @hide 1802 */ 1803 @Deprecated getVolumeName(@onNull File path)1804 public static @NonNull String getVolumeName(@NonNull File path) { 1805 // Ideally we'd find the relevant StorageVolume, but we don't have a 1806 // Context to obtain it from, so the best we can do is assume 1807 if (path.getAbsolutePath() 1808 .startsWith(Environment.getStorageDirectory().getAbsolutePath())) { 1809 return MediaStore.VOLUME_EXTERNAL; 1810 } else { 1811 return MediaStore.VOLUME_INTERNAL; 1812 } 1813 } 1814 1815 /** 1816 * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended 1817 * to be accessed elsewhere. 1818 */ 1819 @Deprecated 1820 private static class InternalThumbnails implements BaseColumns { 1821 /** 1822 * Currently outstanding thumbnail requests that can be cancelled. 1823 */ 1824 // @GuardedBy("sPending") 1825 private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>(); 1826 1827 /** 1828 * Make a blocking request to obtain the given thumbnail, generating it 1829 * if needed. 1830 * 1831 * @see #cancelThumbnail(ContentResolver, Uri) 1832 */ 1833 @Deprecated getThumbnail(@onNull ContentResolver cr, @NonNull Uri uri, int kind, @Nullable BitmapFactory.Options opts)1834 static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri, 1835 int kind, @Nullable BitmapFactory.Options opts) { 1836 final Size size = ThumbnailConstants.getKindSize(kind); 1837 1838 CancellationSignal signal = null; 1839 synchronized (sPending) { 1840 signal = sPending.get(uri); 1841 if (signal == null) { 1842 signal = new CancellationSignal(); 1843 sPending.put(uri, signal); 1844 } 1845 } 1846 1847 try { 1848 return cr.loadThumbnail(uri, size, signal); 1849 } catch (IOException e) { 1850 Log.w(TAG, "Failed to obtain thumbnail for " + uri, e); 1851 return null; 1852 } finally { 1853 synchronized (sPending) { 1854 sPending.remove(uri); 1855 } 1856 } 1857 } 1858 1859 /** 1860 * This method cancels the thumbnail request so clients waiting for 1861 * {@link #getThumbnail} will be interrupted and return immediately. 1862 * Only the original process which made the request can cancel their own 1863 * requests. 1864 */ 1865 @Deprecated cancelThumbnail(@onNull ContentResolver cr, @NonNull Uri uri)1866 static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) { 1867 synchronized (sPending) { 1868 final CancellationSignal signal = sPending.get(uri); 1869 if (signal != null) { 1870 signal.cancel(); 1871 } 1872 } 1873 } 1874 } 1875 1876 /** 1877 * Collection of all media with MIME type of {@code image/*}. 1878 */ 1879 public static final class Images { 1880 /** 1881 * Image metadata columns. 1882 */ 1883 public interface ImageColumns extends MediaColumns { 1884 /** 1885 * The picasa id of the image 1886 * 1887 * @deprecated this value was only relevant for images hosted on 1888 * Picasa, which are no longer supported. 1889 */ 1890 @Deprecated 1891 @Column(Cursor.FIELD_TYPE_STRING) 1892 public static final String PICASA_ID = "picasa_id"; 1893 1894 /** 1895 * Whether the video should be published as public or private 1896 */ 1897 @Column(Cursor.FIELD_TYPE_INTEGER) 1898 public static final String IS_PRIVATE = "isprivate"; 1899 1900 /** 1901 * The latitude where the image was captured. 1902 * 1903 * @deprecated location details are no longer indexed for privacy 1904 * reasons, and this value is now always {@code null}. 1905 * You can still manually obtain location metadata using 1906 * {@link ExifInterface#getLatLong(float[])}. 1907 */ 1908 @Deprecated 1909 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1910 public static final String LATITUDE = "latitude"; 1911 1912 /** 1913 * The longitude where the image was captured. 1914 * 1915 * @deprecated location details are no longer indexed for privacy 1916 * reasons, and this value is now always {@code null}. 1917 * You can still manually obtain location metadata using 1918 * {@link ExifInterface#getLatLong(float[])}. 1919 */ 1920 @Deprecated 1921 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1922 public static final String LONGITUDE = "longitude"; 1923 1924 /** @removed promoted to parent interface */ 1925 public static final String DATE_TAKEN = "datetaken"; 1926 /** @removed promoted to parent interface */ 1927 public static final String ORIENTATION = "orientation"; 1928 1929 /** 1930 * The mini thumb id. 1931 * 1932 * @deprecated all thumbnails should be obtained via 1933 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 1934 * value is no longer supported. 1935 */ 1936 @Deprecated 1937 @Column(Cursor.FIELD_TYPE_INTEGER) 1938 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 1939 1940 /** @removed promoted to parent interface */ 1941 public static final String BUCKET_ID = "bucket_id"; 1942 /** @removed promoted to parent interface */ 1943 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1944 /** @removed promoted to parent interface */ 1945 public static final String GROUP_ID = "group_id"; 1946 1947 /** 1948 * Indexed value of {@link ExifInterface#TAG_IMAGE_DESCRIPTION} 1949 * extracted from this media item. 1950 */ 1951 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1952 public static final String DESCRIPTION = "description"; 1953 1954 /** 1955 * Indexed value of {@link ExifInterface#TAG_EXPOSURE_TIME} 1956 * extracted from this media item. 1957 */ 1958 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1959 public static final String EXPOSURE_TIME = "exposure_time"; 1960 1961 /** 1962 * Indexed value of {@link ExifInterface#TAG_F_NUMBER} 1963 * extracted from this media item. 1964 */ 1965 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1966 public static final String F_NUMBER = "f_number"; 1967 1968 /** 1969 * Indexed value of {@link ExifInterface#TAG_ISO_SPEED_RATINGS} 1970 * extracted from this media item. 1971 */ 1972 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1973 public static final String ISO = "iso"; 1974 1975 /** 1976 * Indexed value of {@link ExifInterface#TAG_SCENE_CAPTURE_TYPE} 1977 * extracted from this media item. 1978 */ 1979 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1980 public static final String SCENE_CAPTURE_TYPE = "scene_capture_type"; 1981 } 1982 1983 public static final class Media implements ImageColumns { 1984 /** 1985 * @deprecated all queries should be performed through 1986 * {@link ContentResolver} directly, which offers modern 1987 * features like {@link CancellationSignal}. 1988 */ 1989 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)1990 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 1991 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 1992 } 1993 1994 /** 1995 * @deprecated all queries should be performed through 1996 * {@link ContentResolver} directly, which offers modern 1997 * features like {@link CancellationSignal}. 1998 */ 1999 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)2000 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 2001 String where, String orderBy) { 2002 return cr.query(uri, projection, where, 2003 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2004 } 2005 2006 /** 2007 * @deprecated all queries should be performed through 2008 * {@link ContentResolver} directly, which offers modern 2009 * features like {@link CancellationSignal}. 2010 */ 2011 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)2012 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 2013 String selection, String [] selectionArgs, String orderBy) { 2014 return cr.query(uri, projection, selection, 2015 selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2016 } 2017 2018 /** 2019 * Retrieves an image for the given url as a {@link Bitmap}. 2020 * 2021 * @param cr The content resolver to use 2022 * @param url The url of the image 2023 * @deprecated loading of images should be performed through 2024 * {@link ImageDecoder#createSource(ContentResolver, Uri)}, 2025 * which offers modern features like 2026 * {@link PostProcessor}. 2027 */ 2028 @Deprecated getBitmap(ContentResolver cr, Uri url)2029 public static final Bitmap getBitmap(ContentResolver cr, Uri url) 2030 throws FileNotFoundException, IOException { 2031 InputStream input = cr.openInputStream(url); 2032 Bitmap bitmap = BitmapFactory.decodeStream(input); 2033 input.close(); 2034 return bitmap; 2035 } 2036 2037 /** 2038 * Insert an image and create a thumbnail for it. 2039 * 2040 * @param cr The content resolver to use 2041 * @param imagePath The path to the image to insert 2042 * @param name The name of the image 2043 * @param description The description of the image 2044 * @return The URL to the newly created image 2045 * @deprecated inserting of images should be performed using 2046 * {@link MediaColumns#IS_PENDING}, which offers richer 2047 * control over lifecycle. 2048 */ 2049 @Deprecated insertImage(ContentResolver cr, String imagePath, String name, String description)2050 public static final String insertImage(ContentResolver cr, String imagePath, 2051 String name, String description) throws FileNotFoundException { 2052 final Bitmap source; 2053 try { 2054 source = ImageDecoder 2055 .decodeBitmap(ImageDecoder.createSource(new File(imagePath))); 2056 } catch (IOException e) { 2057 throw new FileNotFoundException(e.getMessage()); 2058 } 2059 return insertImage(cr, source, name, description); 2060 } 2061 2062 /** 2063 * Insert an image and create a thumbnail for it. 2064 * 2065 * @param cr The content resolver to use 2066 * @param source The stream to use for the image 2067 * @param title The name of the image 2068 * @param description The description of the image 2069 * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored 2070 * for any reason. 2071 * @deprecated inserting of images should be performed using 2072 * {@link MediaColumns#IS_PENDING}, which offers richer 2073 * control over lifecycle. 2074 */ 2075 @Deprecated insertImage(ContentResolver cr, Bitmap source, String title, String description)2076 public static final String insertImage(ContentResolver cr, Bitmap source, String title, 2077 String description) { 2078 if (TextUtils.isEmpty(title)) title = "Image"; 2079 2080 final long now = System.currentTimeMillis(); 2081 final ContentValues values = new ContentValues(); 2082 values.put(MediaColumns.DISPLAY_NAME, title); 2083 values.put(MediaColumns.MIME_TYPE, "image/jpeg"); 2084 values.put(MediaColumns.DATE_ADDED, now / 1000); 2085 values.put(MediaColumns.DATE_MODIFIED, now / 1000); 2086 values.put(MediaColumns.IS_PENDING, 1); 2087 2088 final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 2089 try { 2090 try (OutputStream out = cr.openOutputStream(uri)) { 2091 source.compress(Bitmap.CompressFormat.JPEG, 90, out); 2092 } 2093 2094 // Everything went well above, publish it! 2095 values.clear(); 2096 values.put(MediaColumns.IS_PENDING, 0); 2097 cr.update(uri, values, null, null); 2098 return uri.toString(); 2099 } catch (Exception e) { 2100 Log.w(TAG, "Failed to insert image", e); 2101 cr.delete(uri, null, null); 2102 return null; 2103 } 2104 } 2105 2106 /** 2107 * Get the content:// style URI for the image 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 image media table on the given volume 2112 */ getContentUri(String volumeName)2113 public static Uri getContentUri(String volumeName) { 2114 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 2115 .appendPath("media").build(); 2116 } 2117 2118 /** 2119 * Get the content:// style URI for a single row in the images table 2120 * on the given volume. 2121 * 2122 * @param volumeName the name of the volume to get the URI for 2123 * @param id the image to get the URI for 2124 * @return the URI to the images table on the given volume 2125 */ getContentUri(@onNull String volumeName, long id)2126 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2127 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2128 } 2129 2130 /** 2131 * The content:// style URI for the internal storage. 2132 */ 2133 public static final Uri INTERNAL_CONTENT_URI = 2134 getContentUri("internal"); 2135 2136 /** 2137 * The content:// style URI for the "primary" external storage 2138 * volume. 2139 */ 2140 public static final Uri EXTERNAL_CONTENT_URI = 2141 getContentUri("external"); 2142 2143 /** 2144 * The MIME type of this directory of 2145 * images. Note that each entry in this directory will have a standard 2146 * image MIME type as appropriate -- for example, image/jpeg. 2147 */ 2148 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image"; 2149 2150 /** 2151 * The default sort order for this table 2152 */ 2153 public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME; 2154 } 2155 2156 /** 2157 * This class provides utility methods to obtain thumbnails for various 2158 * {@link Images} items. 2159 * 2160 * @deprecated Callers should migrate to using 2161 * {@link ContentResolver#loadThumbnail}, since it offers 2162 * richer control over requested thumbnail sizes and 2163 * cancellation behavior. 2164 */ 2165 @Deprecated 2166 public static class Thumbnails implements BaseColumns { 2167 /** 2168 * @deprecated all queries should be performed through 2169 * {@link ContentResolver} directly, which offers modern 2170 * features like {@link CancellationSignal}. 2171 */ 2172 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)2173 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 2174 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 2175 } 2176 2177 /** 2178 * @deprecated all queries should be performed through 2179 * {@link ContentResolver} directly, which offers modern 2180 * features like {@link CancellationSignal}. 2181 */ 2182 @Deprecated queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)2183 public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, 2184 String[] projection) { 2185 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER); 2186 } 2187 2188 /** 2189 * @deprecated all queries should be performed through 2190 * {@link ContentResolver} directly, which offers modern 2191 * features like {@link CancellationSignal}. 2192 */ 2193 @Deprecated queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)2194 public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, 2195 String[] projection) { 2196 return cr.query(EXTERNAL_CONTENT_URI, projection, 2197 IMAGE_ID + " = " + origId + " AND " + KIND + " = " + 2198 kind, null, null); 2199 } 2200 2201 /** 2202 * Cancel any outstanding {@link #getThumbnail} requests, causing 2203 * them to return by throwing a {@link OperationCanceledException}. 2204 * <p> 2205 * This method has no effect on 2206 * {@link ContentResolver#loadThumbnail} calls, since they provide 2207 * their own {@link CancellationSignal}. 2208 * 2209 * @deprecated Callers should migrate to using 2210 * {@link ContentResolver#loadThumbnail}, since it 2211 * offers richer control over requested thumbnail sizes 2212 * and cancellation behavior. 2213 */ 2214 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)2215 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 2216 final Uri uri = ContentUris.withAppendedId( 2217 Images.Media.EXTERNAL_CONTENT_URI, origId); 2218 InternalThumbnails.cancelThumbnail(cr, uri); 2219 } 2220 2221 /** 2222 * Return thumbnail representing a specific image item. If a 2223 * thumbnail doesn't exist, this method will block until it's 2224 * generated. Callers are responsible for their own in-memory 2225 * caching of returned values. 2226 * 2227 * As of {@link android.os.Build.VERSION_CODES#Q}, this output 2228 * of the thumbnail has correct rotation, don't need to rotate 2229 * it again. 2230 * 2231 * @param imageId the image item to obtain a thumbnail for. 2232 * @param kind optimal thumbnail size desired. 2233 * @return decoded thumbnail, or {@code null} if problem was 2234 * encountered. 2235 * @deprecated Callers should migrate to using 2236 * {@link ContentResolver#loadThumbnail}, since it 2237 * offers richer control over requested thumbnail sizes 2238 * and cancellation behavior. 2239 */ 2240 @Deprecated getThumbnail(ContentResolver cr, long imageId, int kind, BitmapFactory.Options options)2241 public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind, 2242 BitmapFactory.Options options) { 2243 final Uri uri = ContentUris.withAppendedId( 2244 Images.Media.EXTERNAL_CONTENT_URI, imageId); 2245 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 2246 } 2247 2248 /** 2249 * Cancel any outstanding {@link #getThumbnail} requests, causing 2250 * them to return by throwing a {@link OperationCanceledException}. 2251 * <p> 2252 * This method has no effect on 2253 * {@link ContentResolver#loadThumbnail} calls, since they provide 2254 * their own {@link CancellationSignal}. 2255 * 2256 * @deprecated Callers should migrate to using 2257 * {@link ContentResolver#loadThumbnail}, since it 2258 * offers richer control over requested thumbnail sizes 2259 * and cancellation behavior. 2260 */ 2261 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)2262 public static void cancelThumbnailRequest(ContentResolver cr, long origId, 2263 long groupId) { 2264 cancelThumbnailRequest(cr, origId); 2265 } 2266 2267 /** 2268 * Return thumbnail representing a specific image item. If a 2269 * thumbnail doesn't exist, this method will block until it's 2270 * generated. Callers are responsible for their own in-memory 2271 * caching of returned values. 2272 * 2273 * As of {@link android.os.Build.VERSION_CODES#Q}, this output 2274 * of the thumbnail has correct rotation, don't need to rotate 2275 * it again. 2276 * 2277 * @param imageId the image item to obtain a thumbnail for. 2278 * @param kind optimal thumbnail size desired. 2279 * @return decoded thumbnail, or {@code null} if problem was 2280 * encountered. 2281 * @deprecated Callers should migrate to using 2282 * {@link ContentResolver#loadThumbnail}, since it 2283 * offers richer control over requested thumbnail sizes 2284 * and cancellation behavior. 2285 */ 2286 @Deprecated getThumbnail(ContentResolver cr, long imageId, long groupId, int kind, BitmapFactory.Options options)2287 public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId, 2288 int kind, BitmapFactory.Options options) { 2289 return getThumbnail(cr, imageId, kind, options); 2290 } 2291 2292 /** 2293 * Get the content:// style URI for the image media table on the 2294 * given volume. 2295 * 2296 * @param volumeName the name of the volume to get the URI for 2297 * @return the URI to the image media table on the given volume 2298 */ getContentUri(String volumeName)2299 public static Uri getContentUri(String volumeName) { 2300 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 2301 .appendPath("thumbnails").build(); 2302 } 2303 2304 /** 2305 * The content:// style URI for the internal storage. 2306 */ 2307 public static final Uri INTERNAL_CONTENT_URI = 2308 getContentUri("internal"); 2309 2310 /** 2311 * The content:// style URI for the "primary" external storage 2312 * volume. 2313 */ 2314 public static final Uri EXTERNAL_CONTENT_URI = 2315 getContentUri("external"); 2316 2317 /** 2318 * The default sort order for this table 2319 */ 2320 public static final String DEFAULT_SORT_ORDER = "image_id ASC"; 2321 2322 /** 2323 * Path to the thumbnail file on disk. 2324 * <p> 2325 * Note that apps may not have filesystem permissions to directly 2326 * access this path. Instead of trying to open this path directly, 2327 * apps should use 2328 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 2329 * access. 2330 * 2331 * As of {@link android.os.Build.VERSION_CODES#Q}, this thumbnail 2332 * has correct rotation, don't need to rotate it again. 2333 * 2334 * @deprecated Apps may not have filesystem permissions to directly 2335 * access this path. Instead of trying to open this path 2336 * directly, apps should use 2337 * {@link ContentResolver#loadThumbnail} 2338 * to gain access. 2339 */ 2340 @Deprecated 2341 @Column(Cursor.FIELD_TYPE_STRING) 2342 public static final String DATA = "_data"; 2343 2344 /** 2345 * The original image for the thumbnal 2346 */ 2347 @Column(Cursor.FIELD_TYPE_INTEGER) 2348 public static final String IMAGE_ID = "image_id"; 2349 2350 /** 2351 * The kind of the thumbnail 2352 */ 2353 @Column(Cursor.FIELD_TYPE_INTEGER) 2354 public static final String KIND = "kind"; 2355 2356 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 2357 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 2358 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 2359 2360 /** 2361 * Return the typical {@link Size} (in pixels) used internally when 2362 * the given thumbnail kind is requested. 2363 * 2364 * @deprecated Callers should migrate to using 2365 * {@link ContentResolver#loadThumbnail}, since it 2366 * offers richer control over requested thumbnail sizes 2367 * and cancellation behavior. 2368 */ 2369 @Deprecated getKindSize(int kind)2370 public static @NonNull Size getKindSize(int kind) { 2371 return ThumbnailConstants.getKindSize(kind); 2372 } 2373 2374 /** 2375 * The blob raw data of thumbnail 2376 * 2377 * @deprecated this column never existed internally, and could never 2378 * have returned valid data. 2379 */ 2380 @Deprecated 2381 @Column(Cursor.FIELD_TYPE_BLOB) 2382 public static final String THUMB_DATA = "thumb_data"; 2383 2384 /** 2385 * The width of the thumbnal 2386 */ 2387 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2388 public static final String WIDTH = "width"; 2389 2390 /** 2391 * The height of the thumbnail 2392 */ 2393 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2394 public static final String HEIGHT = "height"; 2395 } 2396 } 2397 2398 /** 2399 * Collection of all media with MIME type of {@code audio/*}. 2400 */ 2401 public static final class Audio { 2402 /** 2403 * Audio metadata columns. 2404 */ 2405 public interface AudioColumns extends MediaColumns { 2406 2407 /** 2408 * A non human readable key calculated from the TITLE, used for 2409 * searching, sorting and grouping 2410 * 2411 * @see Audio#keyFor(String) 2412 * @deprecated These keys are generated using 2413 * {@link java.util.Locale#ROOT}, which means they don't 2414 * reflect locale-specific sorting preferences. To apply 2415 * locale-specific sorting preferences, use 2416 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2417 * {@code COLLATE LOCALIZED}, or 2418 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2419 */ 2420 @Deprecated 2421 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2422 public static final String TITLE_KEY = "title_key"; 2423 2424 /** @removed promoted to parent interface */ 2425 public static final String DURATION = "duration"; 2426 2427 /** 2428 * The position within the audio item at which playback should be 2429 * resumed. 2430 */ 2431 @DurationMillisLong 2432 @Column(Cursor.FIELD_TYPE_INTEGER) 2433 public static final String BOOKMARK = "bookmark"; 2434 2435 /** 2436 * The id of the artist who created the audio file, if any 2437 */ 2438 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2439 public static final String ARTIST_ID = "artist_id"; 2440 2441 /** @removed promoted to parent interface */ 2442 public static final String ARTIST = "artist"; 2443 2444 /** 2445 * The artist credited for the album that contains the audio file 2446 * @hide 2447 */ 2448 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2449 public static final String ALBUM_ARTIST = "album_artist"; 2450 2451 /** 2452 * A non human readable key calculated from the ARTIST, used for 2453 * searching, sorting and grouping 2454 * 2455 * @see Audio#keyFor(String) 2456 * @deprecated These keys are generated using 2457 * {@link java.util.Locale#ROOT}, which means they don't 2458 * reflect locale-specific sorting preferences. To apply 2459 * locale-specific sorting preferences, use 2460 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2461 * {@code COLLATE LOCALIZED}, or 2462 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2463 */ 2464 @Deprecated 2465 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2466 public static final String ARTIST_KEY = "artist_key"; 2467 2468 /** @removed promoted to parent interface */ 2469 public static final String COMPOSER = "composer"; 2470 2471 /** 2472 * The id of the album the audio file is from, if any 2473 */ 2474 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2475 public static final String ALBUM_ID = "album_id"; 2476 2477 /** @removed promoted to parent interface */ 2478 public static final String ALBUM = "album"; 2479 2480 /** 2481 * A non human readable key calculated from the ALBUM, used for 2482 * searching, sorting and grouping 2483 * 2484 * @see Audio#keyFor(String) 2485 * @deprecated These keys are generated using 2486 * {@link java.util.Locale#ROOT}, which means they don't 2487 * reflect locale-specific sorting preferences. To apply 2488 * locale-specific sorting preferences, use 2489 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2490 * {@code COLLATE LOCALIZED}, or 2491 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2492 */ 2493 @Deprecated 2494 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2495 public static final String ALBUM_KEY = "album_key"; 2496 2497 /** 2498 * The track number of this song on the album, if any. 2499 * This number encodes both the track number and the 2500 * disc number. For multi-disc sets, this number will 2501 * be 1xxx for tracks on the first disc, 2xxx for tracks 2502 * on the second disc, etc. 2503 */ 2504 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2505 public static final String TRACK = "track"; 2506 2507 /** 2508 * The year the audio file was recorded, if any 2509 */ 2510 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2511 public static final String YEAR = "year"; 2512 2513 /** 2514 * Non-zero if the audio file is music 2515 */ 2516 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2517 public static final String IS_MUSIC = "is_music"; 2518 2519 /** 2520 * Non-zero if the audio file is a podcast 2521 */ 2522 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2523 public static final String IS_PODCAST = "is_podcast"; 2524 2525 /** 2526 * Non-zero if the audio file may be a ringtone 2527 */ 2528 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2529 public static final String IS_RINGTONE = "is_ringtone"; 2530 2531 /** 2532 * Non-zero if the audio file may be an alarm 2533 */ 2534 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2535 public static final String IS_ALARM = "is_alarm"; 2536 2537 /** 2538 * Non-zero if the audio file may be a notification sound 2539 */ 2540 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2541 public static final String IS_NOTIFICATION = "is_notification"; 2542 2543 /** 2544 * Non-zero if the audio file is an audiobook 2545 */ 2546 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2547 public static final String IS_AUDIOBOOK = "is_audiobook"; 2548 2549 /** 2550 * The id of the genre the audio file is from, if any 2551 */ 2552 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2553 public static final String GENRE_ID = "genre_id"; 2554 2555 /** 2556 * The genre of the audio file, if any. 2557 */ 2558 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2559 public static final String GENRE = "genre"; 2560 2561 /** 2562 * A non human readable key calculated from the GENRE, used for 2563 * searching, sorting and grouping 2564 * 2565 * @see Audio#keyFor(String) 2566 * @deprecated These keys are generated using 2567 * {@link java.util.Locale#ROOT}, which means they don't 2568 * reflect locale-specific sorting preferences. To apply 2569 * locale-specific sorting preferences, use 2570 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2571 * {@code COLLATE LOCALIZED}, or 2572 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2573 */ 2574 @Deprecated 2575 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2576 public static final String GENRE_KEY = "genre_key"; 2577 2578 /** 2579 * The resource URI of a localized title, if any. 2580 * <p> 2581 * Conforms to this pattern: 2582 * <ul> 2583 * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE} 2584 * <li>Authority: Package Name of ringtone title provider 2585 * <li>First Path Segment: Type of resource (must be "string") 2586 * <li>Second Path Segment: Resource ID of title 2587 * </ul> 2588 */ 2589 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2590 public static final String TITLE_RESOURCE_URI = "title_resource_uri"; 2591 } 2592 2593 private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile( 2594 "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)"); 2595 private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile( 2596 "(^(00)+|(00)+$)"); 2597 2598 /** 2599 * Converts a user-visible string into a "key" that can be used for 2600 * grouping, sorting, and searching. 2601 * 2602 * @return Opaque token that should not be parsed or displayed to users. 2603 * @deprecated These keys are generated using 2604 * {@link java.util.Locale#ROOT}, which means they don't 2605 * reflect locale-specific sorting preferences. To apply 2606 * locale-specific sorting preferences, use 2607 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2608 * {@code COLLATE LOCALIZED}, or 2609 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2610 */ 2611 @Deprecated keyFor(@ullable String name)2612 public static @Nullable String keyFor(@Nullable String name) { 2613 if (TextUtils.isEmpty(name)) return ""; 2614 2615 if (UNKNOWN_STRING.equals(name)) { 2616 return "01"; 2617 } 2618 2619 final boolean sortFirst = name.startsWith("\001"); 2620 2621 name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll(""); 2622 if (TextUtils.isEmpty(name)) return ""; 2623 2624 final Collator c = Collator.getInstance(Locale.ROOT); 2625 c.setStrength(Collator.PRIMARY); 2626 name = encodeToString(c.getCollationKey(name).toByteArray()); 2627 2628 name = PATTERN_TRIM_AFTER.matcher(name).replaceAll(""); 2629 if (sortFirst) { 2630 name = "01" + name; 2631 } 2632 return name; 2633 } 2634 encodeToString(byte[] bytes)2635 private static String encodeToString(byte[] bytes) { 2636 final StringBuilder sb = new StringBuilder(); 2637 for (byte b : bytes) { 2638 sb.append(String.format("%02x", b)); 2639 } 2640 return sb.toString(); 2641 } 2642 2643 public static final class Media implements AudioColumns { 2644 /** 2645 * Get the content:// style URI for the audio media table on the 2646 * given volume. 2647 * 2648 * @param volumeName the name of the volume to get the URI for 2649 * @return the URI to the audio media table on the given volume 2650 */ getContentUri(String volumeName)2651 public static Uri getContentUri(String volumeName) { 2652 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2653 .appendPath("media").build(); 2654 } 2655 2656 /** 2657 * Get the content:// style URI for a single row in the audio table 2658 * on the given volume. 2659 * 2660 * @param volumeName the name of the volume to get the URI for 2661 * @param id the audio to get the URI for 2662 * @return the URI to the audio table on the given volume 2663 */ getContentUri(@onNull String volumeName, long id)2664 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2665 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2666 } 2667 2668 /** 2669 * Get the content:// style URI for the given audio media file. 2670 * 2671 * @deprecated Apps may not have filesystem permissions to directly 2672 * access this path. 2673 */ 2674 @Deprecated getContentUriForPath(@onNull String path)2675 public static @Nullable Uri getContentUriForPath(@NonNull String path) { 2676 return getContentUri(getVolumeName(new File(path))); 2677 } 2678 2679 /** 2680 * The content:// style URI for the internal storage. 2681 */ 2682 public static final Uri INTERNAL_CONTENT_URI = 2683 getContentUri("internal"); 2684 2685 /** 2686 * The content:// style URI for the "primary" external storage 2687 * volume. 2688 */ 2689 public static final Uri EXTERNAL_CONTENT_URI = 2690 getContentUri("external"); 2691 2692 /** 2693 * The MIME type for this table. 2694 */ 2695 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 2696 2697 /** 2698 * The MIME type for an audio track. 2699 */ 2700 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio"; 2701 2702 /** 2703 * The default sort order for this table 2704 */ 2705 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 2706 2707 /** 2708 * Activity Action: Start SoundRecorder application. 2709 * <p>Input: nothing. 2710 * <p>Output: An uri to the recorded sound stored in the Media Library 2711 * if the recording was successful. 2712 * May also contain the extra EXTRA_MAX_BYTES. 2713 * @see #EXTRA_MAX_BYTES 2714 */ 2715 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 2716 public static final String RECORD_SOUND_ACTION = 2717 "android.provider.MediaStore.RECORD_SOUND"; 2718 2719 /** 2720 * The name of the Intent-extra used to define a maximum file size for 2721 * a recording made by the SoundRecorder application. 2722 * 2723 * @see #RECORD_SOUND_ACTION 2724 */ 2725 public static final String EXTRA_MAX_BYTES = 2726 "android.provider.MediaStore.extra.MAX_BYTES"; 2727 } 2728 2729 /** 2730 * Audio genre metadata columns. 2731 */ 2732 public interface GenresColumns { 2733 /** 2734 * The name of the genre 2735 */ 2736 @Column(Cursor.FIELD_TYPE_STRING) 2737 public static final String NAME = "name"; 2738 } 2739 2740 /** 2741 * Contains all genres for audio files 2742 */ 2743 public static final class Genres implements BaseColumns, GenresColumns { 2744 /** 2745 * Get the content:// style URI for the audio genres table on the 2746 * given volume. 2747 * 2748 * @param volumeName the name of the volume to get the URI for 2749 * @return the URI to the audio genres table on the given volume 2750 */ getContentUri(String volumeName)2751 public static Uri getContentUri(String volumeName) { 2752 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2753 .appendPath("genres").build(); 2754 } 2755 2756 /** 2757 * Get the content:// style URI for querying the genres of an audio file. 2758 * 2759 * @param volumeName the name of the volume to get the URI for 2760 * @param audioId the ID of the audio file for which to retrieve the genres 2761 * @return the URI to for querying the genres for the audio file 2762 * with the given the volume and audioID 2763 */ getContentUriForAudioId(String volumeName, int audioId)2764 public static Uri getContentUriForAudioId(String volumeName, int audioId) { 2765 return ContentUris.withAppendedId(Audio.Media.getContentUri(volumeName), audioId) 2766 .buildUpon().appendPath("genres").build(); 2767 } 2768 2769 /** 2770 * The content:// style URI for the internal storage. 2771 */ 2772 public static final Uri INTERNAL_CONTENT_URI = 2773 getContentUri("internal"); 2774 2775 /** 2776 * The content:// style URI for the "primary" external storage 2777 * volume. 2778 */ 2779 public static final Uri EXTERNAL_CONTENT_URI = 2780 getContentUri("external"); 2781 2782 /** 2783 * The MIME type for this table. 2784 */ 2785 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre"; 2786 2787 /** 2788 * The MIME type for entries in this table. 2789 */ 2790 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre"; 2791 2792 /** 2793 * The default sort order for this table 2794 */ 2795 public static final String DEFAULT_SORT_ORDER = NAME; 2796 2797 /** 2798 * Sub-directory of each genre containing all members. 2799 */ 2800 public static final class Members implements AudioColumns { 2801 getContentUri(String volumeName, long genreId)2802 public static final Uri getContentUri(String volumeName, long genreId) { 2803 return ContentUris 2804 .withAppendedId(Audio.Genres.getContentUri(volumeName), genreId) 2805 .buildUpon().appendPath("members").build(); 2806 } 2807 2808 /** 2809 * A subdirectory of each genre containing all member audio files. 2810 */ 2811 public static final String CONTENT_DIRECTORY = "members"; 2812 2813 /** 2814 * The default sort order for this table 2815 */ 2816 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 2817 2818 /** 2819 * The ID of the audio file 2820 */ 2821 @Column(Cursor.FIELD_TYPE_INTEGER) 2822 public static final String AUDIO_ID = "audio_id"; 2823 2824 /** 2825 * The ID of the genre 2826 */ 2827 @Column(Cursor.FIELD_TYPE_INTEGER) 2828 public static final String GENRE_ID = "genre_id"; 2829 } 2830 } 2831 2832 /** 2833 * Audio playlist metadata columns. 2834 */ 2835 public interface PlaylistsColumns extends MediaColumns { 2836 /** 2837 * The name of the playlist 2838 */ 2839 @Column(Cursor.FIELD_TYPE_STRING) 2840 public static final String NAME = "name"; 2841 2842 /** 2843 * Path to the playlist file on disk. 2844 * <p> 2845 * Note that apps may not have filesystem permissions to directly 2846 * access this path. Instead of trying to open this path directly, 2847 * apps should use 2848 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 2849 * access. 2850 * 2851 * @deprecated Apps may not have filesystem permissions to directly 2852 * access this path. Instead of trying to open this path 2853 * directly, apps should use 2854 * {@link ContentResolver#openFileDescriptor(Uri, String)} 2855 * to gain access. 2856 */ 2857 @Deprecated 2858 @Column(Cursor.FIELD_TYPE_STRING) 2859 public static final String DATA = "_data"; 2860 2861 /** 2862 * The time the media item was first added. 2863 */ 2864 @CurrentTimeSecondsLong 2865 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2866 public static final String DATE_ADDED = "date_added"; 2867 2868 /** 2869 * The time the media item was last modified. 2870 */ 2871 @CurrentTimeSecondsLong 2872 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2873 public static final String DATE_MODIFIED = "date_modified"; 2874 } 2875 2876 /** 2877 * Contains playlists for audio files 2878 */ 2879 public static final class Playlists implements BaseColumns, 2880 PlaylistsColumns { 2881 /** 2882 * Get the content:// style URI for the audio playlists table on the 2883 * given volume. 2884 * 2885 * @param volumeName the name of the volume to get the URI for 2886 * @return the URI to the audio playlists table on the given volume 2887 */ getContentUri(String volumeName)2888 public static Uri getContentUri(String volumeName) { 2889 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2890 .appendPath("playlists").build(); 2891 } 2892 2893 /** 2894 * The content:// style URI for the internal storage. 2895 */ 2896 public static final Uri INTERNAL_CONTENT_URI = 2897 getContentUri("internal"); 2898 2899 /** 2900 * The content:// style URI for the "primary" external storage 2901 * volume. 2902 */ 2903 public static final Uri EXTERNAL_CONTENT_URI = 2904 getContentUri("external"); 2905 2906 /** 2907 * The MIME type for this table. 2908 */ 2909 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist"; 2910 2911 /** 2912 * The MIME type for entries in this table. 2913 */ 2914 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist"; 2915 2916 /** 2917 * The default sort order for this table 2918 */ 2919 public static final String DEFAULT_SORT_ORDER = NAME; 2920 2921 /** 2922 * Sub-directory of each playlist containing all members. 2923 */ 2924 public static final class Members implements AudioColumns { getContentUri(String volumeName, long playlistId)2925 public static final Uri getContentUri(String volumeName, long playlistId) { 2926 return ContentUris 2927 .withAppendedId(Audio.Playlists.getContentUri(volumeName), playlistId) 2928 .buildUpon().appendPath("members").build(); 2929 } 2930 2931 /** 2932 * Convenience method to move a playlist item to a new location 2933 * @param res The content resolver to use 2934 * @param playlistId The numeric id of the playlist 2935 * @param from The position of the item to move 2936 * @param to The position to move the item to 2937 * @return true on success 2938 */ moveItem(ContentResolver res, long playlistId, int from, int to)2939 public static final boolean moveItem(ContentResolver res, 2940 long playlistId, int from, int to) { 2941 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 2942 playlistId) 2943 .buildUpon() 2944 .appendEncodedPath(String.valueOf(from)) 2945 .appendQueryParameter("move", "true") 2946 .build(); 2947 ContentValues values = new ContentValues(); 2948 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to); 2949 return res.update(uri, values, null, null) != 0; 2950 } 2951 2952 /** 2953 * The ID within the playlist. 2954 */ 2955 @Column(Cursor.FIELD_TYPE_INTEGER) 2956 public static final String _ID = "_id"; 2957 2958 /** 2959 * A subdirectory of each playlist containing all member audio 2960 * files. 2961 */ 2962 public static final String CONTENT_DIRECTORY = "members"; 2963 2964 /** 2965 * The ID of the audio file 2966 */ 2967 @Column(Cursor.FIELD_TYPE_INTEGER) 2968 public static final String AUDIO_ID = "audio_id"; 2969 2970 /** 2971 * The ID of the playlist 2972 */ 2973 @Column(Cursor.FIELD_TYPE_INTEGER) 2974 public static final String PLAYLIST_ID = "playlist_id"; 2975 2976 /** 2977 * The order of the songs in the playlist 2978 */ 2979 @Column(Cursor.FIELD_TYPE_INTEGER) 2980 public static final String PLAY_ORDER = "play_order"; 2981 2982 /** 2983 * The default sort order for this table 2984 */ 2985 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER; 2986 } 2987 } 2988 2989 /** 2990 * Audio artist metadata columns. 2991 */ 2992 public interface ArtistColumns { 2993 /** 2994 * The artist who created the audio file, if any 2995 */ 2996 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2997 public static final String ARTIST = "artist"; 2998 2999 /** 3000 * A non human readable key calculated from the ARTIST, used for 3001 * searching, sorting and grouping 3002 * 3003 * @see Audio#keyFor(String) 3004 * @deprecated These keys are generated using 3005 * {@link java.util.Locale#ROOT}, which means they don't 3006 * reflect locale-specific sorting preferences. To apply 3007 * locale-specific sorting preferences, use 3008 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 3009 * {@code COLLATE LOCALIZED}, or 3010 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 3011 */ 3012 @Deprecated 3013 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3014 public static final String ARTIST_KEY = "artist_key"; 3015 3016 /** 3017 * The number of albums in the database for this artist 3018 */ 3019 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3020 public static final String NUMBER_OF_ALBUMS = "number_of_albums"; 3021 3022 /** 3023 * The number of albums in the database for this artist 3024 */ 3025 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3026 public static final String NUMBER_OF_TRACKS = "number_of_tracks"; 3027 } 3028 3029 /** 3030 * Contains artists for audio files 3031 */ 3032 public static final class Artists implements BaseColumns, ArtistColumns { 3033 /** 3034 * Get the content:// style URI for the artists table on the 3035 * given volume. 3036 * 3037 * @param volumeName the name of the volume to get the URI for 3038 * @return the URI to the audio artists table on the given volume 3039 */ getContentUri(String volumeName)3040 public static Uri getContentUri(String volumeName) { 3041 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3042 .appendPath("artists").build(); 3043 } 3044 3045 /** 3046 * The content:// style URI for the internal storage. 3047 */ 3048 public static final Uri INTERNAL_CONTENT_URI = 3049 getContentUri("internal"); 3050 3051 /** 3052 * The content:// style URI for the "primary" external storage 3053 * volume. 3054 */ 3055 public static final Uri EXTERNAL_CONTENT_URI = 3056 getContentUri("external"); 3057 3058 /** 3059 * The MIME type for this table. 3060 */ 3061 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists"; 3062 3063 /** 3064 * The MIME type for entries in this table. 3065 */ 3066 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist"; 3067 3068 /** 3069 * The default sort order for this table 3070 */ 3071 public static final String DEFAULT_SORT_ORDER = ARTIST_KEY; 3072 3073 /** 3074 * Sub-directory of each artist containing all albums on which 3075 * a song by the artist appears. 3076 */ 3077 public static final class Albums implements AlbumColumns { getContentUri(String volumeName,long artistId)3078 public static final Uri getContentUri(String volumeName,long artistId) { 3079 return ContentUris 3080 .withAppendedId(Audio.Artists.getContentUri(volumeName), artistId) 3081 .buildUpon().appendPath("albums").build(); 3082 } 3083 } 3084 } 3085 3086 /** 3087 * Audio album metadata columns. 3088 */ 3089 public interface AlbumColumns { 3090 3091 /** 3092 * The id for the album 3093 */ 3094 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3095 public static final String ALBUM_ID = "album_id"; 3096 3097 /** 3098 * The album on which the audio file appears, if any 3099 */ 3100 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3101 public static final String ALBUM = "album"; 3102 3103 /** 3104 * The ID of the artist whose songs appear on this album. 3105 */ 3106 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3107 public static final String ARTIST_ID = "artist_id"; 3108 3109 /** 3110 * The name of the artist whose songs appear on this album. 3111 */ 3112 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3113 public static final String ARTIST = "artist"; 3114 3115 /** 3116 * A non human readable key calculated from the ARTIST, used for 3117 * searching, sorting and grouping 3118 * 3119 * @see Audio#keyFor(String) 3120 * @deprecated These keys are generated using 3121 * {@link java.util.Locale#ROOT}, which means they don't 3122 * reflect locale-specific sorting preferences. To apply 3123 * locale-specific sorting preferences, use 3124 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 3125 * {@code COLLATE LOCALIZED}, or 3126 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 3127 */ 3128 @Deprecated 3129 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3130 public static final String ARTIST_KEY = "artist_key"; 3131 3132 /** 3133 * The number of songs on this album 3134 */ 3135 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3136 public static final String NUMBER_OF_SONGS = "numsongs"; 3137 3138 /** 3139 * This column is available when getting album info via artist, 3140 * and indicates the number of songs on the album by the given 3141 * artist. 3142 */ 3143 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3144 public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; 3145 3146 /** 3147 * The year in which the earliest songs 3148 * on this album were released. This will often 3149 * be the same as {@link #LAST_YEAR}, but for compilation albums 3150 * they might differ. 3151 */ 3152 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3153 public static final String FIRST_YEAR = "minyear"; 3154 3155 /** 3156 * The year in which the latest songs 3157 * on this album were released. This will often 3158 * be the same as {@link #FIRST_YEAR}, but for compilation albums 3159 * they might differ. 3160 */ 3161 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3162 public static final String LAST_YEAR = "maxyear"; 3163 3164 /** 3165 * A non human readable key calculated from the ALBUM, used for 3166 * searching, sorting and grouping 3167 * 3168 * @see Audio#keyFor(String) 3169 * @deprecated These keys are generated using 3170 * {@link java.util.Locale#ROOT}, which means they don't 3171 * reflect locale-specific sorting preferences. To apply 3172 * locale-specific sorting preferences, use 3173 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 3174 * {@code COLLATE LOCALIZED}, or 3175 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 3176 */ 3177 @Deprecated 3178 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3179 public static final String ALBUM_KEY = "album_key"; 3180 3181 /** 3182 * Cached album art. 3183 * 3184 * @deprecated Apps may not have filesystem permissions to directly 3185 * access this path. Instead of trying to open this path 3186 * directly, apps should use 3187 * {@link ContentResolver#loadThumbnail} 3188 * to gain access. 3189 */ 3190 @Deprecated 3191 @Column(Cursor.FIELD_TYPE_STRING) 3192 public static final String ALBUM_ART = "album_art"; 3193 } 3194 3195 /** 3196 * Contains artists for audio files 3197 */ 3198 public static final class Albums implements BaseColumns, AlbumColumns { 3199 /** 3200 * Get the content:// style URI for the albums table on the 3201 * given volume. 3202 * 3203 * @param volumeName the name of the volume to get the URI for 3204 * @return the URI to the audio albums table on the given volume 3205 */ getContentUri(String volumeName)3206 public static Uri getContentUri(String volumeName) { 3207 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3208 .appendPath("albums").build(); 3209 } 3210 3211 /** 3212 * The content:// style URI for the internal storage. 3213 */ 3214 public static final Uri INTERNAL_CONTENT_URI = 3215 getContentUri("internal"); 3216 3217 /** 3218 * The content:// style URI for the "primary" external storage 3219 * volume. 3220 */ 3221 public static final Uri EXTERNAL_CONTENT_URI = 3222 getContentUri("external"); 3223 3224 /** 3225 * The MIME type for this table. 3226 */ 3227 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums"; 3228 3229 /** 3230 * The MIME type for entries in this table. 3231 */ 3232 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album"; 3233 3234 /** 3235 * The default sort order for this table 3236 */ 3237 public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; 3238 } 3239 3240 public static final class Radio { 3241 /** 3242 * The MIME type for entries in this table. 3243 */ 3244 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; 3245 3246 // Not instantiable. Radio()3247 private Radio() { } 3248 } 3249 3250 /** 3251 * This class provides utility methods to obtain thumbnails for various 3252 * {@link Audio} items. 3253 * 3254 * @deprecated Callers should migrate to using 3255 * {@link ContentResolver#loadThumbnail}, since it offers 3256 * richer control over requested thumbnail sizes and 3257 * cancellation behavior. 3258 * @hide 3259 */ 3260 @Deprecated 3261 public static class Thumbnails implements BaseColumns { 3262 /** 3263 * Path to the thumbnail file on disk. 3264 * <p> 3265 * Note that apps may not have filesystem permissions to directly 3266 * access this path. Instead of trying to open this path directly, 3267 * apps should use 3268 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 3269 * access. 3270 * 3271 * @deprecated Apps may not have filesystem permissions to directly 3272 * access this path. Instead of trying to open this path 3273 * directly, apps should use 3274 * {@link ContentResolver#loadThumbnail} 3275 * to gain access. 3276 */ 3277 @Deprecated 3278 @Column(Cursor.FIELD_TYPE_STRING) 3279 public static final String DATA = "_data"; 3280 3281 @Column(Cursor.FIELD_TYPE_INTEGER) 3282 public static final String ALBUM_ID = "album_id"; 3283 } 3284 } 3285 3286 /** 3287 * Collection of all media with MIME type of {@code video/*}. 3288 */ 3289 public static final class Video { 3290 3291 /** 3292 * The default sort order for this table. 3293 */ 3294 public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME; 3295 3296 /** 3297 * @deprecated all queries should be performed through 3298 * {@link ContentResolver} directly, which offers modern 3299 * features like {@link CancellationSignal}. 3300 */ 3301 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)3302 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 3303 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 3304 } 3305 3306 /** 3307 * Video metadata columns. 3308 */ 3309 public interface VideoColumns extends MediaColumns { 3310 /** @removed promoted to parent interface */ 3311 public static final String DURATION = "duration"; 3312 /** @removed promoted to parent interface */ 3313 public static final String ARTIST = "artist"; 3314 /** @removed promoted to parent interface */ 3315 public static final String ALBUM = "album"; 3316 /** @removed promoted to parent interface */ 3317 public static final String RESOLUTION = "resolution"; 3318 3319 /** 3320 * The description of the video recording 3321 */ 3322 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3323 public static final String DESCRIPTION = "description"; 3324 3325 /** 3326 * Whether the video should be published as public or private 3327 */ 3328 @Column(Cursor.FIELD_TYPE_INTEGER) 3329 public static final String IS_PRIVATE = "isprivate"; 3330 3331 /** 3332 * The user-added tags associated with a video 3333 */ 3334 @Column(Cursor.FIELD_TYPE_STRING) 3335 public static final String TAGS = "tags"; 3336 3337 /** 3338 * The YouTube category of the video 3339 */ 3340 @Column(Cursor.FIELD_TYPE_STRING) 3341 public static final String CATEGORY = "category"; 3342 3343 /** 3344 * The language of the video 3345 */ 3346 @Column(Cursor.FIELD_TYPE_STRING) 3347 public static final String LANGUAGE = "language"; 3348 3349 /** 3350 * The latitude where the video was captured. 3351 * 3352 * @deprecated location details are no longer indexed for privacy 3353 * reasons, and this value is now always {@code null}. 3354 * You can still manually obtain location metadata using 3355 * {@link ExifInterface#getLatLong(float[])}. 3356 */ 3357 @Deprecated 3358 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 3359 public static final String LATITUDE = "latitude"; 3360 3361 /** 3362 * The longitude where the video was captured. 3363 * 3364 * @deprecated location details are no longer indexed for privacy 3365 * reasons, and this value is now always {@code null}. 3366 * You can still manually obtain location metadata using 3367 * {@link ExifInterface#getLatLong(float[])}. 3368 */ 3369 @Deprecated 3370 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 3371 public static final String LONGITUDE = "longitude"; 3372 3373 /** @removed promoted to parent interface */ 3374 public static final String DATE_TAKEN = "datetaken"; 3375 3376 /** 3377 * The mini thumb id. 3378 * 3379 * @deprecated all thumbnails should be obtained via 3380 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 3381 * value is no longer supported. 3382 */ 3383 @Deprecated 3384 @Column(Cursor.FIELD_TYPE_INTEGER) 3385 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 3386 3387 /** @removed promoted to parent interface */ 3388 public static final String BUCKET_ID = "bucket_id"; 3389 /** @removed promoted to parent interface */ 3390 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 3391 /** @removed promoted to parent interface */ 3392 public static final String GROUP_ID = "group_id"; 3393 3394 /** 3395 * The position within the video item at which playback should be 3396 * resumed. 3397 */ 3398 @DurationMillisLong 3399 @Column(Cursor.FIELD_TYPE_INTEGER) 3400 public static final String BOOKMARK = "bookmark"; 3401 3402 /** 3403 * The color standard of this media file, if available. 3404 * 3405 * @see MediaFormat#COLOR_STANDARD_BT709 3406 * @see MediaFormat#COLOR_STANDARD_BT601_PAL 3407 * @see MediaFormat#COLOR_STANDARD_BT601_NTSC 3408 * @see MediaFormat#COLOR_STANDARD_BT2020 3409 */ 3410 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3411 public static final String COLOR_STANDARD = "color_standard"; 3412 3413 /** 3414 * The color transfer of this media file, if available. 3415 * 3416 * @see MediaFormat#COLOR_TRANSFER_LINEAR 3417 * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO 3418 * @see MediaFormat#COLOR_TRANSFER_ST2084 3419 * @see MediaFormat#COLOR_TRANSFER_HLG 3420 */ 3421 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3422 public static final String COLOR_TRANSFER = "color_transfer"; 3423 3424 /** 3425 * The color range of this media file, if available. 3426 * 3427 * @see MediaFormat#COLOR_RANGE_LIMITED 3428 * @see MediaFormat#COLOR_RANGE_FULL 3429 */ 3430 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3431 public static final String COLOR_RANGE = "color_range"; 3432 } 3433 3434 public static final class Media implements VideoColumns { 3435 /** 3436 * Get the content:// style URI for the video media table on the 3437 * given volume. 3438 * 3439 * @param volumeName the name of the volume to get the URI for 3440 * @return the URI to the video media table on the given volume 3441 */ getContentUri(String volumeName)3442 public static Uri getContentUri(String volumeName) { 3443 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3444 .appendPath("media").build(); 3445 } 3446 3447 /** 3448 * Get the content:// style URI for a single row in the videos table 3449 * on the given volume. 3450 * 3451 * @param volumeName the name of the volume to get the URI for 3452 * @param id the video to get the URI for 3453 * @return the URI to the videos table on the given volume 3454 */ getContentUri(@onNull String volumeName, long id)3455 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 3456 return ContentUris.withAppendedId(getContentUri(volumeName), id); 3457 } 3458 3459 /** 3460 * The content:// style URI for the internal storage. 3461 */ 3462 public static final Uri INTERNAL_CONTENT_URI = 3463 getContentUri("internal"); 3464 3465 /** 3466 * The content:// style URI for the "primary" external storage 3467 * volume. 3468 */ 3469 public static final Uri EXTERNAL_CONTENT_URI = 3470 getContentUri("external"); 3471 3472 /** 3473 * The MIME type for this table. 3474 */ 3475 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 3476 3477 /** 3478 * The default sort order for this table 3479 */ 3480 public static final String DEFAULT_SORT_ORDER = TITLE; 3481 } 3482 3483 /** 3484 * This class provides utility methods to obtain thumbnails for various 3485 * {@link Video} items. 3486 * 3487 * @deprecated Callers should migrate to using 3488 * {@link ContentResolver#loadThumbnail}, since it offers 3489 * richer control over requested thumbnail sizes and 3490 * cancellation behavior. 3491 */ 3492 @Deprecated 3493 public static class Thumbnails implements BaseColumns { 3494 /** 3495 * Cancel any outstanding {@link #getThumbnail} requests, causing 3496 * them to return by throwing a {@link OperationCanceledException}. 3497 * <p> 3498 * This method has no effect on 3499 * {@link ContentResolver#loadThumbnail} calls, since they provide 3500 * their own {@link CancellationSignal}. 3501 * 3502 * @deprecated Callers should migrate to using 3503 * {@link ContentResolver#loadThumbnail}, since it 3504 * offers richer control over requested thumbnail sizes 3505 * and cancellation behavior. 3506 */ 3507 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)3508 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 3509 final Uri uri = ContentUris.withAppendedId( 3510 Video.Media.EXTERNAL_CONTENT_URI, origId); 3511 InternalThumbnails.cancelThumbnail(cr, uri); 3512 } 3513 3514 /** 3515 * Return thumbnail representing a specific video item. If a 3516 * thumbnail doesn't exist, this method will block until it's 3517 * generated. Callers are responsible for their own in-memory 3518 * caching of returned values. 3519 * 3520 * @param videoId the video item to obtain a thumbnail for. 3521 * @param kind optimal thumbnail size desired. 3522 * @return decoded thumbnail, or {@code null} if problem was 3523 * encountered. 3524 * @deprecated Callers should migrate to using 3525 * {@link ContentResolver#loadThumbnail}, since it 3526 * offers richer control over requested thumbnail sizes 3527 * and cancellation behavior. 3528 */ 3529 @Deprecated getThumbnail(ContentResolver cr, long videoId, int kind, BitmapFactory.Options options)3530 public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind, 3531 BitmapFactory.Options options) { 3532 final Uri uri = ContentUris.withAppendedId( 3533 Video.Media.EXTERNAL_CONTENT_URI, videoId); 3534 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 3535 } 3536 3537 /** 3538 * Cancel any outstanding {@link #getThumbnail} requests, causing 3539 * them to return by throwing a {@link OperationCanceledException}. 3540 * <p> 3541 * This method has no effect on 3542 * {@link ContentResolver#loadThumbnail} calls, since they provide 3543 * their own {@link CancellationSignal}. 3544 * 3545 * @deprecated Callers should migrate to using 3546 * {@link ContentResolver#loadThumbnail}, since it 3547 * offers richer control over requested thumbnail sizes 3548 * and cancellation behavior. 3549 */ 3550 @Deprecated cancelThumbnailRequest(ContentResolver cr, long videoId, long groupId)3551 public static void cancelThumbnailRequest(ContentResolver cr, long videoId, 3552 long groupId) { 3553 cancelThumbnailRequest(cr, videoId); 3554 } 3555 3556 /** 3557 * Return thumbnail representing a specific video item. If a 3558 * thumbnail doesn't exist, this method will block until it's 3559 * generated. Callers are responsible for their own in-memory 3560 * caching of returned values. 3561 * 3562 * @param videoId the video item to obtain a thumbnail for. 3563 * @param kind optimal thumbnail size desired. 3564 * @return decoded thumbnail, or {@code null} if problem was 3565 * encountered. 3566 * @deprecated Callers should migrate to using 3567 * {@link ContentResolver#loadThumbnail}, since it 3568 * offers richer control over requested thumbnail sizes 3569 * and cancellation behavior. 3570 */ 3571 @Deprecated getThumbnail(ContentResolver cr, long videoId, long groupId, int kind, BitmapFactory.Options options)3572 public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId, 3573 int kind, BitmapFactory.Options options) { 3574 return getThumbnail(cr, videoId, kind, options); 3575 } 3576 3577 /** 3578 * Get the content:// style URI for the image media table on the 3579 * given volume. 3580 * 3581 * @param volumeName the name of the volume to get the URI for 3582 * @return the URI to the image media table on the given volume 3583 */ getContentUri(String volumeName)3584 public static Uri getContentUri(String volumeName) { 3585 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3586 .appendPath("thumbnails").build(); 3587 } 3588 3589 /** 3590 * The content:// style URI for the internal storage. 3591 */ 3592 public static final Uri INTERNAL_CONTENT_URI = 3593 getContentUri("internal"); 3594 3595 /** 3596 * The content:// style URI for the "primary" external storage 3597 * volume. 3598 */ 3599 public static final Uri EXTERNAL_CONTENT_URI = 3600 getContentUri("external"); 3601 3602 /** 3603 * The default sort order for this table 3604 */ 3605 public static final String DEFAULT_SORT_ORDER = "video_id ASC"; 3606 3607 /** 3608 * Path to the thumbnail file on disk. 3609 * 3610 * @deprecated Apps may not have filesystem permissions to directly 3611 * access this path. Instead of trying to open this path 3612 * directly, apps should use 3613 * {@link ContentResolver#openFileDescriptor(Uri, String)} 3614 * to gain access. 3615 */ 3616 @Deprecated 3617 @Column(Cursor.FIELD_TYPE_STRING) 3618 public static final String DATA = "_data"; 3619 3620 /** 3621 * The original image for the thumbnal 3622 */ 3623 @Column(Cursor.FIELD_TYPE_INTEGER) 3624 public static final String VIDEO_ID = "video_id"; 3625 3626 /** 3627 * The kind of the thumbnail 3628 */ 3629 @Column(Cursor.FIELD_TYPE_INTEGER) 3630 public static final String KIND = "kind"; 3631 3632 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 3633 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 3634 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 3635 3636 /** 3637 * Return the typical {@link Size} (in pixels) used internally when 3638 * the given thumbnail kind is requested. 3639 * 3640 * @deprecated Callers should migrate to using 3641 * {@link ContentResolver#loadThumbnail}, since it 3642 * offers richer control over requested thumbnail sizes 3643 * and cancellation behavior. 3644 */ 3645 @Deprecated getKindSize(int kind)3646 public static @NonNull Size getKindSize(int kind) { 3647 return ThumbnailConstants.getKindSize(kind); 3648 } 3649 3650 /** 3651 * The width of the thumbnal 3652 */ 3653 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3654 public static final String WIDTH = "width"; 3655 3656 /** 3657 * The height of the thumbnail 3658 */ 3659 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3660 public static final String HEIGHT = "height"; 3661 } 3662 } 3663 3664 /** 3665 * Return list of all specific volume names that make up 3666 * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each 3667 * shared storage device that is currently attached, which typically 3668 * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}. 3669 * <p> 3670 * Each specific volume name can be passed to APIs like 3671 * {@link MediaStore.Images.Media#getContentUri(String)} to interact with 3672 * media on that storage device. 3673 */ getExternalVolumeNames(@onNull Context context)3674 public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) { 3675 final StorageManager sm = context.getSystemService(StorageManager.class); 3676 final Set<String> res = new ArraySet<>(); 3677 for (StorageVolume sv : sm.getStorageVolumes()) { 3678 Log.v(TAG, "Examining volume " + sv.getId() + " with name " 3679 + sv.getMediaStoreVolumeName() + " and state " + sv.getState()); 3680 switch (sv.getState()) { 3681 case Environment.MEDIA_MOUNTED: 3682 case Environment.MEDIA_MOUNTED_READ_ONLY: { 3683 final String volumeName = sv.getMediaStoreVolumeName(); 3684 if (volumeName != null) { 3685 res.add(volumeName); 3686 } 3687 break; 3688 } 3689 } 3690 } 3691 return res; 3692 } 3693 3694 /** 3695 * Return list of all recent volume names that have been part of 3696 * {@link #VOLUME_EXTERNAL}. 3697 * <p> 3698 * These volume names are not currently mounted, but they're likely to 3699 * reappear in the future, so apps are encouraged to preserve any indexed 3700 * metadata related to these volumes to optimize user experiences. 3701 * <p> 3702 * Each specific volume name can be passed to APIs like 3703 * {@link MediaStore.Images.Media#getContentUri(String)} to interact with 3704 * media on that storage device. 3705 */ getRecentExternalVolumeNames(@onNull Context context)3706 public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) { 3707 final StorageManager sm = context.getSystemService(StorageManager.class); 3708 final Set<String> res = new ArraySet<>(); 3709 for (StorageVolume sv : sm.getRecentStorageVolumes()) { 3710 final String volumeName = sv.getMediaStoreVolumeName(); 3711 if (volumeName != null) { 3712 res.add(volumeName); 3713 } 3714 } 3715 return res; 3716 } 3717 3718 /** 3719 * Return the volume name that the given {@link Uri} references. 3720 */ getVolumeName(@onNull Uri uri)3721 public static @NonNull String getVolumeName(@NonNull Uri uri) { 3722 final List<String> segments = uri.getPathSegments(); 3723 switch (uri.getAuthority()) { 3724 case AUTHORITY: 3725 case AUTHORITY_LEGACY: { 3726 if (segments != null && segments.size() > 0) { 3727 return segments.get(0); 3728 } 3729 } 3730 } 3731 throw new IllegalArgumentException("Missing volume name: " + uri); 3732 } 3733 3734 /** {@hide} */ checkArgumentVolumeName(@onNull String volumeName)3735 public static @NonNull String checkArgumentVolumeName(@NonNull String volumeName) { 3736 if (TextUtils.isEmpty(volumeName)) { 3737 throw new IllegalArgumentException(); 3738 } 3739 3740 if (VOLUME_INTERNAL.equals(volumeName)) { 3741 return volumeName; 3742 } else if (VOLUME_EXTERNAL.equals(volumeName)) { 3743 return volumeName; 3744 } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) { 3745 return volumeName; 3746 } else if (VOLUME_DEMO.equals(volumeName)) { 3747 return volumeName; 3748 } 3749 3750 // When not one of the well-known values above, it must be a hex UUID 3751 for (int i = 0; i < volumeName.length(); i++) { 3752 final char c = volumeName.charAt(i); 3753 if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) { 3754 continue; 3755 } else { 3756 throw new IllegalArgumentException("Invalid volume name: " + volumeName); 3757 } 3758 } 3759 return volumeName; 3760 } 3761 3762 /** 3763 * Uri for querying the state of the media scanner. 3764 */ getMediaScannerUri()3765 public static Uri getMediaScannerUri() { 3766 return AUTHORITY_URI.buildUpon().appendPath("none").appendPath("media_scanner").build(); 3767 } 3768 3769 /** 3770 * Name of current volume being scanned by the media scanner. 3771 */ 3772 public static final String MEDIA_SCANNER_VOLUME = "volume"; 3773 3774 /** 3775 * Name of the file signaling the media scanner to ignore media in the containing directory 3776 * and its subdirectories. Developers should use this to avoid application graphics showing 3777 * up in the Gallery and likewise prevent application sounds and music from showing up in 3778 * the Music app. 3779 */ 3780 public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; 3781 3782 /** 3783 * Return an opaque version string describing the {@link MediaStore} state. 3784 * <p> 3785 * Applications that import data from {@link MediaStore} into their own 3786 * caches can use this to detect that {@link MediaStore} has undergone 3787 * substantial changes, and that data should be rescanned. 3788 * <p> 3789 * No other assumptions should be made about the meaning of the version. 3790 * <p> 3791 * This method returns the version for 3792 * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a 3793 * different volume, use {@link #getVersion(Context, String)}. 3794 */ getVersion(@onNull Context context)3795 public static @NonNull String getVersion(@NonNull Context context) { 3796 return getVersion(context, VOLUME_EXTERNAL_PRIMARY); 3797 } 3798 3799 /** 3800 * Return an opaque version string describing the {@link MediaStore} state. 3801 * <p> 3802 * Applications that import data from {@link MediaStore} into their own 3803 * caches can use this to detect that {@link MediaStore} has undergone 3804 * substantial changes, and that data should be rescanned. 3805 * <p> 3806 * No other assumptions should be made about the meaning of the version. 3807 * 3808 * @param volumeName specific volume to obtain an opaque version string for. 3809 * Must be one of the values returned from 3810 * {@link #getExternalVolumeNames(Context)}. 3811 */ getVersion(@onNull Context context, @NonNull String volumeName)3812 public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) { 3813 final ContentResolver resolver = context.getContentResolver(); 3814 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3815 final Bundle in = new Bundle(); 3816 in.putString(Intent.EXTRA_TEXT, volumeName); 3817 final Bundle out = client.call(GET_VERSION_CALL, null, in); 3818 return out.getString(Intent.EXTRA_TEXT); 3819 } catch (RemoteException e) { 3820 throw e.rethrowAsRuntimeException(); 3821 } 3822 } 3823 3824 /** 3825 * Return the latest generation value for the given volume. 3826 * <p> 3827 * Generation numbers are useful for apps that are attempting to quickly 3828 * identify exactly which media items have been added or changed since a 3829 * previous point in time. Generation numbers are monotonically increasing 3830 * over time, and can be safely arithmetically compared. 3831 * <p> 3832 * Detecting media changes using generation numbers is more robust than 3833 * using {@link MediaColumns#DATE_ADDED} or 3834 * {@link MediaColumns#DATE_MODIFIED}, since those values may change in 3835 * unexpected ways when apps use {@link File#setLastModified(long)} or when 3836 * the system clock is set incorrectly. 3837 * <p> 3838 * Note that before comparing these detailed generation values, you should 3839 * first confirm that the overall version hasn't changed by checking 3840 * {@link MediaStore#getVersion(Context, String)}, since that indicates when 3841 * a more radical change has occurred. If the overall version changes, you 3842 * should assume that generation numbers have been reset and perform a full 3843 * synchronization pass. 3844 * 3845 * @param volumeName specific volume to obtain an generation value for. Must 3846 * be one of the values returned from 3847 * {@link #getExternalVolumeNames(Context)}. 3848 * @see MediaColumns#GENERATION_ADDED 3849 * @see MediaColumns#GENERATION_MODIFIED 3850 */ getGeneration(@onNull Context context, @NonNull String volumeName)3851 public static long getGeneration(@NonNull Context context, @NonNull String volumeName) { 3852 return getGeneration(context.getContentResolver(), volumeName); 3853 } 3854 3855 /** {@hide} */ getGeneration(@onNull ContentResolver resolver, @NonNull String volumeName)3856 public static long getGeneration(@NonNull ContentResolver resolver, 3857 @NonNull String volumeName) { 3858 final Bundle in = new Bundle(); 3859 in.putString(Intent.EXTRA_TEXT, volumeName); 3860 final Bundle out = resolver.call(AUTHORITY, GET_GENERATION_CALL, null, in); 3861 return out.getLong(Intent.EXTRA_INDEX); 3862 } 3863 3864 /** 3865 * Return a {@link DocumentsProvider} Uri that is an equivalent to the given 3866 * {@link MediaStore} Uri. 3867 * <p> 3868 * This allows apps with Storage Access Framework permissions to convert 3869 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 3870 * to the same underlying item. Note that this method doesn't grant any new 3871 * permissions; callers must already hold permissions obtained with 3872 * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs. 3873 * 3874 * @param mediaUri The {@link MediaStore} Uri to convert. 3875 * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null} 3876 * if no equivalent was found. 3877 * @see #getMediaUri(Context, Uri) 3878 */ getDocumentUri(@onNull Context context, @NonNull Uri mediaUri)3879 public static @Nullable Uri getDocumentUri(@NonNull Context context, @NonNull Uri mediaUri) { 3880 final ContentResolver resolver = context.getContentResolver(); 3881 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 3882 3883 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3884 final Bundle in = new Bundle(); 3885 in.putParcelable(EXTRA_URI, mediaUri); 3886 in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions)); 3887 final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in); 3888 return out.getParcelable(EXTRA_URI); 3889 } catch (RemoteException e) { 3890 throw e.rethrowAsRuntimeException(); 3891 } 3892 } 3893 3894 /** 3895 * Return a {@link MediaStore} Uri that is an equivalent to the given 3896 * {@link DocumentsProvider} Uri. 3897 * <p> 3898 * This allows apps with Storage Access Framework permissions to convert 3899 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 3900 * to the same underlying item. Note that this method doesn't grant any new 3901 * permissions; callers must already hold permissions obtained with 3902 * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs. 3903 * 3904 * @param documentUri The {@link DocumentsProvider} Uri to convert. 3905 * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no 3906 * equivalent was found. 3907 * @see #getDocumentUri(Context, Uri) 3908 */ getMediaUri(@onNull Context context, @NonNull Uri documentUri)3909 public static @Nullable Uri getMediaUri(@NonNull Context context, @NonNull Uri documentUri) { 3910 final ContentResolver resolver = context.getContentResolver(); 3911 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 3912 3913 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3914 final Bundle in = new Bundle(); 3915 in.putParcelable(EXTRA_URI, documentUri); 3916 in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions)); 3917 final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in); 3918 return out.getParcelable(EXTRA_URI); 3919 } catch (RemoteException e) { 3920 throw e.rethrowAsRuntimeException(); 3921 } 3922 } 3923 3924 /** {@hide} */ resolvePlaylistMembers(@onNull ContentResolver resolver, @NonNull Uri playlistUri)3925 public static void resolvePlaylistMembers(@NonNull ContentResolver resolver, 3926 @NonNull Uri playlistUri) { 3927 final Bundle in = new Bundle(); 3928 in.putParcelable(EXTRA_URI, playlistUri); 3929 resolver.call(AUTHORITY, RESOLVE_PLAYLIST_MEMBERS_CALL, null, in); 3930 } 3931 3932 /** {@hide} */ runIdleMaintenance(@onNull ContentResolver resolver)3933 public static void runIdleMaintenance(@NonNull ContentResolver resolver) { 3934 resolver.call(AUTHORITY, RUN_IDLE_MAINTENANCE_CALL, null, null); 3935 } 3936 3937 /** 3938 * Block until any pending operations have finished, such as 3939 * {@link #scanFile} or {@link #scanVolume} requests. 3940 * 3941 * @hide 3942 */ 3943 @SystemApi 3944 @TestApi 3945 @WorkerThread waitForIdle(@onNull ContentResolver resolver)3946 public static void waitForIdle(@NonNull ContentResolver resolver) { 3947 resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null); 3948 } 3949 3950 /** 3951 * Perform a blocking scan of the given {@link File}, returning the 3952 * {@link Uri} of the scanned file. 3953 * 3954 * @hide 3955 */ 3956 @SystemApi 3957 @TestApi 3958 @WorkerThread 3959 @SuppressLint("StreamFiles") scanFile(@onNull ContentResolver resolver, @NonNull File file)3960 public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) { 3961 final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null); 3962 return out.getParcelable(Intent.EXTRA_STREAM); 3963 } 3964 3965 /** 3966 * Perform a blocking scan of the given storage volume. 3967 * 3968 * @hide 3969 */ 3970 @SystemApi 3971 @TestApi 3972 @WorkerThread scanVolume(@onNull ContentResolver resolver, @NonNull String volumeName)3973 public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) { 3974 resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null); 3975 } 3976 } 3977