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