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