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