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