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 */ 1179 @Column(Cursor.FIELD_TYPE_STRING) 1180 public static final String DATA = "_data"; 1181 1182 /** 1183 * Indexed value of {@link File#length()} extracted from this media 1184 * item. 1185 */ 1186 @BytesLong 1187 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1188 public static final String SIZE = "_size"; 1189 1190 /** 1191 * The display name of the media item. 1192 * <p> 1193 * For example, an item stored at 1194 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 1195 * display name of {@code IMG1024.JPG}. 1196 */ 1197 @Column(Cursor.FIELD_TYPE_STRING) 1198 public static final String DISPLAY_NAME = "_display_name"; 1199 1200 /** 1201 * The time the media item was first added. 1202 */ 1203 @CurrentTimeSecondsLong 1204 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1205 public static final String DATE_ADDED = "date_added"; 1206 1207 /** 1208 * Indexed value of {@link File#lastModified()} extracted from this 1209 * media item. 1210 */ 1211 @CurrentTimeSecondsLong 1212 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1213 public static final String DATE_MODIFIED = "date_modified"; 1214 1215 /** 1216 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DATE} or 1217 * {@link ExifInterface#TAG_DATETIME_ORIGINAL} extracted from this media 1218 * item. 1219 * <p> 1220 * Note that images must define both 1221 * {@link ExifInterface#TAG_DATETIME_ORIGINAL} and 1222 * {@code ExifInterface#TAG_OFFSET_TIME_ORIGINAL} to reliably determine 1223 * this value in relation to the epoch. 1224 */ 1225 @CurrentTimeMillisLong 1226 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1227 public static final String DATE_TAKEN = "datetaken"; 1228 1229 /** 1230 * The MIME type of the media item. 1231 * <p> 1232 * This is typically defined based on the file extension of the media 1233 * item. However, it may be the value of the {@code format} attribute 1234 * defined by the <em>Dublin Core Media Initiative</em> standard, 1235 * extracted from any XMP metadata contained within this media item. 1236 * <p class="note"> 1237 * Note: the {@code format} attribute may be ignored if the top-level 1238 * MIME type disagrees with the file extension. For example, it's 1239 * reasonable for an {@code image/jpeg} file to declare a {@code format} 1240 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 1241 * {@code format} of {@code audio/ogg} would be ignored. 1242 * <p> 1243 * This is a read-only column that is automatically computed. 1244 */ 1245 @Column(Cursor.FIELD_TYPE_STRING) 1246 public static final String MIME_TYPE = "mime_type"; 1247 1248 /** 1249 * Flag indicating if a media item is DRM protected. 1250 */ 1251 @Column(Cursor.FIELD_TYPE_INTEGER) 1252 public static final String IS_DRM = "is_drm"; 1253 1254 /** 1255 * Flag indicating if a media item is pending, and still being inserted 1256 * by its owner. While this flag is set, only the owner of the item can 1257 * open the underlying file; requests from other apps will be rejected. 1258 * <p> 1259 * Pending items are retained either until they are published by setting 1260 * the field to {@code 0}, or until they expire as defined by 1261 * {@link #DATE_EXPIRES}. 1262 * 1263 * @see MediaStore#QUERY_ARG_MATCH_PENDING 1264 */ 1265 @Column(Cursor.FIELD_TYPE_INTEGER) 1266 public static final String IS_PENDING = "is_pending"; 1267 1268 /** 1269 * Flag indicating if a media item is trashed. 1270 * <p> 1271 * Trashed items are retained until they expire as defined by 1272 * {@link #DATE_EXPIRES}. 1273 * 1274 * @see MediaColumns#IS_TRASHED 1275 * @see MediaStore#QUERY_ARG_MATCH_TRASHED 1276 * @see MediaStore#createTrashRequest 1277 */ 1278 @Column(Cursor.FIELD_TYPE_INTEGER) 1279 public static final String IS_TRASHED = "is_trashed"; 1280 1281 /** 1282 * The time the media item should be considered expired. Typically only 1283 * meaningful in the context of {@link #IS_PENDING} or 1284 * {@link #IS_TRASHED}. 1285 * <p> 1286 * The value stored in this column is automatically calculated when 1287 * {@link #IS_PENDING} or {@link #IS_TRASHED} is changed. The default 1288 * pending expiration is typically 7 days, and the default trashed 1289 * expiration is typically 30 days. 1290 * <p> 1291 * Expired media items are automatically deleted once their expiration 1292 * time has passed, typically during during the next device idle period. 1293 */ 1294 @CurrentTimeSecondsLong 1295 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1296 public static final String DATE_EXPIRES = "date_expires"; 1297 1298 /** 1299 * Indexed value of 1300 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_WIDTH}, 1301 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_WIDTH} or 1302 * {@link ExifInterface#TAG_IMAGE_WIDTH} extracted from this media item. 1303 */ 1304 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1305 public static final String WIDTH = "width"; 1306 1307 /** 1308 * Indexed value of 1309 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_HEIGHT}, 1310 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_HEIGHT} or 1311 * {@link ExifInterface#TAG_IMAGE_LENGTH} extracted from this media 1312 * item. 1313 */ 1314 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1315 public static final String HEIGHT = "height"; 1316 1317 /** 1318 * Calculated value that combines {@link #WIDTH} and {@link #HEIGHT} 1319 * into a user-presentable string. 1320 */ 1321 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1322 public static final String RESOLUTION = "resolution"; 1323 1324 /** 1325 * Package name that contributed this media. The value may be 1326 * {@code NULL} if ownership cannot be reliably determined. 1327 */ 1328 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1329 public static final String OWNER_PACKAGE_NAME = "owner_package_name"; 1330 1331 /** 1332 * Volume name of the specific storage device where this media item is 1333 * persisted. The value is typically one of the volume names returned 1334 * from {@link MediaStore#getExternalVolumeNames(Context)}. 1335 * <p> 1336 * This is a read-only column that is automatically computed. 1337 */ 1338 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1339 public static final String VOLUME_NAME = "volume_name"; 1340 1341 /** 1342 * Relative path of this media item within the storage device where it 1343 * is persisted. For example, an item stored at 1344 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 1345 * path of {@code DCIM/Vacation/}. 1346 * <p> 1347 * This value should only be used for organizational purposes, and you 1348 * should not attempt to construct or access a raw filesystem path using 1349 * this value. If you need to open a media item, use an API like 1350 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 1351 * <p> 1352 * When this value is set to {@code NULL} during an 1353 * {@link ContentResolver#insert} operation, the newly created item will 1354 * be placed in a relevant default location based on the type of media 1355 * being inserted. For example, a {@code image/jpeg} item will be placed 1356 * under {@link Environment#DIRECTORY_PICTURES}. 1357 * <p> 1358 * You can modify this column during an {@link ContentResolver#update} 1359 * call, which will move the underlying file on disk. 1360 * <p> 1361 * In both cases above, content must be placed under a top-level 1362 * directory that is relevant to the media type. For example, attempting 1363 * to place a {@code audio/mpeg} file under 1364 * {@link Environment#DIRECTORY_PICTURES} will be rejected. 1365 */ 1366 @Column(Cursor.FIELD_TYPE_STRING) 1367 public static final String RELATIVE_PATH = "relative_path"; 1368 1369 /** 1370 * The primary bucket ID of this media item. This can be useful to 1371 * present the user a first-level clustering of related media items. 1372 * This is a read-only column that is automatically computed. 1373 */ 1374 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1375 public static final String BUCKET_ID = "bucket_id"; 1376 1377 /** 1378 * The primary bucket display name of this media item. This can be 1379 * useful to present the user a first-level clustering of related 1380 * media items. This is a read-only column that is automatically 1381 * computed. 1382 */ 1383 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1384 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1385 1386 /** 1387 * The group ID of this media item. This can be useful to present 1388 * the user a grouping of related media items, such a burst of 1389 * images, or a {@code JPG} and {@code DNG} version of the same 1390 * image. 1391 * <p> 1392 * This is a read-only column that is automatically computed based 1393 * on the first portion of the filename. For example, 1394 * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG} 1395 * will have the same {@link #GROUP_ID} because the first portion of 1396 * their filenames is identical. 1397 * 1398 * @removed 1399 */ 1400 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1401 @Deprecated 1402 public static final String GROUP_ID = "group_id"; 1403 1404 /** 1405 * The "document ID" GUID as defined by the <em>XMP Media 1406 * Management</em> standard, extracted from any XMP metadata contained 1407 * within this media item. The value is {@code null} when no metadata 1408 * was found. 1409 * <p> 1410 * Each "document ID" is created once for each new resource. Different 1411 * renditions of that resource are expected to have different IDs. 1412 */ 1413 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1414 public static final String DOCUMENT_ID = "document_id"; 1415 1416 /** 1417 * The "instance ID" GUID as defined by the <em>XMP Media 1418 * Management</em> standard, extracted from any XMP metadata contained 1419 * within this media item. The value is {@code null} when no metadata 1420 * was found. 1421 * <p> 1422 * This "instance ID" changes with each save operation of a specific 1423 * "document ID". 1424 */ 1425 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1426 public static final String INSTANCE_ID = "instance_id"; 1427 1428 /** 1429 * The "original document ID" GUID as defined by the <em>XMP Media 1430 * Management</em> standard, extracted from any XMP metadata contained 1431 * within this media item. 1432 * <p> 1433 * This "original document ID" links a resource to its original source. 1434 * For example, when you save a PSD document as a JPEG, then convert the 1435 * JPEG to GIF format, the "original document ID" of both the JPEG and 1436 * GIF files is the "document ID" of the original PSD file. 1437 */ 1438 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1439 public static final String ORIGINAL_DOCUMENT_ID = "original_document_id"; 1440 1441 /** 1442 * Indexed value of 1443 * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_ROTATION}, 1444 * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_ROTATION}, or 1445 * {@link ExifInterface#TAG_ORIENTATION} extracted from this media item. 1446 * <p> 1447 * For consistency the indexed value is expressed in degrees, such as 0, 1448 * 90, 180, or 270. 1449 */ 1450 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1451 public static final String ORIENTATION = "orientation"; 1452 1453 /** 1454 * Flag indicating if the media item has been marked as being a 1455 * "favorite" by the user. 1456 * 1457 * @see MediaColumns#IS_FAVORITE 1458 * @see MediaStore#QUERY_ARG_MATCH_FAVORITE 1459 * @see MediaStore#createFavoriteRequest 1460 */ 1461 @Column(Cursor.FIELD_TYPE_INTEGER) 1462 public static final String IS_FAVORITE = "is_favorite"; 1463 1464 /** 1465 * Flag indicating if the media item has been marked as being part of 1466 * the {@link Downloads} collection. 1467 */ 1468 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1469 public static final String IS_DOWNLOAD = "is_download"; 1470 1471 /** 1472 * Generation number at which metadata for this media item was first 1473 * inserted. This is useful for apps that are attempting to quickly 1474 * identify exactly which media items have been added since a previous 1475 * point in time. Generation numbers are monotonically increasing over 1476 * time, and can be safely arithmetically compared. 1477 * <p> 1478 * Detecting media additions using generation numbers is more robust 1479 * than using {@link #DATE_ADDED}, since those values may change in 1480 * unexpected ways when apps use {@link File#setLastModified(long)} or 1481 * when the system clock is set incorrectly. 1482 * <p> 1483 * Note that before comparing these detailed generation values, you 1484 * should first confirm that the overall version hasn't changed by 1485 * checking {@link MediaStore#getVersion(Context, String)}, since that 1486 * indicates when a more radical change has occurred. If the overall 1487 * version changes, you should assume that generation numbers have been 1488 * reset and perform a full synchronization pass. 1489 * 1490 * @see MediaStore#getGeneration(Context, String) 1491 */ 1492 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1493 public static final String GENERATION_ADDED = "generation_added"; 1494 1495 /** 1496 * Generation number at which metadata for this media item was last 1497 * changed. This is useful for apps that are attempting to quickly 1498 * identify exactly which media items have changed since a previous 1499 * point in time. Generation numbers are monotonically increasing over 1500 * time, and can be safely arithmetically compared. 1501 * <p> 1502 * Detecting media changes using generation numbers is more robust than 1503 * using {@link #DATE_MODIFIED}, since those values may change in 1504 * unexpected ways when apps use {@link File#setLastModified(long)} or 1505 * when the system clock is set incorrectly. 1506 * <p> 1507 * Note that before comparing these detailed generation values, you 1508 * should first confirm that the overall version hasn't changed by 1509 * checking {@link MediaStore#getVersion(Context, String)}, since that 1510 * indicates when a more radical change has occurred. If the overall 1511 * version changes, you should assume that generation numbers have been 1512 * reset and perform a full synchronization pass. 1513 * 1514 * @see MediaStore#getGeneration(Context, String) 1515 */ 1516 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1517 public static final String GENERATION_MODIFIED = "generation_modified"; 1518 1519 /** 1520 * Indexed XMP metadata extracted from this media item. 1521 * <p> 1522 * The structure of this metadata is defined by the <a href= 1523 * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform"><em>XMP 1524 * Media Management</em> standard</a>, published as ISO 16684-1:2012. 1525 * <p> 1526 * This metadata is typically extracted from a 1527 * {@link ExifInterface#TAG_XMP} contained inside an image file or from 1528 * a {@code XMP_} box contained inside an ISO/IEC base media file format 1529 * (MPEG-4 Part 12). 1530 * <p> 1531 * Note that any location details are redacted from this metadata for 1532 * privacy reasons. 1533 */ 1534 @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true) 1535 public static final String XMP = "xmp"; 1536 1537 // ======================================= 1538 // ==== MediaMetadataRetriever values ==== 1539 // ======================================= 1540 1541 /** 1542 * Indexed value of 1543 * {@link MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER} extracted 1544 * from this media item. 1545 */ 1546 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1547 public static final String CD_TRACK_NUMBER = "cd_track_number"; 1548 1549 /** 1550 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUM} 1551 * extracted from this media item. 1552 */ 1553 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1554 public static final String ALBUM = "album"; 1555 1556 /** 1557 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ARTIST} 1558 * or {@link ExifInterface#TAG_ARTIST} extracted from this media item. 1559 */ 1560 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1561 public static final String ARTIST = "artist"; 1562 1563 /** 1564 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_AUTHOR} 1565 * extracted from this media item. 1566 */ 1567 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1568 public static final String AUTHOR = "author"; 1569 1570 /** 1571 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPOSER} 1572 * extracted from this media item. 1573 */ 1574 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1575 public static final String COMPOSER = "composer"; 1576 1577 // METADATA_KEY_DATE is DATE_TAKEN 1578 1579 /** 1580 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_GENRE} 1581 * extracted from this media item. 1582 */ 1583 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1584 public static final String GENRE = "genre"; 1585 1586 /** 1587 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_TITLE} 1588 * extracted from this media item. 1589 */ 1590 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1591 public static final String TITLE = "title"; 1592 1593 /** 1594 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_YEAR} 1595 * extracted from this media item. 1596 */ 1597 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1598 public static final String YEAR = "year"; 1599 1600 /** 1601 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DURATION} 1602 * extracted from this media item. 1603 */ 1604 @DurationMillisLong 1605 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1606 public static final String DURATION = "duration"; 1607 1608 /** 1609 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_NUM_TRACKS} 1610 * extracted from this media item. 1611 */ 1612 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1613 public static final String NUM_TRACKS = "num_tracks"; 1614 1615 /** 1616 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_WRITER} 1617 * extracted from this media item. 1618 */ 1619 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1620 public static final String WRITER = "writer"; 1621 1622 // METADATA_KEY_MIMETYPE is MIME_TYPE 1623 1624 /** 1625 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST} 1626 * extracted from this media item. 1627 */ 1628 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1629 public static final String ALBUM_ARTIST = "album_artist"; 1630 1631 /** 1632 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER} 1633 * extracted from this media item. 1634 */ 1635 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1636 public static final String DISC_NUMBER = "disc_number"; 1637 1638 /** 1639 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPILATION} 1640 * extracted from this media item. 1641 */ 1642 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1643 public static final String COMPILATION = "compilation"; 1644 1645 // HAS_AUDIO is ignored 1646 // HAS_VIDEO is ignored 1647 // VIDEO_WIDTH is WIDTH 1648 // VIDEO_HEIGHT is HEIGHT 1649 1650 /** 1651 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_BITRATE} 1652 * extracted from this media item. 1653 */ 1654 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1655 public static final String BITRATE = "bitrate"; 1656 1657 // TIMED_TEXT_LANGUAGES is ignored 1658 // IS_DRM is ignored 1659 // LOCATION is LATITUDE and LONGITUDE 1660 // VIDEO_ROTATION is ORIENTATION 1661 1662 /** 1663 * Indexed value of 1664 * {@link MediaMetadataRetriever#METADATA_KEY_CAPTURE_FRAMERATE} 1665 * extracted from this media item. 1666 */ 1667 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1668 public static final String CAPTURE_FRAMERATE = "capture_framerate"; 1669 1670 // HAS_IMAGE is ignored 1671 // IMAGE_COUNT is ignored 1672 // IMAGE_PRIMARY is ignored 1673 // IMAGE_WIDTH is WIDTH 1674 // IMAGE_HEIGHT is HEIGHT 1675 // IMAGE_ROTATION is ORIENTATION 1676 // VIDEO_FRAME_COUNT is ignored 1677 // EXIF_OFFSET is ignored 1678 // EXIF_LENGTH is ignored 1679 // COLOR_STANDARD is ignored 1680 // COLOR_TRANSFER is ignored 1681 // COLOR_RANGE is ignored 1682 // SAMPLERATE is ignored 1683 // BITS_PER_SAMPLE is ignored 1684 } 1685 1686 /** 1687 * Media provider table containing an index of all files in the media storage, 1688 * including non-media files. This should be used by applications that work with 1689 * non-media file types (text, HTML, PDF, etc) as well as applications that need to 1690 * work with multiple media file types in a single query. 1691 */ 1692 public static final class Files { 1693 /** @hide */ 1694 public static final String TABLE = "files"; 1695 1696 /** @hide */ 1697 public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL); 1698 1699 /** 1700 * Get the content:// style URI for the files table on the 1701 * given volume. 1702 * 1703 * @param volumeName the name of the volume to get the URI for 1704 * @return the URI to the files table on the given volume 1705 */ getContentUri(String volumeName)1706 public static Uri getContentUri(String volumeName) { 1707 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("file").build(); 1708 } 1709 1710 /** 1711 * Get the content:// style URI for a single row in the files table on the 1712 * given volume. 1713 * 1714 * @param volumeName the name of the volume to get the URI for 1715 * @param rowId the file to get the URI for 1716 * @return the URI to the files table on the given volume 1717 */ getContentUri(String volumeName, long rowId)1718 public static final Uri getContentUri(String volumeName, 1719 long rowId) { 1720 return ContentUris.withAppendedId(getContentUri(volumeName), rowId); 1721 } 1722 1723 /** {@hide} */ 1724 @UnsupportedAppUsage getMtpObjectsUri(@onNull String volumeName)1725 public static Uri getMtpObjectsUri(@NonNull String volumeName) { 1726 return MediaStore.Files.getContentUri(volumeName); 1727 } 1728 1729 /** {@hide} */ 1730 @UnsupportedAppUsage getMtpObjectsUri(@onNull String volumeName, long fileId)1731 public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) { 1732 return MediaStore.Files.getContentUri(volumeName, fileId); 1733 } 1734 1735 /** {@hide} */ 1736 @UnsupportedAppUsage getMtpReferencesUri(@onNull String volumeName, long fileId)1737 public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) { 1738 return MediaStore.Files.getContentUri(volumeName, fileId); 1739 } 1740 1741 /** 1742 * Used to trigger special logic for directories. 1743 * @hide 1744 */ getDirectoryUri(String volumeName)1745 public static final Uri getDirectoryUri(String volumeName) { 1746 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build(); 1747 } 1748 1749 /** @hide */ getContentUriForPath(String path)1750 public static final Uri getContentUriForPath(String path) { 1751 return getContentUri(getVolumeName(new File(path))); 1752 } 1753 1754 /** 1755 * File metadata columns. 1756 */ 1757 public interface FileColumns extends MediaColumns { 1758 /** 1759 * The MTP storage ID of the file 1760 * @hide 1761 */ 1762 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1763 @Deprecated 1764 // @Column(Cursor.FIELD_TYPE_INTEGER) 1765 public static final String STORAGE_ID = "storage_id"; 1766 1767 /** 1768 * The MTP format code of the file 1769 * @hide 1770 */ 1771 @UnsupportedAppUsage 1772 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1773 public static final String FORMAT = "format"; 1774 1775 /** 1776 * The index of the parent directory of the file 1777 */ 1778 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1779 public static final String PARENT = "parent"; 1780 1781 /** 1782 * The MIME type of the media item. 1783 * <p> 1784 * This is typically defined based on the file extension of the media 1785 * item. However, it may be the value of the {@code format} attribute 1786 * defined by the <em>Dublin Core Media Initiative</em> standard, 1787 * extracted from any XMP metadata contained within this media item. 1788 * <p class="note"> 1789 * Note: the {@code format} attribute may be ignored if the top-level 1790 * MIME type disagrees with the file extension. For example, it's 1791 * reasonable for an {@code image/jpeg} file to declare a {@code format} 1792 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 1793 * {@code format} of {@code audio/ogg} would be ignored. 1794 * <p> 1795 * This is a read-only column that is automatically computed. 1796 */ 1797 @Column(Cursor.FIELD_TYPE_STRING) 1798 public static final String MIME_TYPE = "mime_type"; 1799 1800 /** @removed promoted to parent interface */ 1801 public static final String TITLE = "title"; 1802 1803 /** 1804 * The media type (audio, video, image, document, playlist or subtitle) 1805 * of the file, or 0 for not a media file 1806 */ 1807 @Column(Cursor.FIELD_TYPE_INTEGER) 1808 public static final String MEDIA_TYPE = "media_type"; 1809 1810 /** 1811 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1812 * is not an audio, image, video, document, playlist, or subtitles file. 1813 */ 1814 public static final int MEDIA_TYPE_NONE = 0; 1815 1816 /** 1817 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1818 * is an image file. 1819 */ 1820 public static final int MEDIA_TYPE_IMAGE = 1; 1821 1822 /** 1823 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1824 * is an audio file. 1825 */ 1826 public static final int MEDIA_TYPE_AUDIO = 2; 1827 1828 /** 1829 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1830 * is a video file. 1831 */ 1832 public static final int MEDIA_TYPE_VIDEO = 3; 1833 1834 /** 1835 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1836 * is a playlist file. 1837 * 1838 * @deprecated Android playlists are now deprecated. We will keep the current 1839 * functionality for compatibility reasons, but we will no longer take 1840 * feature request. We do not advise adding new usages of Android Playlists. 1841 * M3U files can be used as an alternative. 1842 */ 1843 @Deprecated 1844 public static final int MEDIA_TYPE_PLAYLIST = 4; 1845 1846 /** 1847 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1848 * is a subtitles or lyrics file. 1849 */ 1850 public static final int MEDIA_TYPE_SUBTITLE = 5; 1851 1852 /** 1853 * Constant for the {@link #MEDIA_TYPE} column indicating that file is a document file. 1854 */ 1855 public static final int MEDIA_TYPE_DOCUMENT = 6; 1856 1857 /** 1858 * Constant indicating the count of {@link #MEDIA_TYPE} columns. 1859 * @hide 1860 */ 1861 public static final int MEDIA_TYPE_COUNT = 7; 1862 1863 /** 1864 * Modifier of the database row 1865 * 1866 * Specifies the last modifying operation of the database row. This 1867 * does not give any information on the package that modified the 1868 * database row. 1869 * Initially, this column will be populated by 1870 * {@link ContentResolver}#insert and media scan operations. And, 1871 * the column will be used to identify if the file was previously 1872 * scanned. 1873 * @hide 1874 */ 1875 // @Column(value = Cursor.FIELD_TYPE_INTEGER) 1876 public static final String _MODIFIER = "_modifier"; 1877 1878 /** 1879 * Constant for the {@link #_MODIFIER} column indicating 1880 * that the last modifier of the database row is FUSE operation. 1881 * @hide 1882 */ 1883 public static final int _MODIFIER_FUSE = 1; 1884 1885 /** 1886 * Constant for the {@link #_MODIFIER} column indicating 1887 * that the last modifier of the database row is explicit 1888 * {@link ContentResolver} operation from app. 1889 * @hide 1890 */ 1891 public static final int _MODIFIER_CR = 2; 1892 1893 /** 1894 * Constant for the {@link #_MODIFIER} column indicating 1895 * that the last modifier of the database row is a media scan 1896 * operation. 1897 * @hide 1898 */ 1899 public static final int _MODIFIER_MEDIA_SCAN = 3; 1900 1901 /** 1902 * Constant for the {@link #_MODIFIER} column indicating 1903 * that the last modifier of the database row is explicit 1904 * {@link ContentResolver} operation and is waiting for metadata 1905 * update. 1906 * @hide 1907 */ 1908 public static final int _MODIFIER_CR_PENDING_METADATA = 4; 1909 1910 /** 1911 * Status of the transcode file 1912 * 1913 * For apps that do not support modern media formats for video, we 1914 * seamlessly transcode the file and return transcoded file for 1915 * both file path and ContentResolver operations. This column tracks 1916 * the status of the transcoded file. 1917 * 1918 * @hide 1919 */ 1920 // @Column(value = Cursor.FIELD_TYPE_INTEGER) 1921 public static final String _TRANSCODE_STATUS = "_transcode_status"; 1922 1923 /** 1924 * Constant for the {@link #_TRANSCODE_STATUS} column indicating 1925 * that the transcode file if exists is empty or never transcoded. 1926 * @hide 1927 */ 1928 public static final int TRANSCODE_EMPTY = 0; 1929 1930 /** 1931 * Constant for the {@link #_TRANSCODE_STATUS} column indicating 1932 * that the transcode file if exists contains transcoded video. 1933 * @hide 1934 */ 1935 public static final int TRANSCODE_COMPLETE = 1; 1936 1937 /** 1938 * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_CODEC_TYPE} 1939 * extracted from the video file. This value be null for non-video files. 1940 * 1941 * @hide 1942 */ 1943 // @Column(value = Cursor.FIELD_TYPE_INTEGER) 1944 public static final String _VIDEO_CODEC_TYPE = "_video_codec_type"; 1945 1946 /** 1947 * Redacted Uri-ID corresponding to this DB entry. The value will be null if no 1948 * redacted uri has ever been created for this uri. 1949 * 1950 * @hide 1951 */ 1952 // @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1953 public static final String REDACTED_URI_ID = "redacted_uri_id"; 1954 1955 /** 1956 * Indexed value of {@link UserIdInt} to which the file belongs. 1957 * 1958 * @hide 1959 */ 1960 // @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1961 public static final String _USER_ID = "_user_id"; 1962 } 1963 } 1964 1965 /** @hide */ 1966 public static class ThumbnailConstants { 1967 public static final int MINI_KIND = 1; 1968 public static final int FULL_SCREEN_KIND = 2; 1969 public static final int MICRO_KIND = 3; 1970 1971 public static final Size MINI_SIZE = new Size(512, 384); 1972 public static final Size FULL_SCREEN_SIZE = new Size(1024, 786); 1973 public static final Size MICRO_SIZE = new Size(96, 96); 1974 getKindSize(int kind)1975 public static @NonNull Size getKindSize(int kind) { 1976 if (kind == ThumbnailConstants.MICRO_KIND) { 1977 return ThumbnailConstants.MICRO_SIZE; 1978 } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { 1979 return ThumbnailConstants.FULL_SCREEN_SIZE; 1980 } else if (kind == ThumbnailConstants.MINI_KIND) { 1981 return ThumbnailConstants.MINI_SIZE; 1982 } else { 1983 throw new IllegalArgumentException("Unsupported kind: " + kind); 1984 } 1985 } 1986 } 1987 1988 /** 1989 * Download metadata columns. 1990 */ 1991 public interface DownloadColumns extends MediaColumns { 1992 /** 1993 * Uri indicating where the item has been downloaded from. 1994 */ 1995 @Column(Cursor.FIELD_TYPE_STRING) 1996 String DOWNLOAD_URI = "download_uri"; 1997 1998 /** 1999 * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}. 2000 */ 2001 @Column(Cursor.FIELD_TYPE_STRING) 2002 String REFERER_URI = "referer_uri"; 2003 2004 /** 2005 * The description of the download. 2006 * 2007 * @removed 2008 */ 2009 @Deprecated 2010 @Column(Cursor.FIELD_TYPE_STRING) 2011 String DESCRIPTION = "description"; 2012 } 2013 2014 /** 2015 * Collection of downloaded items. 2016 */ 2017 public static final class Downloads implements DownloadColumns { Downloads()2018 private Downloads() {} 2019 2020 /** 2021 * The content:// style URI for the internal storage. 2022 */ 2023 @NonNull 2024 public static final Uri INTERNAL_CONTENT_URI = 2025 getContentUri("internal"); 2026 2027 /** 2028 * The content:// style URI for the "primary" external storage 2029 * volume. 2030 */ 2031 @NonNull 2032 public static final Uri EXTERNAL_CONTENT_URI = 2033 getContentUri("external"); 2034 2035 /** 2036 * The MIME type for this table. 2037 */ 2038 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download"; 2039 2040 /** 2041 * Get the content:// style URI for the downloads table on the 2042 * given volume. 2043 * 2044 * @param volumeName the name of the volume to get the URI for 2045 * @return the URI to the image media table on the given volume 2046 */ getContentUri(@onNull String volumeName)2047 public static @NonNull Uri getContentUri(@NonNull String volumeName) { 2048 return AUTHORITY_URI.buildUpon().appendPath(volumeName) 2049 .appendPath("downloads").build(); 2050 } 2051 2052 /** 2053 * Get the content:// style URI for a single row in the downloads table 2054 * on the given volume. 2055 * 2056 * @param volumeName the name of the volume to get the URI for 2057 * @param id the download to get the URI for 2058 * @return the URI to the downloads table on the given volume 2059 */ getContentUri(@onNull String volumeName, long id)2060 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2061 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2062 } 2063 2064 /** @hide */ getContentUriForPath(@onNull String path)2065 public static @NonNull Uri getContentUriForPath(@NonNull String path) { 2066 return getContentUri(getVolumeName(new File(path))); 2067 } 2068 } 2069 2070 /** 2071 * Regex that matches paths under well-known storage paths. 2072 * Copied from FileUtils.java 2073 */ 2074 private static final Pattern PATTERN_VOLUME_NAME = Pattern.compile( 2075 "(?i)^/storage/([^/]+)"); 2076 2077 /** 2078 * @deprecated since this method doesn't have a {@link Context}, we can't 2079 * find the actual {@link StorageVolume} for the given path, so 2080 * only a vague guess is returned. Callers should use 2081 * {@link StorageManager#getStorageVolume(File)} instead. 2082 * @hide 2083 */ 2084 @Deprecated getVolumeName(@onNull File path)2085 public static @NonNull String getVolumeName(@NonNull File path) { 2086 // Ideally we'd find the relevant StorageVolume, but we don't have a 2087 // Context to obtain it from, so the best we can do is assume 2088 // Borrowed the logic from FileUtils.extractVolumeName 2089 final Matcher matcher = PATTERN_VOLUME_NAME.matcher(path.getAbsolutePath()); 2090 if (matcher.find()) { 2091 final String volumeName = matcher.group(1); 2092 if (volumeName.equals("emulated")) { 2093 return MediaStore.VOLUME_EXTERNAL_PRIMARY; 2094 } else { 2095 return volumeName.toLowerCase(Locale.ROOT); 2096 } 2097 } else { 2098 return MediaStore.VOLUME_INTERNAL; 2099 } 2100 } 2101 2102 /** 2103 * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended 2104 * to be accessed elsewhere. 2105 */ 2106 @Deprecated 2107 private static class InternalThumbnails implements BaseColumns { 2108 /** 2109 * Currently outstanding thumbnail requests that can be cancelled. 2110 */ 2111 // @GuardedBy("sPending") 2112 private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>(); 2113 2114 /** 2115 * Make a blocking request to obtain the given thumbnail, generating it 2116 * if needed. 2117 * 2118 * @see #cancelThumbnail(ContentResolver, Uri) 2119 */ 2120 @Deprecated getThumbnail(@onNull ContentResolver cr, @NonNull Uri uri, int kind, @Nullable BitmapFactory.Options opts)2121 static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri, 2122 int kind, @Nullable BitmapFactory.Options opts) { 2123 final Size size = ThumbnailConstants.getKindSize(kind); 2124 2125 CancellationSignal signal = null; 2126 synchronized (sPending) { 2127 signal = sPending.get(uri); 2128 if (signal == null) { 2129 signal = new CancellationSignal(); 2130 sPending.put(uri, signal); 2131 } 2132 } 2133 2134 try { 2135 return cr.loadThumbnail(uri, size, signal); 2136 } catch (IOException e) { 2137 Log.w(TAG, "Failed to obtain thumbnail for " + uri, e); 2138 return null; 2139 } finally { 2140 synchronized (sPending) { 2141 sPending.remove(uri); 2142 } 2143 } 2144 } 2145 2146 /** 2147 * This method cancels the thumbnail request so clients waiting for 2148 * {@link #getThumbnail} will be interrupted and return immediately. 2149 * Only the original process which made the request can cancel their own 2150 * requests. 2151 */ 2152 @Deprecated cancelThumbnail(@onNull ContentResolver cr, @NonNull Uri uri)2153 static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) { 2154 synchronized (sPending) { 2155 final CancellationSignal signal = sPending.get(uri); 2156 if (signal != null) { 2157 signal.cancel(); 2158 } 2159 } 2160 } 2161 } 2162 2163 /** 2164 * Collection of all media with MIME type of {@code image/*}. 2165 */ 2166 public static final class Images { 2167 /** 2168 * Image metadata columns. 2169 */ 2170 public interface ImageColumns extends MediaColumns { 2171 /** 2172 * The picasa id of the image 2173 * 2174 * @deprecated this value was only relevant for images hosted on 2175 * Picasa, which are no longer supported. 2176 */ 2177 @Deprecated 2178 @Column(Cursor.FIELD_TYPE_STRING) 2179 public static final String PICASA_ID = "picasa_id"; 2180 2181 /** 2182 * Whether the image should be published as public or private 2183 */ 2184 @Column(Cursor.FIELD_TYPE_INTEGER) 2185 public static final String IS_PRIVATE = "isprivate"; 2186 2187 /** 2188 * The latitude where the image was captured. 2189 * 2190 * @deprecated location details are no longer indexed for privacy 2191 * reasons, and this value is now always {@code null}. 2192 * You can still manually obtain location metadata using 2193 * {@link ExifInterface#getLatLong(float[])}. 2194 */ 2195 @Deprecated 2196 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 2197 public static final String LATITUDE = "latitude"; 2198 2199 /** 2200 * The longitude where the image was captured. 2201 * 2202 * @deprecated location details are no longer indexed for privacy 2203 * reasons, and this value is now always {@code null}. 2204 * You can still manually obtain location metadata using 2205 * {@link ExifInterface#getLatLong(float[])}. 2206 */ 2207 @Deprecated 2208 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 2209 public static final String LONGITUDE = "longitude"; 2210 2211 /** @removed promoted to parent interface */ 2212 public static final String DATE_TAKEN = "datetaken"; 2213 /** @removed promoted to parent interface */ 2214 public static final String ORIENTATION = "orientation"; 2215 2216 /** 2217 * The mini thumb id. 2218 * 2219 * @deprecated all thumbnails should be obtained via 2220 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 2221 * value is no longer supported. 2222 */ 2223 @Deprecated 2224 @Column(Cursor.FIELD_TYPE_INTEGER) 2225 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 2226 2227 /** @removed promoted to parent interface */ 2228 public static final String BUCKET_ID = "bucket_id"; 2229 /** @removed promoted to parent interface */ 2230 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 2231 /** @removed promoted to parent interface */ 2232 public static final String GROUP_ID = "group_id"; 2233 2234 /** 2235 * Indexed value of {@link ExifInterface#TAG_IMAGE_DESCRIPTION} 2236 * extracted from this media item. 2237 */ 2238 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2239 public static final String DESCRIPTION = "description"; 2240 2241 /** 2242 * Indexed value of {@link ExifInterface#TAG_EXPOSURE_TIME} 2243 * extracted from this media item. 2244 */ 2245 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2246 public static final String EXPOSURE_TIME = "exposure_time"; 2247 2248 /** 2249 * Indexed value of {@link ExifInterface#TAG_F_NUMBER} 2250 * extracted from this media item. 2251 */ 2252 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2253 public static final String F_NUMBER = "f_number"; 2254 2255 /** 2256 * Indexed value of {@link ExifInterface#TAG_ISO_SPEED_RATINGS} 2257 * extracted from this media item. 2258 */ 2259 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2260 public static final String ISO = "iso"; 2261 2262 /** 2263 * Indexed value of {@link ExifInterface#TAG_SCENE_CAPTURE_TYPE} 2264 * extracted from this media item. 2265 */ 2266 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2267 public static final String SCENE_CAPTURE_TYPE = "scene_capture_type"; 2268 } 2269 2270 public static final class Media implements ImageColumns { 2271 /** 2272 * @deprecated all queries should be performed through 2273 * {@link ContentResolver} directly, which offers modern 2274 * features like {@link CancellationSignal}. 2275 */ 2276 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)2277 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 2278 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 2279 } 2280 2281 /** 2282 * @deprecated all queries should be performed through 2283 * {@link ContentResolver} directly, which offers modern 2284 * features like {@link CancellationSignal}. 2285 */ 2286 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)2287 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 2288 String where, String orderBy) { 2289 return cr.query(uri, projection, where, 2290 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2291 } 2292 2293 /** 2294 * @deprecated all queries should be performed through 2295 * {@link ContentResolver} directly, which offers modern 2296 * features like {@link CancellationSignal}. 2297 */ 2298 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)2299 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 2300 String selection, String [] selectionArgs, String orderBy) { 2301 return cr.query(uri, projection, selection, 2302 selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 2303 } 2304 2305 /** 2306 * Retrieves an image for the given url as a {@link Bitmap}. 2307 * 2308 * @param cr The content resolver to use 2309 * @param url The url of the image 2310 * @deprecated loading of images should be performed through 2311 * {@link ImageDecoder#createSource(ContentResolver, Uri)}, 2312 * which offers modern features like 2313 * {@link PostProcessor}. 2314 */ 2315 @Deprecated getBitmap(ContentResolver cr, Uri url)2316 public static final Bitmap getBitmap(ContentResolver cr, Uri url) 2317 throws FileNotFoundException, IOException { 2318 InputStream input = cr.openInputStream(url); 2319 Bitmap bitmap = BitmapFactory.decodeStream(input); 2320 input.close(); 2321 return bitmap; 2322 } 2323 2324 /** 2325 * Insert an image and create a thumbnail for it. 2326 * 2327 * @param cr The content resolver to use 2328 * @param imagePath The path to the image to insert 2329 * @param name The name of the image 2330 * @param description The description of the image 2331 * @return The URL to the newly created image 2332 * @deprecated inserting of images should be performed using 2333 * {@link MediaColumns#IS_PENDING}, which offers richer 2334 * control over lifecycle. 2335 */ 2336 @Deprecated insertImage(ContentResolver cr, String imagePath, String name, String description)2337 public static final String insertImage(ContentResolver cr, String imagePath, 2338 String name, String description) throws FileNotFoundException { 2339 final Bitmap source; 2340 try { 2341 source = ImageDecoder 2342 .decodeBitmap(ImageDecoder.createSource(new File(imagePath))); 2343 } catch (IOException e) { 2344 throw new FileNotFoundException(e.getMessage()); 2345 } 2346 return insertImage(cr, source, name, description); 2347 } 2348 2349 /** 2350 * Insert an image and create a thumbnail for it. 2351 * 2352 * @param cr The content resolver to use 2353 * @param source The stream to use for the image 2354 * @param title The name of the image 2355 * @param description The description of the image 2356 * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored 2357 * for any reason. 2358 * @deprecated inserting of images should be performed using 2359 * {@link MediaColumns#IS_PENDING}, which offers richer 2360 * control over lifecycle. 2361 */ 2362 @Deprecated insertImage(ContentResolver cr, Bitmap source, String title, String description)2363 public static final String insertImage(ContentResolver cr, Bitmap source, String title, 2364 String description) { 2365 if (TextUtils.isEmpty(title)) title = "Image"; 2366 2367 final long now = System.currentTimeMillis(); 2368 final ContentValues values = new ContentValues(); 2369 values.put(MediaColumns.DISPLAY_NAME, title); 2370 values.put(MediaColumns.MIME_TYPE, "image/jpeg"); 2371 values.put(MediaColumns.DATE_ADDED, now / 1000); 2372 values.put(MediaColumns.DATE_MODIFIED, now / 1000); 2373 values.put(MediaColumns.IS_PENDING, 1); 2374 2375 final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 2376 try { 2377 try (OutputStream out = cr.openOutputStream(uri)) { 2378 source.compress(Bitmap.CompressFormat.JPEG, 90, out); 2379 } 2380 2381 // Everything went well above, publish it! 2382 values.clear(); 2383 values.put(MediaColumns.IS_PENDING, 0); 2384 cr.update(uri, values, null, null); 2385 return uri.toString(); 2386 } catch (Exception e) { 2387 Log.w(TAG, "Failed to insert image", e); 2388 cr.delete(uri, null, null); 2389 return null; 2390 } 2391 } 2392 2393 /** 2394 * Get the content:// style URI for the image media table on the 2395 * given volume. 2396 * 2397 * @param volumeName the name of the volume to get the URI for 2398 * @return the URI to the image media table on the given volume 2399 */ getContentUri(String volumeName)2400 public static Uri getContentUri(String volumeName) { 2401 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 2402 .appendPath("media").build(); 2403 } 2404 2405 /** 2406 * Get the content:// style URI for a single row in the images table 2407 * on the given volume. 2408 * 2409 * @param volumeName the name of the volume to get the URI for 2410 * @param id the image to get the URI for 2411 * @return the URI to the images table on the given volume 2412 */ getContentUri(@onNull String volumeName, long id)2413 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2414 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2415 } 2416 2417 /** 2418 * The content:// style URI for the internal storage. 2419 */ 2420 public static final Uri INTERNAL_CONTENT_URI = 2421 getContentUri("internal"); 2422 2423 /** 2424 * The content:// style URI for the "primary" external storage 2425 * volume. 2426 */ 2427 public static final Uri EXTERNAL_CONTENT_URI = 2428 getContentUri("external"); 2429 2430 /** 2431 * The MIME type of this directory of 2432 * images. Note that each entry in this directory will have a standard 2433 * image MIME type as appropriate -- for example, image/jpeg. 2434 */ 2435 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image"; 2436 2437 /** 2438 * The default sort order for this table 2439 */ 2440 public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME; 2441 } 2442 2443 /** 2444 * This class provides utility methods to obtain thumbnails for various 2445 * {@link Images} items. 2446 * 2447 * @deprecated Callers should migrate to using 2448 * {@link ContentResolver#loadThumbnail}, since it offers 2449 * richer control over requested thumbnail sizes and 2450 * cancellation behavior. 2451 */ 2452 @Deprecated 2453 public static class Thumbnails implements BaseColumns { 2454 /** 2455 * @deprecated all queries should be performed through 2456 * {@link ContentResolver} directly, which offers modern 2457 * features like {@link CancellationSignal}. 2458 */ 2459 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)2460 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 2461 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 2462 } 2463 2464 /** 2465 * @deprecated all queries should be performed through 2466 * {@link ContentResolver} directly, which offers modern 2467 * features like {@link CancellationSignal}. 2468 */ 2469 @Deprecated queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)2470 public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, 2471 String[] projection) { 2472 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER); 2473 } 2474 2475 /** 2476 * @deprecated all queries should be performed through 2477 * {@link ContentResolver} directly, which offers modern 2478 * features like {@link CancellationSignal}. 2479 */ 2480 @Deprecated queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)2481 public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, 2482 String[] projection) { 2483 return cr.query(EXTERNAL_CONTENT_URI, projection, 2484 IMAGE_ID + " = " + origId + " AND " + KIND + " = " + 2485 kind, null, null); 2486 } 2487 2488 /** 2489 * Cancel any outstanding {@link #getThumbnail} requests, causing 2490 * them to return by throwing a {@link OperationCanceledException}. 2491 * <p> 2492 * This method has no effect on 2493 * {@link ContentResolver#loadThumbnail} calls, since they provide 2494 * their own {@link CancellationSignal}. 2495 * 2496 * @deprecated Callers should migrate to using 2497 * {@link ContentResolver#loadThumbnail}, since it 2498 * offers richer control over requested thumbnail sizes 2499 * and cancellation behavior. 2500 */ 2501 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)2502 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 2503 final Uri uri = ContentUris.withAppendedId( 2504 Images.Media.EXTERNAL_CONTENT_URI, origId); 2505 InternalThumbnails.cancelThumbnail(cr, uri); 2506 } 2507 2508 /** 2509 * Return thumbnail representing a specific image item. If a 2510 * thumbnail doesn't exist, this method will block until it's 2511 * generated. Callers are responsible for their own in-memory 2512 * caching of returned values. 2513 * 2514 * As of {@link android.os.Build.VERSION_CODES#Q}, this output 2515 * of the thumbnail has correct rotation, don't need to rotate 2516 * it again. 2517 * 2518 * @param imageId the image item to obtain a thumbnail for. 2519 * @param kind optimal thumbnail size desired. 2520 * @return decoded thumbnail, or {@code null} if problem was 2521 * encountered. 2522 * @deprecated Callers should migrate to using 2523 * {@link ContentResolver#loadThumbnail}, since it 2524 * offers richer control over requested thumbnail sizes 2525 * and cancellation behavior. 2526 */ 2527 @Deprecated getThumbnail(ContentResolver cr, long imageId, int kind, BitmapFactory.Options options)2528 public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind, 2529 BitmapFactory.Options options) { 2530 final Uri uri = ContentUris.withAppendedId( 2531 Images.Media.EXTERNAL_CONTENT_URI, imageId); 2532 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 2533 } 2534 2535 /** 2536 * Cancel any outstanding {@link #getThumbnail} requests, causing 2537 * them to return by throwing a {@link OperationCanceledException}. 2538 * <p> 2539 * This method has no effect on 2540 * {@link ContentResolver#loadThumbnail} calls, since they provide 2541 * their own {@link CancellationSignal}. 2542 * 2543 * @deprecated Callers should migrate to using 2544 * {@link ContentResolver#loadThumbnail}, since it 2545 * offers richer control over requested thumbnail sizes 2546 * and cancellation behavior. 2547 */ 2548 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)2549 public static void cancelThumbnailRequest(ContentResolver cr, long origId, 2550 long groupId) { 2551 cancelThumbnailRequest(cr, origId); 2552 } 2553 2554 /** 2555 * Return thumbnail representing a specific image item. If a 2556 * thumbnail doesn't exist, this method will block until it's 2557 * generated. Callers are responsible for their own in-memory 2558 * caching of returned values. 2559 * 2560 * As of {@link android.os.Build.VERSION_CODES#Q}, this output 2561 * of the thumbnail has correct rotation, don't need to rotate 2562 * it again. 2563 * 2564 * @param imageId the image item to obtain a thumbnail for. 2565 * @param kind optimal thumbnail size desired. 2566 * @return decoded thumbnail, or {@code null} if problem was 2567 * encountered. 2568 * @deprecated Callers should migrate to using 2569 * {@link ContentResolver#loadThumbnail}, since it 2570 * offers richer control over requested thumbnail sizes 2571 * and cancellation behavior. 2572 */ 2573 @Deprecated getThumbnail(ContentResolver cr, long imageId, long groupId, int kind, BitmapFactory.Options options)2574 public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId, 2575 int kind, BitmapFactory.Options options) { 2576 return getThumbnail(cr, imageId, kind, options); 2577 } 2578 2579 /** 2580 * Get the content:// style URI for the image media table on the 2581 * given volume. 2582 * 2583 * @param volumeName the name of the volume to get the URI for 2584 * @return the URI to the image media table on the given volume 2585 */ getContentUri(String volumeName)2586 public static Uri getContentUri(String volumeName) { 2587 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 2588 .appendPath("thumbnails").build(); 2589 } 2590 2591 /** 2592 * The content:// style URI for the internal storage. 2593 */ 2594 public static final Uri INTERNAL_CONTENT_URI = 2595 getContentUri("internal"); 2596 2597 /** 2598 * The content:// style URI for the "primary" external storage 2599 * volume. 2600 */ 2601 public static final Uri EXTERNAL_CONTENT_URI = 2602 getContentUri("external"); 2603 2604 /** 2605 * The default sort order for this table 2606 */ 2607 public static final String DEFAULT_SORT_ORDER = "image_id ASC"; 2608 2609 /** 2610 * Path to the thumbnail file on disk. 2611 * 2612 * As of {@link android.os.Build.VERSION_CODES#Q}, this thumbnail 2613 * has correct rotation, don't need to rotate it again. 2614 */ 2615 @Column(Cursor.FIELD_TYPE_STRING) 2616 public static final String DATA = "_data"; 2617 2618 /** 2619 * The original image for the thumbnal 2620 */ 2621 @Column(Cursor.FIELD_TYPE_INTEGER) 2622 public static final String IMAGE_ID = "image_id"; 2623 2624 /** 2625 * The kind of the thumbnail 2626 */ 2627 @Column(Cursor.FIELD_TYPE_INTEGER) 2628 public static final String KIND = "kind"; 2629 2630 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 2631 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 2632 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 2633 2634 /** 2635 * Return the typical {@link Size} (in pixels) used internally when 2636 * the given thumbnail kind is requested. 2637 * 2638 * @deprecated Callers should migrate to using 2639 * {@link ContentResolver#loadThumbnail}, since it 2640 * offers richer control over requested thumbnail sizes 2641 * and cancellation behavior. 2642 */ 2643 @Deprecated getKindSize(int kind)2644 public static @NonNull Size getKindSize(int kind) { 2645 return ThumbnailConstants.getKindSize(kind); 2646 } 2647 2648 /** 2649 * The blob raw data of thumbnail 2650 * 2651 * @deprecated this column never existed internally, and could never 2652 * have returned valid data. 2653 */ 2654 @Deprecated 2655 @Column(Cursor.FIELD_TYPE_BLOB) 2656 public static final String THUMB_DATA = "thumb_data"; 2657 2658 /** 2659 * The width of the thumbnal 2660 */ 2661 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2662 public static final String WIDTH = "width"; 2663 2664 /** 2665 * The height of the thumbnail 2666 */ 2667 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2668 public static final String HEIGHT = "height"; 2669 } 2670 } 2671 2672 /** 2673 * Collection of all media with MIME type of {@code audio/*}. 2674 */ 2675 public static final class Audio { 2676 /** 2677 * Audio metadata columns. 2678 */ 2679 public interface AudioColumns extends MediaColumns { 2680 2681 /** 2682 * A non human readable key calculated from the TITLE, used for 2683 * searching, sorting and grouping 2684 * 2685 * @see Audio#keyFor(String) 2686 * @deprecated These keys are generated using 2687 * {@link java.util.Locale#ROOT}, which means they don't 2688 * reflect locale-specific sorting preferences. To apply 2689 * locale-specific sorting preferences, use 2690 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2691 * {@code COLLATE LOCALIZED}, or 2692 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2693 */ 2694 @Deprecated 2695 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2696 public static final String TITLE_KEY = "title_key"; 2697 2698 /** @removed promoted to parent interface */ 2699 public static final String DURATION = "duration"; 2700 2701 /** 2702 * The position within the audio item at which playback should be 2703 * resumed. 2704 */ 2705 @DurationMillisLong 2706 @Column(Cursor.FIELD_TYPE_INTEGER) 2707 public static final String BOOKMARK = "bookmark"; 2708 2709 /** 2710 * The id of the artist who created the audio file, if any 2711 */ 2712 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2713 public static final String ARTIST_ID = "artist_id"; 2714 2715 /** @removed promoted to parent interface */ 2716 public static final String ARTIST = "artist"; 2717 2718 /** 2719 * The artist credited for the album that contains the audio file 2720 * @hide 2721 */ 2722 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2723 public static final String ALBUM_ARTIST = "album_artist"; 2724 2725 /** 2726 * A non human readable key calculated from the ARTIST, used for 2727 * searching, sorting and grouping 2728 * 2729 * @see Audio#keyFor(String) 2730 * @deprecated These keys are generated using 2731 * {@link java.util.Locale#ROOT}, which means they don't 2732 * reflect locale-specific sorting preferences. To apply 2733 * locale-specific sorting preferences, use 2734 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2735 * {@code COLLATE LOCALIZED}, or 2736 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2737 */ 2738 @Deprecated 2739 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2740 public static final String ARTIST_KEY = "artist_key"; 2741 2742 /** @removed promoted to parent interface */ 2743 public static final String COMPOSER = "composer"; 2744 2745 /** 2746 * The id of the album the audio file is from, if any 2747 */ 2748 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2749 public static final String ALBUM_ID = "album_id"; 2750 2751 /** @removed promoted to parent interface */ 2752 public static final String ALBUM = "album"; 2753 2754 /** 2755 * A non human readable key calculated from the ALBUM, used for 2756 * searching, sorting and grouping 2757 * 2758 * @see Audio#keyFor(String) 2759 * @deprecated These keys are generated using 2760 * {@link java.util.Locale#ROOT}, which means they don't 2761 * reflect locale-specific sorting preferences. To apply 2762 * locale-specific sorting preferences, use 2763 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2764 * {@code COLLATE LOCALIZED}, or 2765 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2766 */ 2767 @Deprecated 2768 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2769 public static final String ALBUM_KEY = "album_key"; 2770 2771 /** 2772 * The track number of this song on the album, if any. 2773 * This number encodes both the track number and the 2774 * disc number. For multi-disc sets, this number will 2775 * be 1xxx for tracks on the first disc, 2xxx for tracks 2776 * on the second disc, etc. 2777 */ 2778 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2779 public static final String TRACK = "track"; 2780 2781 /** 2782 * The year the audio file was recorded, if any 2783 */ 2784 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2785 public static final String YEAR = "year"; 2786 2787 /** 2788 * Non-zero if the audio file is music 2789 */ 2790 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2791 public static final String IS_MUSIC = "is_music"; 2792 2793 /** 2794 * Non-zero if the audio file is a podcast 2795 */ 2796 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2797 public static final String IS_PODCAST = "is_podcast"; 2798 2799 /** 2800 * Non-zero if the audio file may be a ringtone 2801 */ 2802 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2803 public static final String IS_RINGTONE = "is_ringtone"; 2804 2805 /** 2806 * Non-zero if the audio file may be an alarm 2807 */ 2808 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2809 public static final String IS_ALARM = "is_alarm"; 2810 2811 /** 2812 * Non-zero if the audio file may be a notification sound 2813 */ 2814 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2815 public static final String IS_NOTIFICATION = "is_notification"; 2816 2817 /** 2818 * Non-zero if the audio file is an audiobook 2819 */ 2820 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2821 public static final String IS_AUDIOBOOK = "is_audiobook"; 2822 2823 /** 2824 * Non-zero if the audio file is a voice recording recorded 2825 * by voice recorder apps 2826 */ 2827 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2828 public static final String IS_RECORDING = "is_recording"; 2829 2830 /** 2831 * The id of the genre the audio file is from, if any 2832 */ 2833 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2834 public static final String GENRE_ID = "genre_id"; 2835 2836 /** 2837 * The genre of the audio file, if any. 2838 */ 2839 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2840 public static final String GENRE = "genre"; 2841 2842 /** 2843 * A non human readable key calculated from the GENRE, used for 2844 * searching, sorting and grouping 2845 * 2846 * @see Audio#keyFor(String) 2847 * @deprecated These keys are generated using 2848 * {@link java.util.Locale#ROOT}, which means they don't 2849 * reflect locale-specific sorting preferences. To apply 2850 * locale-specific sorting preferences, use 2851 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2852 * {@code COLLATE LOCALIZED}, or 2853 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2854 */ 2855 @Deprecated 2856 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2857 public static final String GENRE_KEY = "genre_key"; 2858 2859 /** 2860 * The resource URI of a localized title, if any. 2861 * <p> 2862 * Conforms to this pattern: 2863 * <ul> 2864 * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE} 2865 * <li>Authority: Package Name of ringtone title provider 2866 * <li>First Path Segment: Type of resource (must be "string") 2867 * <li>Second Path Segment: Resource ID of title 2868 * </ul> 2869 */ 2870 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2871 public static final String TITLE_RESOURCE_URI = "title_resource_uri"; 2872 } 2873 2874 private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile( 2875 "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)"); 2876 private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile( 2877 "(^(00)+|(00)+$)"); 2878 2879 /** 2880 * Converts a user-visible string into a "key" that can be used for 2881 * grouping, sorting, and searching. 2882 * 2883 * @return Opaque token that should not be parsed or displayed to users. 2884 * @deprecated These keys are generated using 2885 * {@link java.util.Locale#ROOT}, which means they don't 2886 * reflect locale-specific sorting preferences. To apply 2887 * locale-specific sorting preferences, use 2888 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 2889 * {@code COLLATE LOCALIZED}, or 2890 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 2891 */ 2892 @Deprecated keyFor(@ullable String name)2893 public static @Nullable String keyFor(@Nullable String name) { 2894 if (TextUtils.isEmpty(name)) return ""; 2895 2896 if (UNKNOWN_STRING.equals(name)) { 2897 return "01"; 2898 } 2899 2900 final boolean sortFirst = name.startsWith("\001"); 2901 2902 name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll(""); 2903 if (TextUtils.isEmpty(name)) return ""; 2904 2905 final Collator c = Collator.getInstance(Locale.ROOT); 2906 c.setStrength(Collator.PRIMARY); 2907 name = encodeToString(c.getCollationKey(name).toByteArray()); 2908 2909 name = PATTERN_TRIM_AFTER.matcher(name).replaceAll(""); 2910 if (sortFirst) { 2911 name = "01" + name; 2912 } 2913 return name; 2914 } 2915 encodeToString(byte[] bytes)2916 private static String encodeToString(byte[] bytes) { 2917 final StringBuilder sb = new StringBuilder(); 2918 for (byte b : bytes) { 2919 sb.append(String.format("%02x", b)); 2920 } 2921 return sb.toString(); 2922 } 2923 2924 public static final class Media implements AudioColumns { 2925 /** 2926 * Get the content:// style URI for the audio media table on the 2927 * given volume. 2928 * 2929 * @param volumeName the name of the volume to get the URI for 2930 * @return the URI to the audio media table on the given volume 2931 */ getContentUri(String volumeName)2932 public static Uri getContentUri(String volumeName) { 2933 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2934 .appendPath("media").build(); 2935 } 2936 2937 /** 2938 * Get the content:// style URI for a single row in the audio table 2939 * on the given volume. 2940 * 2941 * @param volumeName the name of the volume to get the URI for 2942 * @param id the audio to get the URI for 2943 * @return the URI to the audio table on the given volume 2944 */ getContentUri(@onNull String volumeName, long id)2945 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2946 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2947 } 2948 2949 /** 2950 * Get the content:// style URI for the given audio media file. 2951 * 2952 * @deprecated Apps may not have filesystem permissions to directly 2953 * access this path. 2954 */ 2955 @Deprecated getContentUriForPath(@onNull String path)2956 public static @Nullable Uri getContentUriForPath(@NonNull String path) { 2957 return getContentUri(getVolumeName(new File(path))); 2958 } 2959 2960 /** 2961 * The content:// style URI for the internal storage. 2962 */ 2963 public static final Uri INTERNAL_CONTENT_URI = 2964 getContentUri("internal"); 2965 2966 /** 2967 * The content:// style URI for the "primary" external storage 2968 * volume. 2969 */ 2970 public static final Uri EXTERNAL_CONTENT_URI = 2971 getContentUri("external"); 2972 2973 /** 2974 * The MIME type for this table. 2975 */ 2976 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 2977 2978 /** 2979 * The MIME type for an audio track. 2980 */ 2981 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio"; 2982 2983 /** 2984 * The default sort order for this table 2985 */ 2986 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 2987 2988 /** 2989 * Activity Action: Start SoundRecorder application. 2990 * <p>Input: nothing. 2991 * <p>Output: An uri to the recorded sound stored in the Media Library 2992 * if the recording was successful. 2993 * May also contain the extra EXTRA_MAX_BYTES. 2994 * @see #EXTRA_MAX_BYTES 2995 */ 2996 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 2997 public static final String RECORD_SOUND_ACTION = 2998 "android.provider.MediaStore.RECORD_SOUND"; 2999 3000 /** 3001 * The name of the Intent-extra used to define a maximum file size for 3002 * a recording made by the SoundRecorder application. 3003 * 3004 * @see #RECORD_SOUND_ACTION 3005 */ 3006 public static final String EXTRA_MAX_BYTES = 3007 "android.provider.MediaStore.extra.MAX_BYTES"; 3008 } 3009 3010 /** 3011 * Audio genre metadata columns. 3012 */ 3013 public interface GenresColumns { 3014 /** 3015 * The name of the genre 3016 */ 3017 @Column(Cursor.FIELD_TYPE_STRING) 3018 public static final String NAME = "name"; 3019 } 3020 3021 /** 3022 * Contains all genres for audio files 3023 */ 3024 public static final class Genres implements BaseColumns, GenresColumns { 3025 /** 3026 * Get the content:// style URI for the audio genres table on the 3027 * given volume. 3028 * 3029 * @param volumeName the name of the volume to get the URI for 3030 * @return the URI to the audio genres table on the given volume 3031 */ getContentUri(String volumeName)3032 public static Uri getContentUri(String volumeName) { 3033 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3034 .appendPath("genres").build(); 3035 } 3036 3037 /** 3038 * Get the content:// style URI for querying the genres of an audio file. 3039 * 3040 * @param volumeName the name of the volume to get the URI for 3041 * @param audioId the ID of the audio file for which to retrieve the genres 3042 * @return the URI to for querying the genres for the audio file 3043 * with the given the volume and audioID 3044 */ getContentUriForAudioId(String volumeName, int audioId)3045 public static Uri getContentUriForAudioId(String volumeName, int audioId) { 3046 return ContentUris.withAppendedId(Audio.Media.getContentUri(volumeName), audioId) 3047 .buildUpon().appendPath("genres").build(); 3048 } 3049 3050 /** 3051 * The content:// style URI for the internal storage. 3052 */ 3053 public static final Uri INTERNAL_CONTENT_URI = 3054 getContentUri("internal"); 3055 3056 /** 3057 * The content:// style URI for the "primary" external storage 3058 * volume. 3059 */ 3060 public static final Uri EXTERNAL_CONTENT_URI = 3061 getContentUri("external"); 3062 3063 /** 3064 * The MIME type for this table. 3065 */ 3066 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre"; 3067 3068 /** 3069 * The MIME type for entries in this table. 3070 */ 3071 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre"; 3072 3073 /** 3074 * The default sort order for this table 3075 */ 3076 public static final String DEFAULT_SORT_ORDER = NAME; 3077 3078 /** 3079 * Sub-directory of each genre containing all members. 3080 */ 3081 public static final class Members implements AudioColumns { 3082 getContentUri(String volumeName, long genreId)3083 public static final Uri getContentUri(String volumeName, long genreId) { 3084 return ContentUris 3085 .withAppendedId(Audio.Genres.getContentUri(volumeName), genreId) 3086 .buildUpon().appendPath("members").build(); 3087 } 3088 3089 /** 3090 * A subdirectory of each genre containing all member audio files. 3091 */ 3092 public static final String CONTENT_DIRECTORY = "members"; 3093 3094 /** 3095 * The default sort order for this table 3096 */ 3097 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 3098 3099 /** 3100 * The ID of the audio file 3101 */ 3102 @Column(Cursor.FIELD_TYPE_INTEGER) 3103 public static final String AUDIO_ID = "audio_id"; 3104 3105 /** 3106 * The ID of the genre 3107 */ 3108 @Column(Cursor.FIELD_TYPE_INTEGER) 3109 public static final String GENRE_ID = "genre_id"; 3110 } 3111 } 3112 3113 /** 3114 * Audio playlist metadata columns. 3115 * 3116 * @deprecated Android playlists are now deprecated. We will keep the current 3117 * functionality for compatibility reasons, but we will no longer take 3118 * feature request. We do not advise adding new usages of Android Playlists. 3119 * M3U files can be used as an alternative. 3120 */ 3121 @Deprecated 3122 public interface PlaylistsColumns extends MediaColumns { 3123 /** 3124 * The name of the playlist 3125 */ 3126 @Column(Cursor.FIELD_TYPE_STRING) 3127 public static final String NAME = "name"; 3128 3129 /** 3130 * Path to the playlist file on disk. 3131 */ 3132 @Column(Cursor.FIELD_TYPE_STRING) 3133 public static final String DATA = "_data"; 3134 3135 /** 3136 * The time the media item was first added. 3137 */ 3138 @CurrentTimeSecondsLong 3139 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3140 public static final String DATE_ADDED = "date_added"; 3141 3142 /** 3143 * The time the media item was last modified. 3144 */ 3145 @CurrentTimeSecondsLong 3146 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3147 public static final String DATE_MODIFIED = "date_modified"; 3148 } 3149 3150 /** 3151 * Contains playlists for audio files 3152 * 3153 * @deprecated Android playlists are now deprecated. We will keep the current 3154 * functionality for compatibility resons, but we will no longer take 3155 * feature request. We do not advise adding new usages of Android Playlists. 3156 * M3U files can be used as an alternative. 3157 */ 3158 @Deprecated 3159 public static final class Playlists implements BaseColumns, 3160 PlaylistsColumns { 3161 /** 3162 * Get the content:// style URI for the audio playlists table on the 3163 * given volume. 3164 * 3165 * @param volumeName the name of the volume to get the URI for 3166 * @return the URI to the audio playlists table on the given volume 3167 */ getContentUri(String volumeName)3168 public static Uri getContentUri(String volumeName) { 3169 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3170 .appendPath("playlists").build(); 3171 } 3172 3173 /** 3174 * The content:// style URI for the internal storage. 3175 */ 3176 public static final Uri INTERNAL_CONTENT_URI = 3177 getContentUri("internal"); 3178 3179 /** 3180 * The content:// style URI for the "primary" external storage 3181 * volume. 3182 */ 3183 public static final Uri EXTERNAL_CONTENT_URI = 3184 getContentUri("external"); 3185 3186 /** 3187 * The MIME type for this table. 3188 */ 3189 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist"; 3190 3191 /** 3192 * The MIME type for entries in this table. 3193 */ 3194 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist"; 3195 3196 /** 3197 * The default sort order for this table 3198 */ 3199 public static final String DEFAULT_SORT_ORDER = NAME; 3200 3201 /** 3202 * Sub-directory of each playlist containing all members. 3203 */ 3204 public static final class Members implements AudioColumns { getContentUri(String volumeName, long playlistId)3205 public static final Uri getContentUri(String volumeName, long playlistId) { 3206 return ContentUris 3207 .withAppendedId(Audio.Playlists.getContentUri(volumeName), playlistId) 3208 .buildUpon().appendPath("members").build(); 3209 } 3210 3211 /** 3212 * Convenience method to move a playlist item to a new location 3213 * @param res The content resolver to use 3214 * @param playlistId The numeric id of the playlist 3215 * @param from The position of the item to move 3216 * @param to The position to move the item to 3217 * @return true on success 3218 */ moveItem(ContentResolver res, long playlistId, int from, int to)3219 public static final boolean moveItem(ContentResolver res, 3220 long playlistId, int from, int to) { 3221 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 3222 playlistId) 3223 .buildUpon() 3224 .appendEncodedPath(String.valueOf(from)) 3225 .appendQueryParameter("move", "true") 3226 .build(); 3227 ContentValues values = new ContentValues(); 3228 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to); 3229 return res.update(uri, values, null, null) != 0; 3230 } 3231 3232 /** 3233 * The ID within the playlist. 3234 */ 3235 @Column(Cursor.FIELD_TYPE_INTEGER) 3236 public static final String _ID = "_id"; 3237 3238 /** 3239 * A subdirectory of each playlist containing all member audio 3240 * files. 3241 */ 3242 public static final String CONTENT_DIRECTORY = "members"; 3243 3244 /** 3245 * The ID of the audio file 3246 */ 3247 @Column(Cursor.FIELD_TYPE_INTEGER) 3248 public static final String AUDIO_ID = "audio_id"; 3249 3250 /** 3251 * The ID of the playlist 3252 */ 3253 @Column(Cursor.FIELD_TYPE_INTEGER) 3254 public static final String PLAYLIST_ID = "playlist_id"; 3255 3256 /** 3257 * The order of the songs in the playlist 3258 */ 3259 @Column(Cursor.FIELD_TYPE_INTEGER) 3260 public static final String PLAY_ORDER = "play_order"; 3261 3262 /** 3263 * The default sort order for this table 3264 */ 3265 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER; 3266 } 3267 } 3268 3269 /** 3270 * Audio artist metadata columns. 3271 */ 3272 public interface ArtistColumns { 3273 /** 3274 * The artist who created the audio file, if any 3275 */ 3276 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3277 public static final String ARTIST = "artist"; 3278 3279 /** 3280 * A non human readable key calculated from the ARTIST, used for 3281 * searching, sorting and grouping 3282 * 3283 * @see Audio#keyFor(String) 3284 * @deprecated These keys are generated using 3285 * {@link java.util.Locale#ROOT}, which means they don't 3286 * reflect locale-specific sorting preferences. To apply 3287 * locale-specific sorting preferences, use 3288 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 3289 * {@code COLLATE LOCALIZED}, or 3290 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 3291 */ 3292 @Deprecated 3293 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3294 public static final String ARTIST_KEY = "artist_key"; 3295 3296 /** 3297 * The number of albums in the database for this artist 3298 */ 3299 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3300 public static final String NUMBER_OF_ALBUMS = "number_of_albums"; 3301 3302 /** 3303 * The number of albums in the database for this artist 3304 */ 3305 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3306 public static final String NUMBER_OF_TRACKS = "number_of_tracks"; 3307 } 3308 3309 /** 3310 * Contains artists for audio files 3311 */ 3312 public static final class Artists implements BaseColumns, ArtistColumns { 3313 /** 3314 * Get the content:// style URI for the artists table on the 3315 * given volume. 3316 * 3317 * @param volumeName the name of the volume to get the URI for 3318 * @return the URI to the audio artists table on the given volume 3319 */ getContentUri(String volumeName)3320 public static Uri getContentUri(String volumeName) { 3321 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3322 .appendPath("artists").build(); 3323 } 3324 3325 /** 3326 * The content:// style URI for the internal storage. 3327 */ 3328 public static final Uri INTERNAL_CONTENT_URI = 3329 getContentUri("internal"); 3330 3331 /** 3332 * The content:// style URI for the "primary" external storage 3333 * volume. 3334 */ 3335 public static final Uri EXTERNAL_CONTENT_URI = 3336 getContentUri("external"); 3337 3338 /** 3339 * The MIME type for this table. 3340 */ 3341 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists"; 3342 3343 /** 3344 * The MIME type for entries in this table. 3345 */ 3346 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist"; 3347 3348 /** 3349 * The default sort order for this table 3350 */ 3351 public static final String DEFAULT_SORT_ORDER = ARTIST_KEY; 3352 3353 /** 3354 * Sub-directory of each artist containing all albums on which 3355 * a song by the artist appears. 3356 */ 3357 public static final class Albums implements BaseColumns, AlbumColumns { getContentUri(String volumeName,long artistId)3358 public static final Uri getContentUri(String volumeName,long artistId) { 3359 return ContentUris 3360 .withAppendedId(Audio.Artists.getContentUri(volumeName), artistId) 3361 .buildUpon().appendPath("albums").build(); 3362 } 3363 } 3364 } 3365 3366 /** 3367 * Audio album metadata columns. 3368 */ 3369 public interface AlbumColumns { 3370 3371 /** 3372 * The id for the album 3373 */ 3374 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3375 public static final String ALBUM_ID = "album_id"; 3376 3377 /** 3378 * The album on which the audio file appears, if any 3379 */ 3380 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3381 public static final String ALBUM = "album"; 3382 3383 /** 3384 * The ID of the artist whose songs appear on this album. 3385 */ 3386 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3387 public static final String ARTIST_ID = "artist_id"; 3388 3389 /** 3390 * The name of the artist whose songs appear on this album. 3391 */ 3392 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3393 public static final String ARTIST = "artist"; 3394 3395 /** 3396 * A non human readable key calculated from the ARTIST, used for 3397 * searching, sorting and grouping 3398 * 3399 * @see Audio#keyFor(String) 3400 * @deprecated These keys are generated using 3401 * {@link java.util.Locale#ROOT}, which means they don't 3402 * reflect locale-specific sorting preferences. To apply 3403 * locale-specific sorting preferences, use 3404 * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with 3405 * {@code COLLATE LOCALIZED}, or 3406 * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}. 3407 */ 3408 @Deprecated 3409 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3410 public static final String ARTIST_KEY = "artist_key"; 3411 3412 /** 3413 * The number of songs on this album 3414 */ 3415 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3416 public static final String NUMBER_OF_SONGS = "numsongs"; 3417 3418 /** 3419 * This column is available when getting album info via artist, 3420 * and indicates the number of songs on the album by the given 3421 * artist. 3422 */ 3423 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3424 public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; 3425 3426 /** 3427 * The year in which the earliest songs 3428 * on this album were released. This will often 3429 * be the same as {@link #LAST_YEAR}, but for compilation albums 3430 * they might differ. 3431 */ 3432 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3433 public static final String FIRST_YEAR = "minyear"; 3434 3435 /** 3436 * The year in which the latest songs 3437 * on this album were released. This will often 3438 * be the same as {@link #FIRST_YEAR}, but for compilation albums 3439 * they might differ. 3440 */ 3441 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3442 public static final String LAST_YEAR = "maxyear"; 3443 3444 /** 3445 * A non human readable key calculated from the ALBUM, 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 ALBUM_KEY = "album_key"; 3460 3461 /** 3462 * Cached album art. 3463 * 3464 * @deprecated Apps may not have filesystem permissions to directly 3465 * access this path. Instead of trying to open this path 3466 * directly, apps should use 3467 * {@link ContentResolver#loadThumbnail} 3468 * to gain access. 3469 */ 3470 @Deprecated 3471 @Column(Cursor.FIELD_TYPE_STRING) 3472 public static final String ALBUM_ART = "album_art"; 3473 } 3474 3475 /** 3476 * Contains artists for audio files 3477 */ 3478 public static final class Albums implements BaseColumns, AlbumColumns { 3479 /** 3480 * Get the content:// style URI for the albums table on the 3481 * given volume. 3482 * 3483 * @param volumeName the name of the volume to get the URI for 3484 * @return the URI to the audio albums table on the given volume 3485 */ getContentUri(String volumeName)3486 public static Uri getContentUri(String volumeName) { 3487 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 3488 .appendPath("albums").build(); 3489 } 3490 3491 /** 3492 * The content:// style URI for the internal storage. 3493 */ 3494 public static final Uri INTERNAL_CONTENT_URI = 3495 getContentUri("internal"); 3496 3497 /** 3498 * The content:// style URI for the "primary" external storage 3499 * volume. 3500 */ 3501 public static final Uri EXTERNAL_CONTENT_URI = 3502 getContentUri("external"); 3503 3504 /** 3505 * The MIME type for this table. 3506 */ 3507 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums"; 3508 3509 /** 3510 * The MIME type for entries in this table. 3511 */ 3512 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album"; 3513 3514 /** 3515 * The default sort order for this table 3516 */ 3517 public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; 3518 } 3519 3520 public static final class Radio { 3521 /** 3522 * The MIME type for entries in this table. 3523 */ 3524 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; 3525 3526 // Not instantiable. Radio()3527 private Radio() { } 3528 } 3529 3530 /** 3531 * This class provides utility methods to obtain thumbnails for various 3532 * {@link Audio} items. 3533 * 3534 * @deprecated Callers should migrate to using 3535 * {@link ContentResolver#loadThumbnail}, since it offers 3536 * richer control over requested thumbnail sizes and 3537 * cancellation behavior. 3538 * @hide 3539 */ 3540 @Deprecated 3541 public static class Thumbnails implements BaseColumns { 3542 /** 3543 * Path to the thumbnail file on disk. 3544 */ 3545 @Column(Cursor.FIELD_TYPE_STRING) 3546 public static final String DATA = "_data"; 3547 3548 @Column(Cursor.FIELD_TYPE_INTEGER) 3549 public static final String ALBUM_ID = "album_id"; 3550 } 3551 } 3552 3553 /** 3554 * Collection of all media with MIME type of {@code video/*}. 3555 */ 3556 public static final class Video { 3557 3558 /** 3559 * The default sort order for this table. 3560 */ 3561 public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME; 3562 3563 /** 3564 * @deprecated all queries should be performed through 3565 * {@link ContentResolver} directly, which offers modern 3566 * features like {@link CancellationSignal}. 3567 */ 3568 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)3569 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 3570 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 3571 } 3572 3573 /** 3574 * Video metadata columns. 3575 */ 3576 public interface VideoColumns extends MediaColumns { 3577 /** @removed promoted to parent interface */ 3578 public static final String DURATION = "duration"; 3579 /** @removed promoted to parent interface */ 3580 public static final String ARTIST = "artist"; 3581 /** @removed promoted to parent interface */ 3582 public static final String ALBUM = "album"; 3583 /** @removed promoted to parent interface */ 3584 public static final String RESOLUTION = "resolution"; 3585 3586 /** 3587 * The description of the video recording 3588 */ 3589 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 3590 public static final String DESCRIPTION = "description"; 3591 3592 /** 3593 * Whether the video should be published as public or private 3594 */ 3595 @Column(Cursor.FIELD_TYPE_INTEGER) 3596 public static final String IS_PRIVATE = "isprivate"; 3597 3598 /** 3599 * The user-added tags associated with a video 3600 */ 3601 @Column(Cursor.FIELD_TYPE_STRING) 3602 public static final String TAGS = "tags"; 3603 3604 /** 3605 * The YouTube category of the video 3606 */ 3607 @Column(Cursor.FIELD_TYPE_STRING) 3608 public static final String CATEGORY = "category"; 3609 3610 /** 3611 * The language of the video 3612 */ 3613 @Column(Cursor.FIELD_TYPE_STRING) 3614 public static final String LANGUAGE = "language"; 3615 3616 /** 3617 * The latitude where the video was captured. 3618 * 3619 * @deprecated location details are no longer indexed for privacy 3620 * reasons, and this value is now always {@code null}. 3621 * You can still manually obtain location metadata using 3622 * {@link MediaMetadataRetriever#METADATA_KEY_LOCATION}. 3623 */ 3624 @Deprecated 3625 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 3626 public static final String LATITUDE = "latitude"; 3627 3628 /** 3629 * The longitude where the video was captured. 3630 * 3631 * @deprecated location details are no longer indexed for privacy 3632 * reasons, and this value is now always {@code null}. 3633 * You can still manually obtain location metadata using 3634 * {@link MediaMetadataRetriever#METADATA_KEY_LOCATION}. 3635 */ 3636 @Deprecated 3637 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 3638 public static final String LONGITUDE = "longitude"; 3639 3640 /** @removed promoted to parent interface */ 3641 public static final String DATE_TAKEN = "datetaken"; 3642 3643 /** 3644 * The mini thumb id. 3645 * 3646 * @deprecated all thumbnails should be obtained via 3647 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 3648 * value is no longer supported. 3649 */ 3650 @Deprecated 3651 @Column(Cursor.FIELD_TYPE_INTEGER) 3652 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 3653 3654 /** @removed promoted to parent interface */ 3655 public static final String BUCKET_ID = "bucket_id"; 3656 /** @removed promoted to parent interface */ 3657 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 3658 /** @removed promoted to parent interface */ 3659 public static final String GROUP_ID = "group_id"; 3660 3661 /** 3662 * The position within the video item at which playback should be 3663 * resumed. 3664 */ 3665 @DurationMillisLong 3666 @Column(Cursor.FIELD_TYPE_INTEGER) 3667 public static final String BOOKMARK = "bookmark"; 3668 3669 /** 3670 * The color standard of this media file, if available. 3671 * 3672 * @see MediaFormat#COLOR_STANDARD_BT709 3673 * @see MediaFormat#COLOR_STANDARD_BT601_PAL 3674 * @see MediaFormat#COLOR_STANDARD_BT601_NTSC 3675 * @see MediaFormat#COLOR_STANDARD_BT2020 3676 */ 3677 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3678 public static final String COLOR_STANDARD = "color_standard"; 3679 3680 /** 3681 * The color transfer of this media file, if available. 3682 * 3683 * @see MediaFormat#COLOR_TRANSFER_LINEAR 3684 * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO 3685 * @see MediaFormat#COLOR_TRANSFER_ST2084 3686 * @see MediaFormat#COLOR_TRANSFER_HLG 3687 */ 3688 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3689 public static final String COLOR_TRANSFER = "color_transfer"; 3690 3691 /** 3692 * The color range of this media file, if available. 3693 * 3694 * @see MediaFormat#COLOR_RANGE_LIMITED 3695 * @see MediaFormat#COLOR_RANGE_FULL 3696 */ 3697 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3698 public static final String COLOR_RANGE = "color_range"; 3699 } 3700 3701 public static final class Media implements VideoColumns { 3702 /** 3703 * Get the content:// style URI for the video media table on the 3704 * given volume. 3705 * 3706 * @param volumeName the name of the volume to get the URI for 3707 * @return the URI to the video media table on the given volume 3708 */ getContentUri(String volumeName)3709 public static Uri getContentUri(String volumeName) { 3710 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3711 .appendPath("media").build(); 3712 } 3713 3714 /** 3715 * Get the content:// style URI for a single row in the videos table 3716 * on the given volume. 3717 * 3718 * @param volumeName the name of the volume to get the URI for 3719 * @param id the video to get the URI for 3720 * @return the URI to the videos table on the given volume 3721 */ getContentUri(@onNull String volumeName, long id)3722 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 3723 return ContentUris.withAppendedId(getContentUri(volumeName), id); 3724 } 3725 3726 /** 3727 * The content:// style URI for the internal storage. 3728 */ 3729 public static final Uri INTERNAL_CONTENT_URI = 3730 getContentUri("internal"); 3731 3732 /** 3733 * The content:// style URI for the "primary" external storage 3734 * volume. 3735 */ 3736 public static final Uri EXTERNAL_CONTENT_URI = 3737 getContentUri("external"); 3738 3739 /** 3740 * The MIME type for this table. 3741 */ 3742 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 3743 3744 /** 3745 * The default sort order for this table 3746 */ 3747 public static final String DEFAULT_SORT_ORDER = TITLE; 3748 } 3749 3750 /** 3751 * This class provides utility methods to obtain thumbnails for various 3752 * {@link Video} items. 3753 * 3754 * @deprecated Callers should migrate to using 3755 * {@link ContentResolver#loadThumbnail}, since it offers 3756 * richer control over requested thumbnail sizes and 3757 * cancellation behavior. 3758 */ 3759 @Deprecated 3760 public static class Thumbnails implements BaseColumns { 3761 /** 3762 * Cancel any outstanding {@link #getThumbnail} requests, causing 3763 * them to return by throwing a {@link OperationCanceledException}. 3764 * <p> 3765 * This method has no effect on 3766 * {@link ContentResolver#loadThumbnail} calls, since they provide 3767 * their own {@link CancellationSignal}. 3768 * 3769 * @deprecated Callers should migrate to using 3770 * {@link ContentResolver#loadThumbnail}, since it 3771 * offers richer control over requested thumbnail sizes 3772 * and cancellation behavior. 3773 */ 3774 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)3775 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 3776 final Uri uri = ContentUris.withAppendedId( 3777 Video.Media.EXTERNAL_CONTENT_URI, origId); 3778 InternalThumbnails.cancelThumbnail(cr, uri); 3779 } 3780 3781 /** 3782 * Return thumbnail representing a specific video item. If a 3783 * thumbnail doesn't exist, this method will block until it's 3784 * generated. Callers are responsible for their own in-memory 3785 * caching of returned values. 3786 * 3787 * @param videoId the video item to obtain a thumbnail for. 3788 * @param kind optimal thumbnail size desired. 3789 * @return decoded thumbnail, or {@code null} if problem was 3790 * encountered. 3791 * @deprecated Callers should migrate to using 3792 * {@link ContentResolver#loadThumbnail}, since it 3793 * offers richer control over requested thumbnail sizes 3794 * and cancellation behavior. 3795 */ 3796 @Deprecated getThumbnail(ContentResolver cr, long videoId, int kind, BitmapFactory.Options options)3797 public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind, 3798 BitmapFactory.Options options) { 3799 final Uri uri = ContentUris.withAppendedId( 3800 Video.Media.EXTERNAL_CONTENT_URI, videoId); 3801 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 3802 } 3803 3804 /** 3805 * Cancel any outstanding {@link #getThumbnail} requests, causing 3806 * them to return by throwing a {@link OperationCanceledException}. 3807 * <p> 3808 * This method has no effect on 3809 * {@link ContentResolver#loadThumbnail} calls, since they provide 3810 * their own {@link CancellationSignal}. 3811 * 3812 * @deprecated Callers should migrate to using 3813 * {@link ContentResolver#loadThumbnail}, since it 3814 * offers richer control over requested thumbnail sizes 3815 * and cancellation behavior. 3816 */ 3817 @Deprecated cancelThumbnailRequest(ContentResolver cr, long videoId, long groupId)3818 public static void cancelThumbnailRequest(ContentResolver cr, long videoId, 3819 long groupId) { 3820 cancelThumbnailRequest(cr, videoId); 3821 } 3822 3823 /** 3824 * Return thumbnail representing a specific video item. If a 3825 * thumbnail doesn't exist, this method will block until it's 3826 * generated. Callers are responsible for their own in-memory 3827 * caching of returned values. 3828 * 3829 * @param videoId the video item to obtain a thumbnail for. 3830 * @param kind optimal thumbnail size desired. 3831 * @return decoded thumbnail, or {@code null} if problem was 3832 * encountered. 3833 * @deprecated Callers should migrate to using 3834 * {@link ContentResolver#loadThumbnail}, since it 3835 * offers richer control over requested thumbnail sizes 3836 * and cancellation behavior. 3837 */ 3838 @Deprecated getThumbnail(ContentResolver cr, long videoId, long groupId, int kind, BitmapFactory.Options options)3839 public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId, 3840 int kind, BitmapFactory.Options options) { 3841 return getThumbnail(cr, videoId, kind, options); 3842 } 3843 3844 /** 3845 * Get the content:// style URI for the image media table on the 3846 * given volume. 3847 * 3848 * @param volumeName the name of the volume to get the URI for 3849 * @return the URI to the image media table on the given volume 3850 */ getContentUri(String volumeName)3851 public static Uri getContentUri(String volumeName) { 3852 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3853 .appendPath("thumbnails").build(); 3854 } 3855 3856 /** 3857 * The content:// style URI for the internal storage. 3858 */ 3859 public static final Uri INTERNAL_CONTENT_URI = 3860 getContentUri("internal"); 3861 3862 /** 3863 * The content:// style URI for the "primary" external storage 3864 * volume. 3865 */ 3866 public static final Uri EXTERNAL_CONTENT_URI = 3867 getContentUri("external"); 3868 3869 /** 3870 * The default sort order for this table 3871 */ 3872 public static final String DEFAULT_SORT_ORDER = "video_id ASC"; 3873 3874 /** 3875 * Path to the thumbnail file on disk. 3876 */ 3877 @Column(Cursor.FIELD_TYPE_STRING) 3878 public static final String DATA = "_data"; 3879 3880 /** 3881 * The original image for the thumbnal 3882 */ 3883 @Column(Cursor.FIELD_TYPE_INTEGER) 3884 public static final String VIDEO_ID = "video_id"; 3885 3886 /** 3887 * The kind of the thumbnail 3888 */ 3889 @Column(Cursor.FIELD_TYPE_INTEGER) 3890 public static final String KIND = "kind"; 3891 3892 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 3893 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 3894 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 3895 3896 /** 3897 * Return the typical {@link Size} (in pixels) used internally when 3898 * the given thumbnail kind is requested. 3899 * 3900 * @deprecated Callers should migrate to using 3901 * {@link ContentResolver#loadThumbnail}, since it 3902 * offers richer control over requested thumbnail sizes 3903 * and cancellation behavior. 3904 */ 3905 @Deprecated getKindSize(int kind)3906 public static @NonNull Size getKindSize(int kind) { 3907 return ThumbnailConstants.getKindSize(kind); 3908 } 3909 3910 /** 3911 * The width of the thumbnal 3912 */ 3913 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3914 public static final String WIDTH = "width"; 3915 3916 /** 3917 * The height of the thumbnail 3918 */ 3919 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3920 public static final String HEIGHT = "height"; 3921 } 3922 } 3923 3924 /** 3925 * Return list of all specific volume names that make up 3926 * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each 3927 * shared storage device that is currently attached, which typically 3928 * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}. 3929 * <p> 3930 * Each specific volume name can be passed to APIs like 3931 * {@link MediaStore.Images.Media#getContentUri(String)} to interact with 3932 * media on that storage device. 3933 */ getExternalVolumeNames(@onNull Context context)3934 public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) { 3935 final StorageManager sm = context.getSystemService(StorageManager.class); 3936 final Set<String> res = new ArraySet<>(); 3937 for (StorageVolume sv : sm.getStorageVolumes()) { 3938 Log.v(TAG, "Examining volume " + sv.getId() + " with name " 3939 + sv.getMediaStoreVolumeName() + " and state " + sv.getState()); 3940 switch (sv.getState()) { 3941 case Environment.MEDIA_MOUNTED: 3942 case Environment.MEDIA_MOUNTED_READ_ONLY: { 3943 final String volumeName = sv.getMediaStoreVolumeName(); 3944 if (volumeName != null) { 3945 res.add(volumeName); 3946 } 3947 break; 3948 } 3949 } 3950 } 3951 return res; 3952 } 3953 3954 /** 3955 * Return list of all recent volume names that have been part of 3956 * {@link #VOLUME_EXTERNAL}. 3957 * <p> 3958 * These volume names are not currently mounted, but they're likely to 3959 * reappear in the future, so apps are encouraged to preserve any indexed 3960 * metadata related to these volumes to optimize user experiences. 3961 * <p> 3962 * Each specific volume name can be passed to APIs like 3963 * {@link MediaStore.Images.Media#getContentUri(String)} to interact with 3964 * media on that storage device. 3965 */ getRecentExternalVolumeNames(@onNull Context context)3966 public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) { 3967 final StorageManager sm = context.getSystemService(StorageManager.class); 3968 final Set<String> res = new ArraySet<>(); 3969 for (StorageVolume sv : sm.getRecentStorageVolumes()) { 3970 final String volumeName = sv.getMediaStoreVolumeName(); 3971 if (volumeName != null) { 3972 res.add(volumeName); 3973 } 3974 } 3975 return res; 3976 } 3977 3978 /** 3979 * Return the volume name that the given {@link Uri} references. 3980 */ getVolumeName(@onNull Uri uri)3981 public static @NonNull String getVolumeName(@NonNull Uri uri) { 3982 final List<String> segments = uri.getPathSegments(); 3983 switch (uri.getAuthority()) { 3984 case AUTHORITY: 3985 case AUTHORITY_LEGACY: { 3986 if (segments != null && segments.size() > 0) { 3987 return segments.get(0); 3988 } 3989 } 3990 } 3991 throw new IllegalArgumentException("Missing volume name: " + uri); 3992 } 3993 3994 /** {@hide} */ checkArgumentVolumeName(@onNull String volumeName)3995 public static @NonNull String checkArgumentVolumeName(@NonNull String volumeName) { 3996 if (TextUtils.isEmpty(volumeName)) { 3997 throw new IllegalArgumentException(); 3998 } 3999 4000 if (VOLUME_INTERNAL.equals(volumeName)) { 4001 return volumeName; 4002 } else if (VOLUME_EXTERNAL.equals(volumeName)) { 4003 return volumeName; 4004 } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) { 4005 return volumeName; 4006 } else if (VOLUME_DEMO.equals(volumeName)) { 4007 return volumeName; 4008 } 4009 4010 // When not one of the well-known values above, it must be a hex UUID 4011 for (int i = 0; i < volumeName.length(); i++) { 4012 final char c = volumeName.charAt(i); 4013 if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) { 4014 continue; 4015 } else { 4016 throw new IllegalArgumentException("Invalid volume name: " + volumeName); 4017 } 4018 } 4019 return volumeName; 4020 } 4021 4022 /** 4023 * Uri for querying the state of the media scanner. 4024 */ getMediaScannerUri()4025 public static Uri getMediaScannerUri() { 4026 return AUTHORITY_URI.buildUpon().appendPath("none").appendPath("media_scanner").build(); 4027 } 4028 4029 /** 4030 * Name of current volume being scanned by the media scanner. 4031 */ 4032 public static final String MEDIA_SCANNER_VOLUME = "volume"; 4033 4034 /** 4035 * Name of the file signaling the media scanner to ignore media in the containing directory 4036 * and its subdirectories. Developers should use this to avoid application graphics showing 4037 * up in the Gallery and likewise prevent application sounds and music from showing up in 4038 * the Music app. 4039 */ 4040 public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; 4041 4042 /** 4043 * Return an opaque version string describing the {@link MediaStore} state. 4044 * <p> 4045 * Applications that import data from {@link MediaStore} into their own 4046 * caches can use this to detect that {@link MediaStore} has undergone 4047 * substantial changes, and that data should be rescanned. 4048 * <p> 4049 * No other assumptions should be made about the meaning of the version. 4050 * <p> 4051 * This method returns the version for 4052 * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a 4053 * different volume, use {@link #getVersion(Context, String)}. 4054 */ getVersion(@onNull Context context)4055 public static @NonNull String getVersion(@NonNull Context context) { 4056 return getVersion(context, VOLUME_EXTERNAL_PRIMARY); 4057 } 4058 4059 /** 4060 * Return an opaque version string describing the {@link MediaStore} state. 4061 * <p> 4062 * Applications that import data from {@link MediaStore} into their own 4063 * caches can use this to detect that {@link MediaStore} has undergone 4064 * substantial changes, and that data should be rescanned. 4065 * <p> 4066 * No other assumptions should be made about the meaning of the version. 4067 * 4068 * @param volumeName specific volume to obtain an opaque version string for. 4069 * Must be one of the values returned from 4070 * {@link #getExternalVolumeNames(Context)}. 4071 */ getVersion(@onNull Context context, @NonNull String volumeName)4072 public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) { 4073 final ContentResolver resolver = context.getContentResolver(); 4074 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 4075 final Bundle in = new Bundle(); 4076 in.putString(Intent.EXTRA_TEXT, volumeName); 4077 final Bundle out = client.call(GET_VERSION_CALL, null, in); 4078 return out.getString(Intent.EXTRA_TEXT); 4079 } catch (RemoteException e) { 4080 throw e.rethrowAsRuntimeException(); 4081 } 4082 } 4083 4084 /** 4085 * Return the latest generation value for the given volume. 4086 * <p> 4087 * Generation numbers are useful for apps that are attempting to quickly 4088 * identify exactly which media items have been added or changed since a 4089 * previous point in time. Generation numbers are monotonically increasing 4090 * over time, and can be safely arithmetically compared. 4091 * <p> 4092 * Detecting media changes using generation numbers is more robust than 4093 * using {@link MediaColumns#DATE_ADDED} or 4094 * {@link MediaColumns#DATE_MODIFIED}, since those values may change in 4095 * unexpected ways when apps use {@link File#setLastModified(long)} or when 4096 * the system clock is set incorrectly. 4097 * <p> 4098 * Note that before comparing these detailed generation values, you should 4099 * first confirm that the overall version hasn't changed by checking 4100 * {@link MediaStore#getVersion(Context, String)}, since that indicates when 4101 * a more radical change has occurred. If the overall version changes, you 4102 * should assume that generation numbers have been reset and perform a full 4103 * synchronization pass. 4104 * 4105 * @param volumeName specific volume to obtain an generation value for. Must 4106 * be one of the values returned from 4107 * {@link #getExternalVolumeNames(Context)}. 4108 * @see MediaColumns#GENERATION_ADDED 4109 * @see MediaColumns#GENERATION_MODIFIED 4110 */ getGeneration(@onNull Context context, @NonNull String volumeName)4111 public static long getGeneration(@NonNull Context context, @NonNull String volumeName) { 4112 return getGeneration(context.getContentResolver(), volumeName); 4113 } 4114 4115 /** {@hide} */ getGeneration(@onNull ContentResolver resolver, @NonNull String volumeName)4116 public static long getGeneration(@NonNull ContentResolver resolver, 4117 @NonNull String volumeName) { 4118 final Bundle in = new Bundle(); 4119 in.putString(Intent.EXTRA_TEXT, volumeName); 4120 final Bundle out = resolver.call(AUTHORITY, GET_GENERATION_CALL, null, in); 4121 return out.getLong(Intent.EXTRA_INDEX); 4122 } 4123 4124 /** 4125 * Return a {@link DocumentsProvider} Uri that is an equivalent to the given 4126 * {@link MediaStore} Uri. 4127 * <p> 4128 * This allows apps with Storage Access Framework permissions to convert 4129 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 4130 * to the same underlying item. Note that this method doesn't grant any new 4131 * permissions; callers must already hold permissions obtained with 4132 * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs. 4133 * 4134 * @param mediaUri The {@link MediaStore} Uri to convert. 4135 * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null} 4136 * if no equivalent was found. 4137 * @see #getMediaUri(Context, Uri) 4138 */ getDocumentUri(@onNull Context context, @NonNull Uri mediaUri)4139 public static @Nullable Uri getDocumentUri(@NonNull Context context, @NonNull Uri mediaUri) { 4140 final ContentResolver resolver = context.getContentResolver(); 4141 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 4142 4143 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 4144 final Bundle in = new Bundle(); 4145 in.putParcelable(EXTRA_URI, mediaUri); 4146 in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions)); 4147 final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in); 4148 return out.getParcelable(EXTRA_URI); 4149 } catch (RemoteException e) { 4150 throw e.rethrowAsRuntimeException(); 4151 } 4152 } 4153 4154 /** 4155 * Return a {@link MediaStore} Uri that is an equivalent to the given 4156 * {@link DocumentsProvider} Uri. This only supports {@code ExternalStorageProvider} 4157 * and {@code MediaDocumentsProvider} Uris. 4158 * <p> 4159 * This allows apps with Storage Access Framework permissions to convert 4160 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 4161 * to the same underlying item. 4162 * Note that this method doesn't grant any new permissions, but it grants the same access to 4163 * the Media Store Uri as the caller has to the given DocumentsProvider Uri; callers must 4164 * already hold permissions for documentUri obtained with {@link Intent#ACTION_OPEN_DOCUMENT} 4165 * or related APIs. 4166 * 4167 * @param documentUri The {@link DocumentsProvider} Uri to convert. 4168 * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no 4169 * equivalent was found. 4170 * @see #getDocumentUri(Context, Uri) 4171 */ getMediaUri(@onNull Context context, @NonNull Uri documentUri)4172 public static @Nullable Uri getMediaUri(@NonNull Context context, @NonNull Uri documentUri) { 4173 final ContentResolver resolver = context.getContentResolver(); 4174 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 4175 4176 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 4177 final Bundle in = new Bundle(); 4178 in.putParcelable(EXTRA_URI, documentUri); 4179 in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions)); 4180 final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in); 4181 return out.getParcelable(EXTRA_URI); 4182 } catch (RemoteException e) { 4183 throw e.rethrowAsRuntimeException(); 4184 } 4185 } 4186 4187 /** 4188 * Returns true if the given application is the current system gallery of the device. 4189 * <p> 4190 * The system gallery is one app chosen by the OEM that has read & write access to all photos 4191 * and videos on the device and control over folders in media collections. 4192 * 4193 * @param resolver The {@link ContentResolver} used to connect with 4194 * {@link MediaStore#AUTHORITY}. Typically this value is {@link Context#getContentResolver()}. 4195 * @param uid The uid to be checked if it is the current system gallery. 4196 * @param packageName The package name to be checked if it is the current system gallery. 4197 */ isCurrentSystemGallery( @onNull ContentResolver resolver, int uid, @NonNull String packageName)4198 public static boolean isCurrentSystemGallery( 4199 @NonNull ContentResolver resolver, 4200 int uid, 4201 @NonNull String packageName) { 4202 Bundle in = new Bundle(); 4203 in.putInt(EXTRA_IS_SYSTEM_GALLERY_UID, uid); 4204 final Bundle out = resolver.call(AUTHORITY, IS_SYSTEM_GALLERY_CALL, packageName, in); 4205 return out.getBoolean(EXTRA_IS_SYSTEM_GALLERY_RESPONSE); 4206 } 4207 4208 /** 4209 * Returns an EXIF redacted version of {@code uri} i.e. a {@link Uri} with metadata such as 4210 * location, GPS datestamp etc. redacted from the EXIF headers. 4211 * <p> 4212 * A redacted Uri can be used to share a file with another application wherein exposing 4213 * sensitive information in EXIF headers is not desirable. 4214 * Note: 4215 * 1. Redacted uris cannot be granted write access and can neither be used to perform any kind 4216 * of write operations. 4217 * 2. To get a redacted uri the caller must hold read permission to {@code uri}. 4218 * 4219 * @param resolver The {@link ContentResolver} used to connect with 4220 * {@link MediaStore#AUTHORITY}. Typically this value is gotten from 4221 * {@link Context#getContentResolver()} 4222 * @param uri the {@link Uri} Uri to convert 4223 * @return redacted version of the {@code uri}. Returns {@code null} when the given 4224 * {@link Uri} could not be found or is unsupported 4225 * @throws SecurityException if the caller doesn't have the read access to {@code uri} 4226 * @see #getRedactedUri(ContentResolver, List) 4227 */ 4228 @Nullable getRedactedUri(@onNull ContentResolver resolver, @NonNull Uri uri)4229 public static Uri getRedactedUri(@NonNull ContentResolver resolver, @NonNull Uri uri) { 4230 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 4231 final Bundle in = new Bundle(); 4232 in.putParcelable(EXTRA_URI, uri); 4233 final Bundle out = client.call(GET_REDACTED_MEDIA_URI_CALL, null, in); 4234 return out.getParcelable(EXTRA_URI); 4235 } catch (RemoteException e) { 4236 throw e.rethrowAsRuntimeException(); 4237 } 4238 } 4239 4240 /** 4241 * Returns a list of EXIF redacted version of {@code uris} i.e. a {@link Uri} with metadata 4242 * such as location, GPS datestamp etc. redacted from the EXIF headers. 4243 * <p> 4244 * A redacted Uri can be used to share a file with another application wherein exposing 4245 * sensitive information in EXIF headers is not desirable. 4246 * Note: 4247 * 1. Order of the returned uris follow the order of the {@code uris}. 4248 * 2. Redacted uris cannot be granted write access and can neither be used to perform any kind 4249 * of write operations. 4250 * 3. To get a redacted uri the caller must hold read permission to its corresponding uri. 4251 * 4252 * @param resolver The {@link ContentResolver} used to connect with 4253 * {@link MediaStore#AUTHORITY}. Typically this value is gotten from 4254 * {@link Context#getContentResolver()} 4255 * @param uris the list of {@link Uri} Uri to convert 4256 * @return a list with redacted version of {@code uris}, in the same order. Returns {@code null} 4257 * when the corresponding {@link Uri} could not be found or is unsupported 4258 * @throws SecurityException if the caller doesn't have the read access to all the elements 4259 * in {@code uris} 4260 * @see #getRedactedUri(ContentResolver, Uri) 4261 */ 4262 @NonNull getRedactedUri(@onNull ContentResolver resolver, @NonNull List<Uri> uris)4263 public static List<Uri> getRedactedUri(@NonNull ContentResolver resolver, 4264 @NonNull List<Uri> uris) { 4265 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 4266 final Bundle in = new Bundle(); 4267 in.putParcelableArrayList(EXTRA_URI_LIST, (ArrayList<? extends Parcelable>) uris); 4268 final Bundle out = client.call(GET_REDACTED_MEDIA_URI_LIST_CALL, null, in); 4269 return out.getParcelableArrayList(EXTRA_URI_LIST); 4270 } catch (RemoteException e) { 4271 throw e.rethrowAsRuntimeException(); 4272 } 4273 } 4274 4275 /** {@hide} */ resolvePlaylistMembers(@onNull ContentResolver resolver, @NonNull Uri playlistUri)4276 public static void resolvePlaylistMembers(@NonNull ContentResolver resolver, 4277 @NonNull Uri playlistUri) { 4278 final Bundle in = new Bundle(); 4279 in.putParcelable(EXTRA_URI, playlistUri); 4280 resolver.call(AUTHORITY, RESOLVE_PLAYLIST_MEMBERS_CALL, null, in); 4281 } 4282 4283 /** {@hide} */ runIdleMaintenance(@onNull ContentResolver resolver)4284 public static void runIdleMaintenance(@NonNull ContentResolver resolver) { 4285 resolver.call(AUTHORITY, RUN_IDLE_MAINTENANCE_CALL, null, null); 4286 } 4287 4288 /** 4289 * Block until any pending operations have finished, such as 4290 * {@link #scanFile} or {@link #scanVolume} requests. 4291 * 4292 * @hide 4293 */ 4294 @SystemApi 4295 @WorkerThread waitForIdle(@onNull ContentResolver resolver)4296 public static void waitForIdle(@NonNull ContentResolver resolver) { 4297 resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null); 4298 } 4299 4300 /** 4301 * Perform a blocking scan of the given {@link File}, returning the 4302 * {@link Uri} of the scanned file. 4303 * 4304 * @hide 4305 */ 4306 @SystemApi 4307 @WorkerThread 4308 @SuppressLint("StreamFiles") scanFile(@onNull ContentResolver resolver, @NonNull File file)4309 public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) { 4310 final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null); 4311 return out.getParcelable(Intent.EXTRA_STREAM); 4312 } 4313 4314 /** 4315 * Perform a blocking scan of the given storage volume. 4316 * 4317 * @hide 4318 */ 4319 @SystemApi 4320 @WorkerThread scanVolume(@onNull ContentResolver resolver, @NonNull String volumeName)4321 public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) { 4322 resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null); 4323 } 4324 4325 /** 4326 * Returns whether the calling app is granted {@link android.Manifest.permission#MANAGE_MEDIA} 4327 * or not. 4328 * <p>Declaring the permission {@link android.Manifest.permission#MANAGE_MEDIA} isn't 4329 * enough to gain the access. 4330 * <p>To request access, use {@link android.provider.Settings#ACTION_REQUEST_MANAGE_MEDIA}. 4331 * 4332 * @param context the request context 4333 * @return true, the calling app is granted the permission. Otherwise, false 4334 * 4335 * @see android.Manifest.permission#MANAGE_MEDIA 4336 * @see android.provider.Settings#ACTION_REQUEST_MANAGE_MEDIA 4337 * @see #createDeleteRequest(ContentResolver, Collection) 4338 * @see #createTrashRequest(ContentResolver, Collection, boolean) 4339 * @see #createWriteRequest(ContentResolver, Collection) 4340 */ 4341 @RequiresApi(Build.VERSION_CODES.S) canManageMedia(@onNull Context context)4342 public static boolean canManageMedia(@NonNull Context context) { 4343 Objects.requireNonNull(context); 4344 final String packageName = context.getOpPackageName(); 4345 final int uid = context.getApplicationInfo().uid; 4346 final String permission = android.Manifest.permission.MANAGE_MEDIA; 4347 4348 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 4349 final int opMode = appOps.unsafeCheckOpNoThrow(AppOpsManager.permissionToOp(permission), 4350 uid, packageName); 4351 4352 switch (opMode) { 4353 case AppOpsManager.MODE_DEFAULT: 4354 return PackageManager.PERMISSION_GRANTED == context.checkPermission( 4355 permission, android.os.Process.myPid(), uid); 4356 case AppOpsManager.MODE_ALLOWED: 4357 return true; 4358 case AppOpsManager.MODE_ERRORED: 4359 case AppOpsManager.MODE_IGNORED: 4360 return false; 4361 default: 4362 Log.w(TAG, "Unknown AppOpsManager mode " + opMode); 4363 return false; 4364 } 4365 } 4366 } 4367