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