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.IntRange; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresPermission; 27 import android.annotation.SdkConstant; 28 import android.annotation.SdkConstant.SdkConstantType; 29 import android.annotation.TestApi; 30 import android.annotation.UnsupportedAppUsage; 31 import android.app.Activity; 32 import android.app.AppGlobals; 33 import android.content.ClipData; 34 import android.content.ContentProviderClient; 35 import android.content.ContentResolver; 36 import android.content.ContentUris; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.UriPermission; 41 import android.database.Cursor; 42 import android.database.DatabaseUtils; 43 import android.graphics.Bitmap; 44 import android.graphics.BitmapFactory; 45 import android.graphics.ImageDecoder; 46 import android.graphics.Point; 47 import android.graphics.PostProcessor; 48 import android.media.ExifInterface; 49 import android.media.MediaFile; 50 import android.net.Uri; 51 import android.os.Bundle; 52 import android.os.CancellationSignal; 53 import android.os.Environment; 54 import android.os.FileUtils; 55 import android.os.OperationCanceledException; 56 import android.os.ParcelFileDescriptor; 57 import android.os.RemoteException; 58 import android.os.UserHandle; 59 import android.os.UserManager; 60 import android.os.storage.StorageManager; 61 import android.os.storage.StorageVolume; 62 import android.os.storage.VolumeInfo; 63 import android.service.media.CameraPrewarmService; 64 import android.text.TextUtils; 65 import android.text.format.DateUtils; 66 import android.util.ArrayMap; 67 import android.util.ArraySet; 68 import android.util.Log; 69 70 import com.android.internal.annotations.GuardedBy; 71 72 import java.io.File; 73 import java.io.FileInputStream; 74 import java.io.FileNotFoundException; 75 import java.io.IOException; 76 import java.io.InputStream; 77 import java.io.OutputStream; 78 import java.util.ArrayList; 79 import java.util.Collection; 80 import java.util.List; 81 import java.util.Objects; 82 import java.util.Set; 83 import java.util.regex.Pattern; 84 85 /** 86 * The contract between the media provider and applications. Contains 87 * definitions for the supported URIs and columns. 88 * <p> 89 * The media provider provides an indexed collection of common media types, such 90 * as {@link Audio}, {@link Video}, and {@link Images}, from any attached 91 * storage devices. Each collection is organized based on the primary MIME type 92 * of the underlying content; for example, {@code image/*} content is indexed 93 * under {@link Images}. The {@link Files} collection provides a broad view 94 * across all collections, and does not filter by MIME type. 95 */ 96 public final class MediaStore { 97 private final static String TAG = "MediaStore"; 98 99 /** The authority for the media provider */ 100 public static final String AUTHORITY = "media"; 101 /** A content:// style uri to the authority for the media provider */ 102 public static final @NonNull Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); 103 104 /** 105 * Synthetic volume name that provides a view of all content across the 106 * "internal" storage of the device. 107 * <p> 108 * This synthetic volume provides a merged view of all media distributed 109 * with the device, such as built-in ringtones and wallpapers. 110 * <p> 111 * Because this is a synthetic volume, you can't insert new content into 112 * this volume. 113 */ 114 public static final String VOLUME_INTERNAL = "internal"; 115 116 /** 117 * Synthetic volume name that provides a view of all content across the 118 * "external" storage of the device. 119 * <p> 120 * This synthetic volume provides a merged view of all media across all 121 * currently attached external storage devices. 122 * <p> 123 * Because this is a synthetic volume, you can't insert new content into 124 * this volume. Instead, you can insert content into a specific storage 125 * volume obtained from {@link #getExternalVolumeNames(Context)}. 126 */ 127 public static final String VOLUME_EXTERNAL = "external"; 128 129 /** 130 * Specific volume name that represents the primary external storage device 131 * at {@link Environment#getExternalStorageDirectory()}. 132 * <p> 133 * This volume may not always be available, such as when the user has 134 * ejected the device. You can find a list of all specific volume names 135 * using {@link #getExternalVolumeNames(Context)}. 136 */ 137 public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary"; 138 139 /** {@hide} */ 140 public static final String SCAN_FILE_CALL = "scan_file"; 141 /** {@hide} */ 142 public static final String SCAN_VOLUME_CALL = "scan_volume"; 143 144 /** 145 * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that 146 * the file path originated from shell. 147 * 148 * {@hide} 149 */ 150 public static final String EXTRA_ORIGINATED_FROM_SHELL = 151 "android.intent.extra.originated_from_shell"; 152 153 /** 154 * The method name used by the media scanner and mtp to tell the media provider to 155 * rescan and reclassify that have become unhidden because of renaming folders or 156 * removing nomedia files 157 * @hide 158 */ 159 @Deprecated 160 public static final String UNHIDE_CALL = "unhide"; 161 162 /** 163 * The method name used by the media scanner service to reload all localized ringtone titles due 164 * to a locale change. 165 * @hide 166 */ 167 public static final String RETRANSLATE_CALL = "update_titles"; 168 169 /** {@hide} */ 170 public static final String GET_VERSION_CALL = "get_version"; 171 /** {@hide} */ 172 public static final String GET_DOCUMENT_URI_CALL = "get_document_uri"; 173 /** {@hide} */ 174 public static final String GET_MEDIA_URI_CALL = "get_media_uri"; 175 176 /** {@hide} */ 177 public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media"; 178 /** {@hide} */ 179 public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media"; 180 181 /** 182 * This is for internal use by the media scanner only. 183 * Name of the (optional) Uri parameter that determines whether to skip deleting 184 * the file pointed to by the _data column, when deleting the database entry. 185 * The only appropriate value for this parameter is "false", in which case the 186 * delete will be skipped. Note especially that setting this to true, or omitting 187 * the parameter altogether, will perform the default action, which is different 188 * for different types of media. 189 * @hide 190 */ 191 public static final String PARAM_DELETE_DATA = "deletedata"; 192 193 /** {@hide} */ 194 public static final String PARAM_INCLUDE_PENDING = "includePending"; 195 /** {@hide} */ 196 public static final String PARAM_INCLUDE_TRASHED = "includeTrashed"; 197 /** {@hide} */ 198 public static final String PARAM_PROGRESS = "progress"; 199 /** {@hide} */ 200 public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal"; 201 /** {@hide} */ 202 public static final String PARAM_LIMIT = "limit"; 203 204 /** 205 * Activity Action: Launch a music player. 206 * The activity should be able to play, browse, or manipulate music files stored on the device. 207 * 208 * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead. 209 */ 210 @Deprecated 211 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 212 public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER"; 213 214 /** 215 * Activity Action: Perform a search for media. 216 * Contains at least the {@link android.app.SearchManager#QUERY} extra. 217 * May also contain any combination of the following extras: 218 * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS 219 * 220 * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST 221 * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM 222 * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE 223 * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS 224 */ 225 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 226 public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH"; 227 228 /** 229 * An intent to perform a search for music media and automatically play content from the 230 * result when possible. This can be fired, for example, by the result of a voice recognition 231 * command to listen to music. 232 * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} 233 * and {@link android.app.SearchManager#QUERY} extras. The 234 * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and 235 * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode. 236 * For more information about the search modes for this intent, see 237 * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based 238 * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common 239 * Intents</a>.</p> 240 * 241 * <p>This intent makes the most sense for apps that can support large-scale search of music, 242 * such as services connected to an online database of music which can be streamed and played 243 * on the device.</p> 244 */ 245 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 246 public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = 247 "android.media.action.MEDIA_PLAY_FROM_SEARCH"; 248 249 /** 250 * An intent to perform a search for readable media and automatically play content from the 251 * result when possible. This can be fired, for example, by the result of a voice recognition 252 * command to read a book or magazine. 253 * <p> 254 * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can 255 * contain any type of unstructured text search, like the name of a book or magazine, an author 256 * a genre, a publisher, or any combination of these. 257 * <p> 258 * Because this intent includes an open-ended unstructured search string, it makes the most 259 * sense for apps that can support large-scale search of text media, such as services connected 260 * to an online database of books and/or magazines which can be read on the device. 261 */ 262 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 263 public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH = 264 "android.media.action.TEXT_OPEN_FROM_SEARCH"; 265 266 /** 267 * An intent to perform a search for video media and automatically play content from the 268 * result when possible. This can be fired, for example, by the result of a voice recognition 269 * command to play movies. 270 * <p> 271 * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can 272 * contain any type of unstructured video search, like the name of a movie, one or more actors, 273 * a genre, or any combination of these. 274 * <p> 275 * Because this intent includes an open-ended unstructured search string, it makes the most 276 * sense for apps that can support large-scale search of video, such as services connected to an 277 * online database of videos which can be streamed and played on the device. 278 */ 279 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 280 public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH = 281 "android.media.action.VIDEO_PLAY_FROM_SEARCH"; 282 283 /** 284 * The name of the Intent-extra used to define the artist 285 */ 286 public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist"; 287 /** 288 * The name of the Intent-extra used to define the album 289 */ 290 public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album"; 291 /** 292 * The name of the Intent-extra used to define the song title 293 */ 294 public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title"; 295 /** 296 * The name of the Intent-extra used to define the genre. 297 */ 298 public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre"; 299 /** 300 * The name of the Intent-extra used to define the playlist. 301 */ 302 public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist"; 303 /** 304 * The name of the Intent-extra used to define the radio channel. 305 */ 306 public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel"; 307 /** 308 * The name of the Intent-extra used to define the search focus. The search focus 309 * indicates whether the search should be for things related to the artist, album 310 * or song that is identified by the other extras. 311 */ 312 public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus"; 313 314 /** 315 * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView. 316 * This is an int property that overrides the activity's requestedOrientation. 317 * @see android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED 318 */ 319 public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation"; 320 321 /** 322 * The name of an Intent-extra used to control the UI of a ViewImage. 323 * This is a boolean property that overrides the activity's default fullscreen state. 324 */ 325 public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; 326 327 /** 328 * The name of an Intent-extra used to control the UI of a ViewImage. 329 * This is a boolean property that specifies whether or not to show action icons. 330 */ 331 public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons"; 332 333 /** 334 * The name of the Intent-extra used to control the onCompletion behavior of a MovieView. 335 * This is a boolean property that specifies whether or not to finish the MovieView activity 336 * when the movie completes playing. The default value is true, which means to automatically 337 * exit the movie player activity when the movie completes playing. 338 */ 339 public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion"; 340 341 /** 342 * The name of the Intent action used to launch a camera in still image mode. 343 */ 344 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 345 public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA"; 346 347 /** 348 * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or 349 * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm 350 * service. 351 * <p> 352 * This meta-data should reference the fully qualified class name of the prewarm service 353 * extending {@link CameraPrewarmService}. 354 * <p> 355 * The prewarm service will get bound and receive a prewarm signal 356 * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent. 357 * An application implementing a prewarm service should do the absolute minimum amount of work 358 * to initialize the camera in order to reduce startup time in likely case that shortly after a 359 * camera launch intent would be sent. 360 */ 361 public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = 362 "android.media.still_image_camera_preview_service"; 363 364 /** 365 * The name of the Intent action used to launch a camera in still image mode 366 * for use when the device is secured (e.g. with a pin, password, pattern, 367 * or face unlock). Applications responding to this intent must not expose 368 * any personal content like existing photos or videos on the device. The 369 * applications should be careful not to share any photo or video with other 370 * applications or internet. The activity should use {@link 371 * Activity#setShowWhenLocked} to display 372 * on top of the lock screen while secured. There is no activity stack when 373 * this flag is used, so launching more than one activity is strongly 374 * discouraged. 375 */ 376 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 377 public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE = 378 "android.media.action.STILL_IMAGE_CAMERA_SECURE"; 379 380 /** 381 * The name of the Intent action used to launch a camera in video mode. 382 */ 383 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 384 public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA"; 385 386 /** 387 * Standard Intent action that can be sent to have the camera application 388 * capture an image and return it. 389 * <p> 390 * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. 391 * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap 392 * object in the extra field. This is useful for applications that only need a small image. 393 * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri 394 * value of EXTRA_OUTPUT. 395 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 396 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 397 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 398 * If you don't set a ClipData, it will be copied there for you when calling 399 * {@link Context#startActivity(Intent)}. 400 * 401 * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above 402 * and declares as using the {@link android.Manifest.permission#CAMERA} permission which 403 * is not granted, then attempting to use this action will result in a {@link 404 * java.lang.SecurityException}. 405 * 406 * @see #EXTRA_OUTPUT 407 */ 408 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 409 public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; 410 411 /** 412 * Intent action that can be sent to have the camera application capture an image and return 413 * it when the device is secured (e.g. with a pin, password, pattern, or face unlock). 414 * Applications responding to this intent must not expose any personal content like existing 415 * photos or videos on the device. The applications should be careful not to share any photo 416 * or video with other applications or Internet. The activity should use {@link 417 * Activity#setShowWhenLocked} to display on top of the 418 * lock screen while secured. There is no activity stack when this flag is used, so 419 * launching more than one activity is strongly discouraged. 420 * <p> 421 * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written. 422 * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap 423 * object in the extra field. This is useful for applications that only need a small image. 424 * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri 425 * value of EXTRA_OUTPUT. 426 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 427 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 428 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 429 * If you don't set a ClipData, it will be copied there for you when calling 430 * {@link Context#startActivity(Intent)}. 431 * 432 * @see #ACTION_IMAGE_CAPTURE 433 * @see #EXTRA_OUTPUT 434 */ 435 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 436 public static final String ACTION_IMAGE_CAPTURE_SECURE = 437 "android.media.action.IMAGE_CAPTURE_SECURE"; 438 439 /** 440 * Standard Intent action that can be sent to have the camera application 441 * capture a video and return it. 442 * <p> 443 * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality. 444 * <p> 445 * The caller may pass in an extra EXTRA_OUTPUT to control 446 * where the video is written. If EXTRA_OUTPUT is not present the video will be 447 * written to the standard location for videos, and the Uri of that location will be 448 * returned in the data field of the Uri. 449 * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through 450 * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must 451 * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications. 452 * If you don't set a ClipData, it will be copied there for you when calling 453 * {@link Context#startActivity(Intent)}. 454 * 455 * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above 456 * and declares as using the {@link android.Manifest.permission#CAMERA} permission which 457 * is not granted, then atempting to use this action will result in a {@link 458 * java.lang.SecurityException}. 459 * 460 * @see #EXTRA_OUTPUT 461 * @see #EXTRA_VIDEO_QUALITY 462 * @see #EXTRA_SIZE_LIMIT 463 * @see #EXTRA_DURATION_LIMIT 464 */ 465 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 466 public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; 467 468 /** 469 * Standard action that can be sent to review the given media file. 470 * <p> 471 * The launched application is expected to provide a large-scale view of the 472 * given media file, while allowing the user to quickly access other 473 * recently captured media files. 474 * <p> 475 * Input: {@link Intent#getData} is URI of the primary media item to 476 * initially display. 477 * 478 * @see #ACTION_REVIEW_SECURE 479 * @see #EXTRA_BRIGHTNESS 480 */ 481 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 482 public final static String ACTION_REVIEW = "android.provider.action.REVIEW"; 483 484 /** 485 * Standard action that can be sent to review the given media file when the 486 * device is secured (e.g. with a pin, password, pattern, or face unlock). 487 * The applications should be careful not to share any media with other 488 * applications or Internet. The activity should use 489 * {@link Activity#setShowWhenLocked} to display on top of the lock screen 490 * while secured. There is no activity stack when this flag is used, so 491 * launching more than one activity is strongly discouraged. 492 * <p> 493 * The launched application is expected to provide a large-scale view of the 494 * given primary media file, while only allowing the user to quickly access 495 * other media from an explicit secondary list. 496 * <p> 497 * Input: {@link Intent#getData} is URI of the primary media item to 498 * initially display. {@link Intent#getClipData} is the limited list of 499 * secondary media items that the user is allowed to review. If 500 * {@link Intent#getClipData} is undefined, then no other media access 501 * should be allowed. 502 * 503 * @see #EXTRA_BRIGHTNESS 504 */ 505 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 506 public final static String ACTION_REVIEW_SECURE = "android.provider.action.REVIEW_SECURE"; 507 508 /** 509 * When defined, the launched application is requested to set the given 510 * brightness value via 511 * {@link android.view.WindowManager.LayoutParams#screenBrightness} to help 512 * ensure a smooth transition when launching {@link #ACTION_REVIEW} or 513 * {@link #ACTION_REVIEW_SECURE} intents. 514 */ 515 public final static String EXTRA_BRIGHTNESS = "android.provider.extra.BRIGHTNESS"; 516 517 /** 518 * The name of the Intent-extra used to control the quality of a recorded video. This is an 519 * integer property. Currently value 0 means low quality, suitable for MMS messages, and 520 * value 1 means high quality. In the future other quality levels may be added. 521 */ 522 public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; 523 524 /** 525 * Specify the maximum allowed size. 526 */ 527 public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit"; 528 529 /** 530 * Specify the maximum allowed recording duration in seconds. 531 */ 532 public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit"; 533 534 /** 535 * The name of the Intent-extra used to indicate a content resolver Uri to be used to 536 * store the requested image or video. 537 */ 538 public final static String EXTRA_OUTPUT = "output"; 539 540 /** 541 * The string that is used when a media attribute is not known. For example, 542 * if an audio file does not have any meta data, the artist and album columns 543 * will be set to this value. 544 */ 545 public static final String UNKNOWN_STRING = "<unknown>"; 546 547 /** 548 * Update the given {@link Uri} to also include any pending media items from 549 * calls such as 550 * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}. 551 * By default no pending items are returned. 552 * 553 * @see MediaColumns#IS_PENDING 554 * @see MediaStore#setIncludePending(Uri) 555 */ setIncludePending(@onNull Uri uri)556 public static @NonNull Uri setIncludePending(@NonNull Uri uri) { 557 return setIncludePending(uri.buildUpon()).build(); 558 } 559 560 /** @hide */ setIncludePending(@onNull Uri.Builder uriBuilder)561 public static @NonNull Uri.Builder setIncludePending(@NonNull Uri.Builder uriBuilder) { 562 return uriBuilder.appendQueryParameter(PARAM_INCLUDE_PENDING, "1"); 563 } 564 565 /** 566 * Update the given {@link Uri} to also include any trashed media items from 567 * calls such as 568 * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}. 569 * By default no trashed items are returned. 570 * 571 * @see MediaColumns#IS_TRASHED 572 * @see MediaStore#setIncludeTrashed(Uri) 573 * @see MediaStore#trash(Context, Uri) 574 * @see MediaStore#untrash(Context, Uri) 575 * @removed 576 */ 577 @Deprecated setIncludeTrashed(@onNull Uri uri)578 public static @NonNull Uri setIncludeTrashed(@NonNull Uri uri) { 579 return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_TRASHED, "1").build(); 580 } 581 582 /** 583 * Update the given {@link Uri} to indicate that the caller requires the 584 * original file contents when calling 585 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 586 * <p> 587 * This can be useful when the caller wants to ensure they're backing up the 588 * exact bytes of the underlying media, without any Exif redaction being 589 * performed. 590 * <p> 591 * If the original file contents cannot be provided, a 592 * {@link UnsupportedOperationException} will be thrown when the returned 593 * {@link Uri} is used, such as when the caller doesn't hold 594 * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}. 595 */ setRequireOriginal(@onNull Uri uri)596 public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) { 597 return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build(); 598 } 599 600 /** 601 * Create a new pending media item using the given parameters. Pending items 602 * are expected to have a short lifetime, and owners should either 603 * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a 604 * pending item within a few hours after first creating it. 605 * 606 * @return token which can be passed to {@link #openPending(Context, Uri)} 607 * to work with this pending item. 608 * @see MediaColumns#IS_PENDING 609 * @see MediaStore#setIncludePending(Uri) 610 * @see MediaStore#createPending(Context, PendingParams) 611 * @removed 612 */ 613 @Deprecated createPending(@onNull Context context, @NonNull PendingParams params)614 public static @NonNull Uri createPending(@NonNull Context context, 615 @NonNull PendingParams params) { 616 return context.getContentResolver().insert(params.insertUri, params.insertValues); 617 } 618 619 /** 620 * Open a pending media item to make progress on it. You can open a pending 621 * item multiple times before finally calling either 622 * {@link PendingSession#publish()} or {@link PendingSession#abandon()}. 623 * 624 * @param uri token which was previously returned from 625 * {@link #createPending(Context, PendingParams)}. 626 * @removed 627 */ 628 @Deprecated openPending(@onNull Context context, @NonNull Uri uri)629 public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) { 630 return new PendingSession(context, uri); 631 } 632 633 /** 634 * Parameters that describe a pending media item. 635 * 636 * @removed 637 */ 638 @Deprecated 639 public static class PendingParams { 640 /** {@hide} */ 641 public final Uri insertUri; 642 /** {@hide} */ 643 public final ContentValues insertValues; 644 645 /** 646 * Create parameters that describe a pending media item. 647 * 648 * @param insertUri the {@code content://} Uri where this pending item 649 * should be inserted when finally published. For example, to 650 * publish an image, use 651 * {@link MediaStore.Images.Media#getContentUri(String)}. 652 */ PendingParams(@onNull Uri insertUri, @NonNull String displayName, @NonNull String mimeType)653 public PendingParams(@NonNull Uri insertUri, @NonNull String displayName, 654 @NonNull String mimeType) { 655 this.insertUri = Objects.requireNonNull(insertUri); 656 final long now = System.currentTimeMillis() / 1000; 657 this.insertValues = new ContentValues(); 658 this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName)); 659 this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType)); 660 this.insertValues.put(MediaColumns.DATE_ADDED, now); 661 this.insertValues.put(MediaColumns.DATE_MODIFIED, now); 662 this.insertValues.put(MediaColumns.IS_PENDING, 1); 663 this.insertValues.put(MediaColumns.DATE_EXPIRES, 664 (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000); 665 } 666 667 /** 668 * Optionally set the primary directory under which this pending item 669 * should be persisted. Only specific well-defined directories from 670 * {@link Environment} are allowed based on the media type being 671 * inserted. 672 * <p> 673 * For example, when creating pending {@link MediaStore.Images.Media} 674 * items, only {@link Environment#DIRECTORY_PICTURES} or 675 * {@link Environment#DIRECTORY_DCIM} are allowed. 676 * <p> 677 * You may leave this value undefined to store the media in a default 678 * location. For example, when this value is left undefined, pending 679 * {@link MediaStore.Audio.Media} items are stored under 680 * {@link Environment#DIRECTORY_MUSIC}. 681 * 682 * @see MediaColumns#PRIMARY_DIRECTORY 683 */ setPrimaryDirectory(@ullable String primaryDirectory)684 public void setPrimaryDirectory(@Nullable String primaryDirectory) { 685 if (primaryDirectory == null) { 686 this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY); 687 } else { 688 this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory); 689 } 690 } 691 692 /** 693 * Optionally set the secondary directory under which this pending item 694 * should be persisted. Any valid directory name is allowed. 695 * <p> 696 * You may leave this value undefined to store the media as a direct 697 * descendant of the {@link #setPrimaryDirectory(String)} location. 698 * 699 * @see MediaColumns#SECONDARY_DIRECTORY 700 */ setSecondaryDirectory(@ullable String secondaryDirectory)701 public void setSecondaryDirectory(@Nullable String secondaryDirectory) { 702 if (secondaryDirectory == null) { 703 this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY); 704 } else { 705 this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory); 706 } 707 } 708 709 /** 710 * Optionally set the Uri from where the file has been downloaded. This is used 711 * for files being added to {@link Downloads} table. 712 * 713 * @see DownloadColumns#DOWNLOAD_URI 714 */ setDownloadUri(@ullable Uri downloadUri)715 public void setDownloadUri(@Nullable Uri downloadUri) { 716 if (downloadUri == null) { 717 this.insertValues.remove(DownloadColumns.DOWNLOAD_URI); 718 } else { 719 this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString()); 720 } 721 } 722 723 /** 724 * Optionally set the Uri indicating HTTP referer of the file. This is used for 725 * files being added to {@link Downloads} table. 726 * 727 * @see DownloadColumns#REFERER_URI 728 */ setRefererUri(@ullable Uri refererUri)729 public void setRefererUri(@Nullable Uri refererUri) { 730 if (refererUri == null) { 731 this.insertValues.remove(DownloadColumns.REFERER_URI); 732 } else { 733 this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString()); 734 } 735 } 736 } 737 738 /** 739 * Session actively working on a pending media item. Pending items are 740 * expected to have a short lifetime, and owners should either 741 * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a 742 * pending item within a few hours after first creating it. 743 * 744 * @removed 745 */ 746 @Deprecated 747 public static class PendingSession implements AutoCloseable { 748 /** {@hide} */ 749 private final Context mContext; 750 /** {@hide} */ 751 private final Uri mUri; 752 753 /** {@hide} */ PendingSession(Context context, Uri uri)754 public PendingSession(Context context, Uri uri) { 755 mContext = Objects.requireNonNull(context); 756 mUri = Objects.requireNonNull(uri); 757 } 758 759 /** 760 * Open the underlying file representing this media item. When a media 761 * item is successfully completed, you should 762 * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it. 763 * 764 * @see #notifyProgress(int) 765 */ open()766 public @NonNull ParcelFileDescriptor open() throws FileNotFoundException { 767 return mContext.getContentResolver().openFileDescriptor(mUri, "rw"); 768 } 769 770 /** 771 * Open the underlying file representing this media item. When a media 772 * item is successfully completed, you should 773 * {@link OutputStream#close()} and then {@link #publish()} it. 774 * 775 * @see #notifyProgress(int) 776 */ openOutputStream()777 public @NonNull OutputStream openOutputStream() throws FileNotFoundException { 778 return mContext.getContentResolver().openOutputStream(mUri); 779 } 780 781 /** 782 * Notify of current progress on this pending media item. Gallery 783 * applications may choose to surface progress information of this 784 * pending item. 785 * 786 * @param progress a percentage between 0 and 100. 787 */ notifyProgress(@ntRangefrom = 0, to = 100) int progress)788 public void notifyProgress(@IntRange(from = 0, to = 100) int progress) { 789 final Uri withProgress = mUri.buildUpon() 790 .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build(); 791 mContext.getContentResolver().notifyChange(withProgress, null, 0); 792 } 793 794 /** 795 * When this media item is successfully completed, call this method to 796 * publish and make the final item visible to the user. 797 * 798 * @return the final {@code content://} Uri representing the newly 799 * published media. 800 */ publish()801 public @NonNull Uri publish() { 802 final ContentValues values = new ContentValues(); 803 values.put(MediaColumns.IS_PENDING, 0); 804 values.putNull(MediaColumns.DATE_EXPIRES); 805 mContext.getContentResolver().update(mUri, values, null, null); 806 return mUri; 807 } 808 809 /** 810 * When this media item has failed to be completed, call this method to 811 * destroy the pending item record and any data related to it. 812 */ abandon()813 public void abandon() { 814 mContext.getContentResolver().delete(mUri, null, null); 815 } 816 817 @Override close()818 public void close() { 819 // No resources to close, but at least we can inform people that no 820 // progress is being actively made. 821 notifyProgress(-1); 822 } 823 } 824 825 /** 826 * Mark the given item as being "trashed", meaning it should be deleted at 827 * some point in the future. This is a more gentle operation than simply 828 * calling {@link ContentResolver#delete(Uri, String, String[])}, which 829 * would take effect immediately. 830 * <p> 831 * This method preserves trashed items for at least 48 hours before erasing 832 * them, giving the user a chance to untrash the item. 833 * 834 * @see MediaColumns#IS_TRASHED 835 * @see MediaStore#setIncludeTrashed(Uri) 836 * @see MediaStore#trash(Context, Uri) 837 * @see MediaStore#untrash(Context, Uri) 838 * @removed 839 */ 840 @Deprecated trash(@onNull Context context, @NonNull Uri uri)841 public static void trash(@NonNull Context context, @NonNull Uri uri) { 842 trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS); 843 } 844 845 /** 846 * Mark the given item as being "trashed", meaning it should be deleted at 847 * some point in the future. This is a more gentle operation than simply 848 * calling {@link ContentResolver#delete(Uri, String, String[])}, which 849 * would take effect immediately. 850 * <p> 851 * This method preserves trashed items for at least the given timeout before 852 * erasing them, giving the user a chance to untrash the item. 853 * 854 * @see MediaColumns#IS_TRASHED 855 * @see MediaStore#setIncludeTrashed(Uri) 856 * @see MediaStore#trash(Context, Uri) 857 * @see MediaStore#untrash(Context, Uri) 858 * @removed 859 */ 860 @Deprecated trash(@onNull Context context, @NonNull Uri uri, @DurationMillisLong long timeoutMillis)861 public static void trash(@NonNull Context context, @NonNull Uri uri, 862 @DurationMillisLong long timeoutMillis) { 863 if (timeoutMillis < 0) { 864 throw new IllegalArgumentException(); 865 } 866 867 final ContentValues values = new ContentValues(); 868 values.put(MediaColumns.IS_TRASHED, 1); 869 values.put(MediaColumns.DATE_EXPIRES, 870 (System.currentTimeMillis() + timeoutMillis) / 1000); 871 context.getContentResolver().update(uri, values, null, null); 872 } 873 874 /** 875 * Mark the given item as being "untrashed", meaning it should no longer be 876 * deleted as previously requested through {@link #trash(Context, Uri)}. 877 * 878 * @see MediaColumns#IS_TRASHED 879 * @see MediaStore#setIncludeTrashed(Uri) 880 * @see MediaStore#trash(Context, Uri) 881 * @see MediaStore#untrash(Context, Uri) 882 * @removed 883 */ 884 @Deprecated untrash(@onNull Context context, @NonNull Uri uri)885 public static void untrash(@NonNull Context context, @NonNull Uri uri) { 886 final ContentValues values = new ContentValues(); 887 values.put(MediaColumns.IS_TRASHED, 0); 888 values.putNull(MediaColumns.DATE_EXPIRES); 889 context.getContentResolver().update(uri, values, null, null); 890 } 891 892 /** 893 * Common media metadata columns. 894 */ 895 public interface MediaColumns extends BaseColumns { 896 /** 897 * Absolute filesystem path to the media item on disk. 898 * <p> 899 * Note that apps may not have filesystem permissions to directly access 900 * this path. Instead of trying to open this path directly, apps should 901 * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 902 * access. 903 * 904 * @deprecated Apps may not have filesystem permissions to directly 905 * access this path. Instead of trying to open this path 906 * directly, apps should use 907 * {@link ContentResolver#openFileDescriptor(Uri, String)} 908 * to gain access. 909 */ 910 @Deprecated 911 @Column(Cursor.FIELD_TYPE_STRING) 912 public static final String DATA = "_data"; 913 914 /** 915 * Hash of the media item on disk. 916 * <p> 917 * Contains a 20-byte binary blob which is the SHA-1 hash of the file as 918 * persisted on disk. For performance reasons, the hash may not be 919 * immediately available, in which case a {@code NULL} value will be 920 * returned. If the underlying file is modified, this value will be 921 * cleared and recalculated. 922 * <p> 923 * If you require the hash of a specific item, you can call 924 * {@link ContentResolver#canonicalize(Uri)}, which will block until the 925 * hash is calculated. 926 * 927 * @removed 928 */ 929 @Deprecated 930 @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true) 931 public static final String HASH = "_hash"; 932 933 /** 934 * The size of the media item. 935 */ 936 @BytesLong 937 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 938 public static final String SIZE = "_size"; 939 940 /** 941 * The display name of the media item. 942 * <p> 943 * For example, an item stored at 944 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 945 * display name of {@code IMG1024.JPG}. 946 */ 947 @Column(Cursor.FIELD_TYPE_STRING) 948 public static final String DISPLAY_NAME = "_display_name"; 949 950 /** 951 * The title of the media item. 952 */ 953 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 954 public static final String TITLE = "title"; 955 956 /** 957 * The time the media item was first added. 958 */ 959 @CurrentTimeSecondsLong 960 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 961 public static final String DATE_ADDED = "date_added"; 962 963 /** 964 * The time the media item was last modified. 965 */ 966 @CurrentTimeSecondsLong 967 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 968 public static final String DATE_MODIFIED = "date_modified"; 969 970 /** 971 * The time the media item was taken. 972 */ 973 @CurrentTimeMillisLong 974 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 975 public static final String DATE_TAKEN = "datetaken"; 976 977 /** 978 * The MIME type of the media item. 979 * <p> 980 * This is typically defined based on the file extension of the media 981 * item. However, it may be the value of the {@code format} attribute 982 * defined by the <em>Dublin Core Media Initiative</em> standard, 983 * extracted from any XMP metadata contained within this media item. 984 * <p class="note"> 985 * Note: the {@code format} attribute may be ignored if the top-level 986 * MIME type disagrees with the file extension. For example, it's 987 * reasonable for an {@code image/jpeg} file to declare a {@code format} 988 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 989 * {@code format} of {@code audio/ogg} would be ignored. 990 * <p> 991 * This is a read-only column that is automatically computed. 992 */ 993 @Column(Cursor.FIELD_TYPE_STRING) 994 public static final String MIME_TYPE = "mime_type"; 995 996 /** 997 * The MTP object handle of a newly transfered file. 998 * Used to pass the new file's object handle through the media scanner 999 * from MTP to the media provider 1000 * For internal use only by MTP, media scanner and media provider. 1001 * @hide 1002 */ 1003 @Deprecated 1004 // @Column(Cursor.FIELD_TYPE_INTEGER) 1005 public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id"; 1006 1007 /** 1008 * Non-zero if the media file is drm-protected 1009 * @hide 1010 */ 1011 @UnsupportedAppUsage 1012 @Deprecated 1013 @Column(Cursor.FIELD_TYPE_INTEGER) 1014 public static final String IS_DRM = "is_drm"; 1015 1016 /** 1017 * Flag indicating if a media item is pending, and still being inserted 1018 * by its owner. While this flag is set, only the owner of the item can 1019 * open the underlying file; requests from other apps will be rejected. 1020 * 1021 * @see MediaStore#setIncludePending(Uri) 1022 */ 1023 @Column(Cursor.FIELD_TYPE_INTEGER) 1024 public static final String IS_PENDING = "is_pending"; 1025 1026 /** 1027 * Flag indicating if a media item is trashed. 1028 * 1029 * @see MediaColumns#IS_TRASHED 1030 * @see MediaStore#setIncludeTrashed(Uri) 1031 * @see MediaStore#trash(Context, Uri) 1032 * @see MediaStore#untrash(Context, Uri) 1033 * @removed 1034 */ 1035 @Deprecated 1036 @Column(Cursor.FIELD_TYPE_INTEGER) 1037 public static final String IS_TRASHED = "is_trashed"; 1038 1039 /** 1040 * The time the media item should be considered expired. Typically only 1041 * meaningful in the context of {@link #IS_PENDING}. 1042 */ 1043 @CurrentTimeSecondsLong 1044 @Column(Cursor.FIELD_TYPE_INTEGER) 1045 public static final String DATE_EXPIRES = "date_expires"; 1046 1047 /** 1048 * The width of the media item, in pixels. 1049 */ 1050 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1051 public static final String WIDTH = "width"; 1052 1053 /** 1054 * The height of the media item, in pixels. 1055 */ 1056 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1057 public static final String HEIGHT = "height"; 1058 1059 /** 1060 * Package name that contributed this media. The value may be 1061 * {@code NULL} if ownership cannot be reliably determined. 1062 */ 1063 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1064 public static final String OWNER_PACKAGE_NAME = "owner_package_name"; 1065 1066 /** 1067 * Volume name of the specific storage device where this media item is 1068 * persisted. The value is typically one of the volume names returned 1069 * from {@link MediaStore#getExternalVolumeNames(Context)}. 1070 * <p> 1071 * This is a read-only column that is automatically computed. 1072 */ 1073 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1074 public static final String VOLUME_NAME = "volume_name"; 1075 1076 /** 1077 * Relative path of this media item within the storage device where it 1078 * is persisted. For example, an item stored at 1079 * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a 1080 * path of {@code DCIM/Vacation/}. 1081 * <p> 1082 * This value should only be used for organizational purposes, and you 1083 * should not attempt to construct or access a raw filesystem path using 1084 * this value. If you need to open a media item, use an API like 1085 * {@link ContentResolver#openFileDescriptor(Uri, String)}. 1086 * <p> 1087 * When this value is set to {@code NULL} during an 1088 * {@link ContentResolver#insert} operation, the newly created item will 1089 * be placed in a relevant default location based on the type of media 1090 * being inserted. For example, a {@code image/jpeg} item will be placed 1091 * under {@link Environment#DIRECTORY_PICTURES}. 1092 * <p> 1093 * You can modify this column during an {@link ContentResolver#update} 1094 * call, which will move the underlying file on disk. 1095 * <p> 1096 * In both cases above, content must be placed under a top-level 1097 * directory that is relevant to the media type. For example, attempting 1098 * to place a {@code audio/mpeg} file under 1099 * {@link Environment#DIRECTORY_PICTURES} will be rejected. 1100 */ 1101 @Column(Cursor.FIELD_TYPE_STRING) 1102 public static final String RELATIVE_PATH = "relative_path"; 1103 1104 /** 1105 * The primary directory name this media exists under. The value may be 1106 * {@code NULL} if the media doesn't have a primary directory name. 1107 * 1108 * @removed 1109 * @deprecated Replaced by {@link #RELATIVE_PATH}. 1110 */ 1111 @Column(Cursor.FIELD_TYPE_STRING) 1112 @Deprecated 1113 public static final String PRIMARY_DIRECTORY = "primary_directory"; 1114 1115 /** 1116 * The secondary directory name this media exists under. The value may 1117 * be {@code NULL} if the media doesn't have a secondary directory name. 1118 * 1119 * @removed 1120 * @deprecated Replaced by {@link #RELATIVE_PATH}. 1121 */ 1122 @Column(Cursor.FIELD_TYPE_STRING) 1123 @Deprecated 1124 public static final String SECONDARY_DIRECTORY = "secondary_directory"; 1125 1126 /** 1127 * The primary bucket ID of this media item. This can be useful to 1128 * present the user a first-level clustering of related media items. 1129 * This is a read-only column that is automatically computed. 1130 */ 1131 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1132 public static final String BUCKET_ID = "bucket_id"; 1133 1134 /** 1135 * The primary bucket display name of this media item. This can be 1136 * useful to present the user a first-level clustering of related 1137 * media items. This is a read-only column that is automatically 1138 * computed. 1139 */ 1140 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1141 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1142 1143 /** 1144 * The group ID of this media item. This can be useful to present 1145 * the user a grouping of related media items, such a burst of 1146 * images, or a {@code JPG} and {@code DNG} version of the same 1147 * image. 1148 * <p> 1149 * This is a read-only column that is automatically computed based 1150 * on the first portion of the filename. For example, 1151 * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG} 1152 * will have the same {@link #GROUP_ID} because the first portion of 1153 * their filenames is identical. 1154 * 1155 * @removed 1156 */ 1157 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1158 @Deprecated 1159 public static final String GROUP_ID = "group_id"; 1160 1161 /** 1162 * The "document ID" GUID as defined by the <em>XMP Media 1163 * Management</em> standard, extracted from any XMP metadata contained 1164 * within this media item. The value is {@code null} when no metadata 1165 * was found. 1166 * <p> 1167 * Each "document ID" is created once for each new resource. Different 1168 * renditions of that resource are expected to have different IDs. 1169 */ 1170 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1171 public static final String DOCUMENT_ID = "document_id"; 1172 1173 /** 1174 * The "instance ID" GUID as defined by the <em>XMP Media 1175 * Management</em> standard, extracted from any XMP metadata contained 1176 * within this media item. The value is {@code null} when no metadata 1177 * was found. 1178 * <p> 1179 * This "instance ID" changes with each save operation of a specific 1180 * "document ID". 1181 */ 1182 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1183 public static final String INSTANCE_ID = "instance_id"; 1184 1185 /** 1186 * The "original document ID" GUID as defined by the <em>XMP Media 1187 * Management</em> standard, extracted from any XMP metadata contained 1188 * within this media item. 1189 * <p> 1190 * This "original document ID" links a resource to its original source. 1191 * For example, when you save a PSD document as a JPEG, then convert the 1192 * JPEG to GIF format, the "original document ID" of both the JPEG and 1193 * GIF files is the "document ID" of the original PSD file. 1194 */ 1195 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1196 public static final String ORIGINAL_DOCUMENT_ID = "original_document_id"; 1197 1198 /** 1199 * The duration of the media item. 1200 */ 1201 @DurationMillisLong 1202 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1203 public static final String DURATION = "duration"; 1204 1205 /** 1206 * The orientation for the media item, expressed in degrees. For 1207 * example, 0, 90, 180, or 270 degrees. 1208 */ 1209 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1210 public static final String ORIENTATION = "orientation"; 1211 } 1212 1213 /** 1214 * Media provider table containing an index of all files in the media storage, 1215 * including non-media files. This should be used by applications that work with 1216 * non-media file types (text, HTML, PDF, etc) as well as applications that need to 1217 * work with multiple media file types in a single query. 1218 */ 1219 public static final class Files { 1220 /** @hide */ 1221 public static final String TABLE = "files"; 1222 1223 /** @hide */ 1224 public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL); 1225 1226 /** 1227 * Get the content:// style URI for the files table on the 1228 * given volume. 1229 * 1230 * @param volumeName the name of the volume to get the URI for 1231 * @return the URI to the files table on the given volume 1232 */ getContentUri(String volumeName)1233 public static Uri getContentUri(String volumeName) { 1234 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("file").build(); 1235 } 1236 1237 /** 1238 * Get the content:// style URI for a single row in the files table on the 1239 * given volume. 1240 * 1241 * @param volumeName the name of the volume to get the URI for 1242 * @param rowId the file to get the URI for 1243 * @return the URI to the files table on the given volume 1244 */ getContentUri(String volumeName, long rowId)1245 public static final Uri getContentUri(String volumeName, 1246 long rowId) { 1247 return ContentUris.withAppendedId(getContentUri(volumeName), rowId); 1248 } 1249 1250 /** 1251 * For use only by the MTP implementation. 1252 * @hide 1253 */ 1254 @UnsupportedAppUsage getMtpObjectsUri(String volumeName)1255 public static Uri getMtpObjectsUri(String volumeName) { 1256 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build(); 1257 } 1258 1259 /** 1260 * For use only by the MTP implementation. 1261 * @hide 1262 */ 1263 @UnsupportedAppUsage getMtpObjectsUri(String volumeName, long fileId)1264 public static final Uri getMtpObjectsUri(String volumeName, 1265 long fileId) { 1266 return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId); 1267 } 1268 1269 /** 1270 * Used to implement the MTP GetObjectReferences and SetObjectReferences commands. 1271 * @hide 1272 */ 1273 @UnsupportedAppUsage getMtpReferencesUri(String volumeName, long fileId)1274 public static final Uri getMtpReferencesUri(String volumeName, 1275 long fileId) { 1276 return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references") 1277 .build(); 1278 } 1279 1280 /** 1281 * Used to trigger special logic for directories. 1282 * @hide 1283 */ getDirectoryUri(String volumeName)1284 public static final Uri getDirectoryUri(String volumeName) { 1285 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build(); 1286 } 1287 1288 /** @hide */ getContentUriForPath(String path)1289 public static final Uri getContentUriForPath(String path) { 1290 return getContentUri(getVolumeName(new File(path))); 1291 } 1292 1293 /** 1294 * File metadata columns. 1295 */ 1296 public interface FileColumns extends MediaColumns { 1297 /** 1298 * The MTP storage ID of the file 1299 * @hide 1300 */ 1301 @UnsupportedAppUsage 1302 @Deprecated 1303 // @Column(Cursor.FIELD_TYPE_INTEGER) 1304 public static final String STORAGE_ID = "storage_id"; 1305 1306 /** 1307 * The MTP format code of the file 1308 * @hide 1309 */ 1310 @UnsupportedAppUsage 1311 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1312 public static final String FORMAT = "format"; 1313 1314 /** 1315 * The index of the parent directory of the file 1316 */ 1317 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1318 public static final String PARENT = "parent"; 1319 1320 /** 1321 * The MIME type of the media item. 1322 * <p> 1323 * This is typically defined based on the file extension of the media 1324 * item. However, it may be the value of the {@code format} attribute 1325 * defined by the <em>Dublin Core Media Initiative</em> standard, 1326 * extracted from any XMP metadata contained within this media item. 1327 * <p class="note"> 1328 * Note: the {@code format} attribute may be ignored if the top-level 1329 * MIME type disagrees with the file extension. For example, it's 1330 * reasonable for an {@code image/jpeg} file to declare a {@code format} 1331 * of {@code image/vnd.google.panorama360+jpg}, but declaring a 1332 * {@code format} of {@code audio/ogg} would be ignored. 1333 * <p> 1334 * This is a read-only column that is automatically computed. 1335 */ 1336 @Column(Cursor.FIELD_TYPE_STRING) 1337 public static final String MIME_TYPE = "mime_type"; 1338 1339 /** 1340 * The title of the media item. 1341 */ 1342 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1343 public static final String TITLE = "title"; 1344 1345 /** 1346 * The media type (audio, video, image or playlist) 1347 * of the file, or 0 for not a media file 1348 */ 1349 @Column(Cursor.FIELD_TYPE_INTEGER) 1350 public static final String MEDIA_TYPE = "media_type"; 1351 1352 /** 1353 * Constant for the {@link #MEDIA_TYPE} column indicating that file 1354 * is not an audio, image, video or playlist file. 1355 */ 1356 public static final int MEDIA_TYPE_NONE = 0; 1357 1358 /** 1359 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an image file. 1360 */ 1361 public static final int MEDIA_TYPE_IMAGE = 1; 1362 1363 /** 1364 * Constant for the {@link #MEDIA_TYPE} column indicating that file is an audio file. 1365 */ 1366 public static final int MEDIA_TYPE_AUDIO = 2; 1367 1368 /** 1369 * Constant for the {@link #MEDIA_TYPE} column indicating that file is a video file. 1370 */ 1371 public static final int MEDIA_TYPE_VIDEO = 3; 1372 1373 /** 1374 * Constant for the {@link #MEDIA_TYPE} column indicating that file is a playlist file. 1375 */ 1376 public static final int MEDIA_TYPE_PLAYLIST = 4; 1377 1378 /** 1379 * Column indicating if the file is part of Downloads collection. 1380 * @hide 1381 */ 1382 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 1383 public static final String IS_DOWNLOAD = "is_download"; 1384 } 1385 } 1386 1387 /** @hide */ 1388 public static class ThumbnailConstants { 1389 public static final int MINI_KIND = 1; 1390 public static final int FULL_SCREEN_KIND = 2; 1391 public static final int MICRO_KIND = 3; 1392 1393 public static final Point MINI_SIZE = new Point(512, 384); 1394 public static final Point FULL_SCREEN_SIZE = new Point(1024, 786); 1395 public static final Point MICRO_SIZE = new Point(96, 96); 1396 } 1397 1398 /** 1399 * Download metadata columns. 1400 */ 1401 public interface DownloadColumns extends MediaColumns { 1402 /** 1403 * Uri indicating where the item has been downloaded from. 1404 */ 1405 @Column(Cursor.FIELD_TYPE_STRING) 1406 String DOWNLOAD_URI = "download_uri"; 1407 1408 /** 1409 * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}. 1410 */ 1411 @Column(Cursor.FIELD_TYPE_STRING) 1412 String REFERER_URI = "referer_uri"; 1413 1414 /** 1415 * The description of the download. 1416 * 1417 * @removed 1418 */ 1419 @Deprecated 1420 @Column(Cursor.FIELD_TYPE_STRING) 1421 String DESCRIPTION = "description"; 1422 } 1423 1424 /** 1425 * Collection of downloaded items. 1426 */ 1427 public static final class Downloads implements DownloadColumns { Downloads()1428 private Downloads() {} 1429 1430 /** 1431 * The content:// style URI for the internal storage. 1432 */ 1433 @NonNull 1434 public static final Uri INTERNAL_CONTENT_URI = 1435 getContentUri("internal"); 1436 1437 /** 1438 * The content:// style URI for the "primary" external storage 1439 * volume. 1440 */ 1441 @NonNull 1442 public static final Uri EXTERNAL_CONTENT_URI = 1443 getContentUri("external"); 1444 1445 /** 1446 * The MIME type for this table. 1447 */ 1448 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download"; 1449 1450 /** 1451 * Regex that matches paths that needs to be considered part of downloads collection. 1452 * @hide 1453 */ 1454 public static final Pattern PATTERN_DOWNLOADS_FILE = Pattern.compile( 1455 "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/.+"); 1456 private static final Pattern PATTERN_DOWNLOADS_DIRECTORY = Pattern.compile( 1457 "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/?"); 1458 1459 /** 1460 * Get the content:// style URI for the downloads table on the 1461 * given volume. 1462 * 1463 * @param volumeName the name of the volume to get the URI for 1464 * @return the URI to the image media table on the given volume 1465 */ getContentUri(@onNull String volumeName)1466 public static @NonNull Uri getContentUri(@NonNull String volumeName) { 1467 return AUTHORITY_URI.buildUpon().appendPath(volumeName) 1468 .appendPath("downloads").build(); 1469 } 1470 1471 /** @hide */ getContentUri(@onNull String volumeName, long id)1472 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 1473 return ContentUris.withAppendedId(getContentUri(volumeName), id); 1474 } 1475 1476 /** @hide */ getContentUriForPath(@onNull String path)1477 public static @NonNull Uri getContentUriForPath(@NonNull String path) { 1478 return getContentUri(getVolumeName(new File(path))); 1479 } 1480 1481 /** @hide */ isDownload(@onNull String path)1482 public static boolean isDownload(@NonNull String path) { 1483 return PATTERN_DOWNLOADS_FILE.matcher(path).matches(); 1484 } 1485 1486 /** @hide */ isDownloadDir(@onNull String path)1487 public static boolean isDownloadDir(@NonNull String path) { 1488 return PATTERN_DOWNLOADS_DIRECTORY.matcher(path).matches(); 1489 } 1490 } 1491 1492 /** {@hide} */ getVolumeName(@onNull File path)1493 public static @NonNull String getVolumeName(@NonNull File path) { 1494 if (FileUtils.contains(Environment.getStorageDirectory(), path)) { 1495 final StorageManager sm = AppGlobals.getInitialApplication() 1496 .getSystemService(StorageManager.class); 1497 final StorageVolume sv = sm.getStorageVolume(path); 1498 if (sv != null) { 1499 if (sv.isPrimary()) { 1500 return VOLUME_EXTERNAL_PRIMARY; 1501 } else { 1502 return checkArgumentVolumeName(sv.getNormalizedUuid()); 1503 } 1504 } 1505 throw new IllegalStateException("Unknown volume at " + path); 1506 } else { 1507 return VOLUME_INTERNAL; 1508 } 1509 } 1510 1511 /** 1512 * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended 1513 * to be accessed elsewhere. 1514 */ 1515 @Deprecated 1516 private static class InternalThumbnails implements BaseColumns { 1517 /** 1518 * Currently outstanding thumbnail requests that can be cancelled. 1519 */ 1520 @GuardedBy("sPending") 1521 private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>(); 1522 1523 /** 1524 * Make a blocking request to obtain the given thumbnail, generating it 1525 * if needed. 1526 * 1527 * @see #cancelThumbnail(ContentResolver, Uri) 1528 */ 1529 @Deprecated getThumbnail(@onNull ContentResolver cr, @NonNull Uri uri, int kind, @Nullable BitmapFactory.Options opts)1530 static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri, 1531 int kind, @Nullable BitmapFactory.Options opts) { 1532 final Point size; 1533 if (kind == ThumbnailConstants.MICRO_KIND) { 1534 size = ThumbnailConstants.MICRO_SIZE; 1535 } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) { 1536 size = ThumbnailConstants.FULL_SCREEN_SIZE; 1537 } else if (kind == ThumbnailConstants.MINI_KIND) { 1538 size = ThumbnailConstants.MINI_SIZE; 1539 } else { 1540 throw new IllegalArgumentException("Unsupported kind: " + kind); 1541 } 1542 1543 CancellationSignal signal = null; 1544 synchronized (sPending) { 1545 signal = sPending.get(uri); 1546 if (signal == null) { 1547 signal = new CancellationSignal(); 1548 sPending.put(uri, signal); 1549 } 1550 } 1551 1552 try { 1553 return cr.loadThumbnail(uri, Point.convert(size), signal); 1554 } catch (IOException e) { 1555 Log.w(TAG, "Failed to obtain thumbnail for " + uri, e); 1556 return null; 1557 } finally { 1558 synchronized (sPending) { 1559 sPending.remove(uri); 1560 } 1561 } 1562 } 1563 1564 /** 1565 * This method cancels the thumbnail request so clients waiting for 1566 * {@link #getThumbnail} will be interrupted and return immediately. 1567 * Only the original process which made the request can cancel their own 1568 * requests. 1569 */ 1570 @Deprecated cancelThumbnail(@onNull ContentResolver cr, @NonNull Uri uri)1571 static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) { 1572 synchronized (sPending) { 1573 final CancellationSignal signal = sPending.get(uri); 1574 if (signal != null) { 1575 signal.cancel(); 1576 } 1577 } 1578 } 1579 } 1580 1581 /** 1582 * Collection of all media with MIME type of {@code image/*}. 1583 */ 1584 public static final class Images { 1585 /** 1586 * Image metadata columns. 1587 */ 1588 public interface ImageColumns extends MediaColumns { 1589 /** 1590 * The description of the image 1591 */ 1592 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1593 public static final String DESCRIPTION = "description"; 1594 1595 /** 1596 * The picasa id of the image 1597 * 1598 * @deprecated this value was only relevant for images hosted on 1599 * Picasa, which are no longer supported. 1600 */ 1601 @Deprecated 1602 @Column(Cursor.FIELD_TYPE_STRING) 1603 public static final String PICASA_ID = "picasa_id"; 1604 1605 /** 1606 * Whether the video should be published as public or private 1607 */ 1608 @Column(Cursor.FIELD_TYPE_INTEGER) 1609 public static final String IS_PRIVATE = "isprivate"; 1610 1611 /** 1612 * The latitude where the image was captured. 1613 * 1614 * @deprecated location details are no longer indexed for privacy 1615 * reasons, and this value is now always {@code null}. 1616 * You can still manually obtain location metadata using 1617 * {@link ExifInterface#getLatLong(float[])}. 1618 */ 1619 @Deprecated 1620 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1621 public static final String LATITUDE = "latitude"; 1622 1623 /** 1624 * The longitude where the image was captured. 1625 * 1626 * @deprecated location details are no longer indexed for privacy 1627 * reasons, and this value is now always {@code null}. 1628 * You can still manually obtain location metadata using 1629 * {@link ExifInterface#getLatLong(float[])}. 1630 */ 1631 @Deprecated 1632 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 1633 public static final String LONGITUDE = "longitude"; 1634 1635 /** @removed promoted to parent interface */ 1636 public static final String DATE_TAKEN = "datetaken"; 1637 /** @removed promoted to parent interface */ 1638 public static final String ORIENTATION = "orientation"; 1639 1640 /** 1641 * The mini thumb id. 1642 * 1643 * @deprecated all thumbnails should be obtained via 1644 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 1645 * value is no longer supported. 1646 */ 1647 @Deprecated 1648 @Column(Cursor.FIELD_TYPE_INTEGER) 1649 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 1650 1651 /** @removed promoted to parent interface */ 1652 public static final String BUCKET_ID = "bucket_id"; 1653 /** @removed promoted to parent interface */ 1654 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 1655 /** @removed promoted to parent interface */ 1656 public static final String GROUP_ID = "group_id"; 1657 } 1658 1659 public static final class Media implements ImageColumns { 1660 /** 1661 * @deprecated all queries should be performed through 1662 * {@link ContentResolver} directly, which offers modern 1663 * features like {@link CancellationSignal}. 1664 */ 1665 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)1666 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 1667 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 1668 } 1669 1670 /** 1671 * @deprecated all queries should be performed through 1672 * {@link ContentResolver} directly, which offers modern 1673 * features like {@link CancellationSignal}. 1674 */ 1675 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)1676 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 1677 String where, String orderBy) { 1678 return cr.query(uri, projection, where, 1679 null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 1680 } 1681 1682 /** 1683 * @deprecated all queries should be performed through 1684 * {@link ContentResolver} directly, which offers modern 1685 * features like {@link CancellationSignal}. 1686 */ 1687 @Deprecated query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)1688 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, 1689 String selection, String [] selectionArgs, String orderBy) { 1690 return cr.query(uri, projection, selection, 1691 selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); 1692 } 1693 1694 /** 1695 * Retrieves an image for the given url as a {@link Bitmap}. 1696 * 1697 * @param cr The content resolver to use 1698 * @param url The url of the image 1699 * @deprecated loading of images should be performed through 1700 * {@link ImageDecoder#createSource(ContentResolver, Uri)}, 1701 * which offers modern features like 1702 * {@link PostProcessor}. 1703 */ 1704 @Deprecated getBitmap(ContentResolver cr, Uri url)1705 public static final Bitmap getBitmap(ContentResolver cr, Uri url) 1706 throws FileNotFoundException, IOException { 1707 InputStream input = cr.openInputStream(url); 1708 Bitmap bitmap = BitmapFactory.decodeStream(input); 1709 input.close(); 1710 return bitmap; 1711 } 1712 1713 /** 1714 * Insert an image and create a thumbnail for it. 1715 * 1716 * @param cr The content resolver to use 1717 * @param imagePath The path to the image to insert 1718 * @param name The name of the image 1719 * @param description The description of the image 1720 * @return The URL to the newly created image 1721 * @deprecated inserting of images should be performed using 1722 * {@link MediaColumns#IS_PENDING}, which offers richer 1723 * control over lifecycle. 1724 */ 1725 @Deprecated insertImage(ContentResolver cr, String imagePath, String name, String description)1726 public static final String insertImage(ContentResolver cr, String imagePath, 1727 String name, String description) throws FileNotFoundException { 1728 final File file = new File(imagePath); 1729 final String mimeType = MediaFile.getMimeTypeForFile(imagePath); 1730 1731 if (TextUtils.isEmpty(name)) name = "Image"; 1732 final PendingParams params = new PendingParams( 1733 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType); 1734 1735 final Context context = AppGlobals.getInitialApplication(); 1736 final Uri pendingUri = createPending(context, params); 1737 try (PendingSession session = openPending(context, pendingUri)) { 1738 try (InputStream in = new FileInputStream(file); 1739 OutputStream out = session.openOutputStream()) { 1740 FileUtils.copy(in, out); 1741 } 1742 return session.publish().toString(); 1743 } catch (Exception e) { 1744 Log.w(TAG, "Failed to insert image", e); 1745 context.getContentResolver().delete(pendingUri, null, null); 1746 return null; 1747 } 1748 } 1749 1750 /** 1751 * Insert an image and create a thumbnail for it. 1752 * 1753 * @param cr The content resolver to use 1754 * @param source The stream to use for the image 1755 * @param title The name of the image 1756 * @param description The description of the image 1757 * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored 1758 * for any reason. 1759 * @deprecated inserting of images should be performed using 1760 * {@link MediaColumns#IS_PENDING}, which offers richer 1761 * control over lifecycle. 1762 */ 1763 @Deprecated insertImage(ContentResolver cr, Bitmap source, String title, String description)1764 public static final String insertImage(ContentResolver cr, Bitmap source, 1765 String title, String description) { 1766 if (TextUtils.isEmpty(title)) title = "Image"; 1767 final PendingParams params = new PendingParams( 1768 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg"); 1769 1770 final Context context = AppGlobals.getInitialApplication(); 1771 final Uri pendingUri = createPending(context, params); 1772 try (PendingSession session = openPending(context, pendingUri)) { 1773 try (OutputStream out = session.openOutputStream()) { 1774 source.compress(Bitmap.CompressFormat.JPEG, 90, out); 1775 } 1776 return session.publish().toString(); 1777 } catch (Exception e) { 1778 Log.w(TAG, "Failed to insert image", e); 1779 context.getContentResolver().delete(pendingUri, null, null); 1780 return null; 1781 } 1782 } 1783 1784 /** 1785 * Get the content:// style URI for the image media table on the 1786 * given volume. 1787 * 1788 * @param volumeName the name of the volume to get the URI for 1789 * @return the URI to the image media table on the given volume 1790 */ getContentUri(String volumeName)1791 public static Uri getContentUri(String volumeName) { 1792 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 1793 .appendPath("media").build(); 1794 } 1795 1796 /** @hide */ getContentUri(@onNull String volumeName, long id)1797 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 1798 return ContentUris.withAppendedId(getContentUri(volumeName), id); 1799 } 1800 1801 /** 1802 * The content:// style URI for the internal storage. 1803 */ 1804 public static final Uri INTERNAL_CONTENT_URI = 1805 getContentUri("internal"); 1806 1807 /** 1808 * The content:// style URI for the "primary" external storage 1809 * volume. 1810 */ 1811 public static final Uri EXTERNAL_CONTENT_URI = 1812 getContentUri("external"); 1813 1814 /** 1815 * The MIME type of of this directory of 1816 * images. Note that each entry in this directory will have a standard 1817 * image MIME type as appropriate -- for example, image/jpeg. 1818 */ 1819 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image"; 1820 1821 /** 1822 * The default sort order for this table 1823 */ 1824 public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME; 1825 } 1826 1827 /** 1828 * This class provides utility methods to obtain thumbnails for various 1829 * {@link Images} items. 1830 * 1831 * @deprecated Callers should migrate to using 1832 * {@link ContentResolver#loadThumbnail}, since it offers 1833 * richer control over requested thumbnail sizes and 1834 * cancellation behavior. 1835 */ 1836 @Deprecated 1837 public static class Thumbnails implements BaseColumns { 1838 /** 1839 * @deprecated all queries should be performed through 1840 * {@link ContentResolver} directly, which offers modern 1841 * features like {@link CancellationSignal}. 1842 */ 1843 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)1844 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 1845 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 1846 } 1847 1848 /** 1849 * @deprecated all queries should be performed through 1850 * {@link ContentResolver} directly, which offers modern 1851 * features like {@link CancellationSignal}. 1852 */ 1853 @Deprecated queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)1854 public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, 1855 String[] projection) { 1856 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER); 1857 } 1858 1859 /** 1860 * @deprecated all queries should be performed through 1861 * {@link ContentResolver} directly, which offers modern 1862 * features like {@link CancellationSignal}. 1863 */ 1864 @Deprecated queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)1865 public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, 1866 String[] projection) { 1867 return cr.query(EXTERNAL_CONTENT_URI, projection, 1868 IMAGE_ID + " = " + origId + " AND " + KIND + " = " + 1869 kind, null, null); 1870 } 1871 1872 /** 1873 * Cancel any outstanding {@link #getThumbnail} requests, causing 1874 * them to return by throwing a {@link OperationCanceledException}. 1875 * <p> 1876 * This method has no effect on 1877 * {@link ContentResolver#loadThumbnail} calls, since they provide 1878 * their own {@link CancellationSignal}. 1879 * 1880 * @deprecated Callers should migrate to using 1881 * {@link ContentResolver#loadThumbnail}, since it 1882 * offers richer control over requested thumbnail sizes 1883 * and cancellation behavior. 1884 */ 1885 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)1886 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 1887 final Uri uri = ContentUris.withAppendedId( 1888 Images.Media.EXTERNAL_CONTENT_URI, origId); 1889 InternalThumbnails.cancelThumbnail(cr, uri); 1890 } 1891 1892 /** 1893 * Return thumbnail representing a specific image item. If a 1894 * thumbnail doesn't exist, this method will block until it's 1895 * generated. Callers are responsible for their own in-memory 1896 * caching of returned values. 1897 * 1898 * @param imageId the image item to obtain a thumbnail for. 1899 * @param kind optimal thumbnail size desired. 1900 * @return decoded thumbnail, or {@code null} if problem was 1901 * encountered. 1902 * @deprecated Callers should migrate to using 1903 * {@link ContentResolver#loadThumbnail}, since it 1904 * offers richer control over requested thumbnail sizes 1905 * and cancellation behavior. 1906 */ 1907 @Deprecated getThumbnail(ContentResolver cr, long imageId, int kind, BitmapFactory.Options options)1908 public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind, 1909 BitmapFactory.Options options) { 1910 final Uri uri = ContentUris.withAppendedId( 1911 Images.Media.EXTERNAL_CONTENT_URI, imageId); 1912 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 1913 } 1914 1915 /** 1916 * Cancel any outstanding {@link #getThumbnail} requests, causing 1917 * them to return by throwing a {@link OperationCanceledException}. 1918 * <p> 1919 * This method has no effect on 1920 * {@link ContentResolver#loadThumbnail} calls, since they provide 1921 * their own {@link CancellationSignal}. 1922 * 1923 * @deprecated Callers should migrate to using 1924 * {@link ContentResolver#loadThumbnail}, since it 1925 * offers richer control over requested thumbnail sizes 1926 * and cancellation behavior. 1927 */ 1928 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)1929 public static void cancelThumbnailRequest(ContentResolver cr, long origId, 1930 long groupId) { 1931 cancelThumbnailRequest(cr, origId); 1932 } 1933 1934 /** 1935 * Return thumbnail representing a specific image item. If a 1936 * thumbnail doesn't exist, this method will block until it's 1937 * generated. Callers are responsible for their own in-memory 1938 * caching of returned values. 1939 * 1940 * @param imageId the image item to obtain a thumbnail for. 1941 * @param kind optimal thumbnail size desired. 1942 * @return decoded thumbnail, or {@code null} if problem was 1943 * encountered. 1944 * @deprecated Callers should migrate to using 1945 * {@link ContentResolver#loadThumbnail}, since it 1946 * offers richer control over requested thumbnail sizes 1947 * and cancellation behavior. 1948 */ 1949 @Deprecated getThumbnail(ContentResolver cr, long imageId, long groupId, int kind, BitmapFactory.Options options)1950 public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId, 1951 int kind, BitmapFactory.Options options) { 1952 return getThumbnail(cr, imageId, kind, options); 1953 } 1954 1955 /** 1956 * Get the content:// style URI for the image media table on the 1957 * given volume. 1958 * 1959 * @param volumeName the name of the volume to get the URI for 1960 * @return the URI to the image media table on the given volume 1961 */ getContentUri(String volumeName)1962 public static Uri getContentUri(String volumeName) { 1963 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images") 1964 .appendPath("thumbnails").build(); 1965 } 1966 1967 /** 1968 * The content:// style URI for the internal storage. 1969 */ 1970 public static final Uri INTERNAL_CONTENT_URI = 1971 getContentUri("internal"); 1972 1973 /** 1974 * The content:// style URI for the "primary" external storage 1975 * volume. 1976 */ 1977 public static final Uri EXTERNAL_CONTENT_URI = 1978 getContentUri("external"); 1979 1980 /** 1981 * The default sort order for this table 1982 */ 1983 public static final String DEFAULT_SORT_ORDER = "image_id ASC"; 1984 1985 /** 1986 * Path to the thumbnail file on disk. 1987 * <p> 1988 * Note that apps may not have filesystem permissions to directly 1989 * access this path. Instead of trying to open this path directly, 1990 * apps should use 1991 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 1992 * access. 1993 * 1994 * @deprecated Apps may not have filesystem permissions to directly 1995 * access this path. Instead of trying to open this path 1996 * directly, apps should use 1997 * {@link ContentResolver#loadThumbnail} 1998 * to gain access. 1999 */ 2000 @Deprecated 2001 @Column(Cursor.FIELD_TYPE_STRING) 2002 public static final String DATA = "_data"; 2003 2004 /** 2005 * The original image for the thumbnal 2006 */ 2007 @Column(Cursor.FIELD_TYPE_INTEGER) 2008 public static final String IMAGE_ID = "image_id"; 2009 2010 /** 2011 * The kind of the thumbnail 2012 */ 2013 @Column(Cursor.FIELD_TYPE_INTEGER) 2014 public static final String KIND = "kind"; 2015 2016 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 2017 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 2018 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 2019 2020 /** 2021 * The blob raw data of thumbnail 2022 * 2023 * @deprecated this column never existed internally, and could never 2024 * have returned valid data. 2025 */ 2026 @Deprecated 2027 @Column(Cursor.FIELD_TYPE_BLOB) 2028 public static final String THUMB_DATA = "thumb_data"; 2029 2030 /** 2031 * The width of the thumbnal 2032 */ 2033 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2034 public static final String WIDTH = "width"; 2035 2036 /** 2037 * The height of the thumbnail 2038 */ 2039 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2040 public static final String HEIGHT = "height"; 2041 } 2042 } 2043 2044 /** 2045 * Collection of all media with MIME type of {@code audio/*}. 2046 */ 2047 public static final class Audio { 2048 /** 2049 * Audio metadata columns. 2050 */ 2051 public interface AudioColumns extends MediaColumns { 2052 2053 /** 2054 * A non human readable key calculated from the TITLE, used for 2055 * searching, sorting and grouping 2056 */ 2057 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2058 public static final String TITLE_KEY = "title_key"; 2059 2060 /** @removed promoted to parent interface */ 2061 public static final String DURATION = "duration"; 2062 2063 /** 2064 * The position within the audio item at which playback should be 2065 * resumed. 2066 */ 2067 @DurationMillisLong 2068 @Column(Cursor.FIELD_TYPE_INTEGER) 2069 public static final String BOOKMARK = "bookmark"; 2070 2071 /** 2072 * The id of the artist who created the audio file, if any 2073 */ 2074 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2075 public static final String ARTIST_ID = "artist_id"; 2076 2077 /** 2078 * The artist who created the audio file, if any 2079 */ 2080 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2081 public static final String ARTIST = "artist"; 2082 2083 /** 2084 * The artist credited for the album that contains the audio file 2085 * @hide 2086 */ 2087 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2088 public static final String ALBUM_ARTIST = "album_artist"; 2089 2090 /** 2091 * Whether the song is part of a compilation 2092 * @hide 2093 */ 2094 @Deprecated 2095 // @Column(Cursor.FIELD_TYPE_STRING) 2096 public static final String COMPILATION = "compilation"; 2097 2098 /** 2099 * A non human readable key calculated from the ARTIST, used for 2100 * searching, sorting and grouping 2101 */ 2102 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2103 public static final String ARTIST_KEY = "artist_key"; 2104 2105 /** 2106 * The composer of the audio file, if any 2107 */ 2108 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2109 public static final String COMPOSER = "composer"; 2110 2111 /** 2112 * The id of the album the audio file is from, if any 2113 */ 2114 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2115 public static final String ALBUM_ID = "album_id"; 2116 2117 /** 2118 * The album the audio file is from, if any 2119 */ 2120 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2121 public static final String ALBUM = "album"; 2122 2123 /** 2124 * A non human readable key calculated from the ALBUM, used for 2125 * searching, sorting and grouping 2126 */ 2127 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2128 public static final String ALBUM_KEY = "album_key"; 2129 2130 /** 2131 * The track number of this song on the album, if any. 2132 * This number encodes both the track number and the 2133 * disc number. For multi-disc sets, this number will 2134 * be 1xxx for tracks on the first disc, 2xxx for tracks 2135 * on the second disc, etc. 2136 */ 2137 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2138 public static final String TRACK = "track"; 2139 2140 /** 2141 * The year the audio file was recorded, if any 2142 */ 2143 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2144 public static final String YEAR = "year"; 2145 2146 /** 2147 * Non-zero if the audio file is music 2148 */ 2149 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2150 public static final String IS_MUSIC = "is_music"; 2151 2152 /** 2153 * Non-zero if the audio file is a podcast 2154 */ 2155 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2156 public static final String IS_PODCAST = "is_podcast"; 2157 2158 /** 2159 * Non-zero if the audio file may be a ringtone 2160 */ 2161 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2162 public static final String IS_RINGTONE = "is_ringtone"; 2163 2164 /** 2165 * Non-zero if the audio file may be an alarm 2166 */ 2167 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2168 public static final String IS_ALARM = "is_alarm"; 2169 2170 /** 2171 * Non-zero if the audio file may be a notification sound 2172 */ 2173 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2174 public static final String IS_NOTIFICATION = "is_notification"; 2175 2176 /** 2177 * Non-zero if the audio file is an audiobook 2178 */ 2179 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2180 public static final String IS_AUDIOBOOK = "is_audiobook"; 2181 2182 /** 2183 * The genre of the audio file, if any 2184 * Does not exist in the database - only used by the media scanner for inserts. 2185 * @hide 2186 */ 2187 @Deprecated 2188 // @Column(Cursor.FIELD_TYPE_STRING) 2189 public static final String GENRE = "genre"; 2190 2191 /** 2192 * The resource URI of a localized title, if any 2193 * Conforms to this pattern: 2194 * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE} 2195 * Authority: Package Name of ringtone title provider 2196 * First Path Segment: Type of resource (must be "string") 2197 * Second Path Segment: Resource ID of title 2198 * @hide 2199 */ 2200 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2201 public static final String TITLE_RESOURCE_URI = "title_resource_uri"; 2202 } 2203 2204 /** 2205 * Converts a name to a "key" that can be used for grouping, sorting 2206 * and searching. 2207 * The rules that govern this conversion are: 2208 * - remove 'special' characters like ()[]'!?., 2209 * - remove leading/trailing spaces 2210 * - convert everything to lowercase 2211 * - remove leading "the ", "an " and "a " 2212 * - remove trailing ", the|an|a" 2213 * - remove accents. This step leaves us with CollationKey data, 2214 * which is not human readable 2215 * 2216 * @param name The artist or album name to convert 2217 * @return The "key" for the given name. 2218 */ keyFor(String name)2219 public static String keyFor(String name) { 2220 if (name != null) { 2221 boolean sortfirst = false; 2222 if (name.equals(UNKNOWN_STRING)) { 2223 return "\001"; 2224 } 2225 // Check if the first character is \001. We use this to 2226 // force sorting of certain special files, like the silent ringtone. 2227 if (name.startsWith("\001")) { 2228 sortfirst = true; 2229 } 2230 name = name.trim().toLowerCase(); 2231 if (name.startsWith("the ")) { 2232 name = name.substring(4); 2233 } 2234 if (name.startsWith("an ")) { 2235 name = name.substring(3); 2236 } 2237 if (name.startsWith("a ")) { 2238 name = name.substring(2); 2239 } 2240 if (name.endsWith(", the") || name.endsWith(",the") || 2241 name.endsWith(", an") || name.endsWith(",an") || 2242 name.endsWith(", a") || name.endsWith(",a")) { 2243 name = name.substring(0, name.lastIndexOf(',')); 2244 } 2245 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim(); 2246 if (name.length() > 0) { 2247 // Insert a separator between the characters to avoid 2248 // matches on a partial character. If we ever change 2249 // to start-of-word-only matches, this can be removed. 2250 StringBuilder b = new StringBuilder(); 2251 b.append('.'); 2252 int nl = name.length(); 2253 for (int i = 0; i < nl; i++) { 2254 b.append(name.charAt(i)); 2255 b.append('.'); 2256 } 2257 name = b.toString(); 2258 String key = DatabaseUtils.getCollationKey(name); 2259 if (sortfirst) { 2260 key = "\001" + key; 2261 } 2262 return key; 2263 } else { 2264 return ""; 2265 } 2266 } 2267 return null; 2268 } 2269 2270 public static final class Media implements AudioColumns { 2271 /** 2272 * Get the content:// style URI for the audio media table on the 2273 * given volume. 2274 * 2275 * @param volumeName the name of the volume to get the URI for 2276 * @return the URI to the audio media table on the given volume 2277 */ getContentUri(String volumeName)2278 public static Uri getContentUri(String volumeName) { 2279 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2280 .appendPath("media").build(); 2281 } 2282 2283 /** @hide */ getContentUri(@onNull String volumeName, long id)2284 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 2285 return ContentUris.withAppendedId(getContentUri(volumeName), id); 2286 } 2287 2288 /** 2289 * Get the content:// style URI for the given audio media file. 2290 * 2291 * @deprecated Apps may not have filesystem permissions to directly 2292 * access this path. 2293 */ 2294 @Deprecated getContentUriForPath(@onNull String path)2295 public static @Nullable Uri getContentUriForPath(@NonNull String path) { 2296 return getContentUri(getVolumeName(new File(path))); 2297 } 2298 2299 /** 2300 * The content:// style URI for the internal storage. 2301 */ 2302 public static final Uri INTERNAL_CONTENT_URI = 2303 getContentUri("internal"); 2304 2305 /** 2306 * The content:// style URI for the "primary" external storage 2307 * volume. 2308 */ 2309 public static final Uri EXTERNAL_CONTENT_URI = 2310 getContentUri("external"); 2311 2312 /** 2313 * The MIME type for this table. 2314 */ 2315 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio"; 2316 2317 /** 2318 * The MIME type for an audio track. 2319 */ 2320 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio"; 2321 2322 /** 2323 * The default sort order for this table 2324 */ 2325 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 2326 2327 /** 2328 * Activity Action: Start SoundRecorder application. 2329 * <p>Input: nothing. 2330 * <p>Output: An uri to the recorded sound stored in the Media Library 2331 * if the recording was successful. 2332 * May also contain the extra EXTRA_MAX_BYTES. 2333 * @see #EXTRA_MAX_BYTES 2334 */ 2335 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 2336 public static final String RECORD_SOUND_ACTION = 2337 "android.provider.MediaStore.RECORD_SOUND"; 2338 2339 /** 2340 * The name of the Intent-extra used to define a maximum file size for 2341 * a recording made by the SoundRecorder application. 2342 * 2343 * @see #RECORD_SOUND_ACTION 2344 */ 2345 public static final String EXTRA_MAX_BYTES = 2346 "android.provider.MediaStore.extra.MAX_BYTES"; 2347 } 2348 2349 /** 2350 * Audio genre metadata columns. 2351 */ 2352 public interface GenresColumns { 2353 /** 2354 * The name of the genre 2355 */ 2356 @Column(Cursor.FIELD_TYPE_STRING) 2357 public static final String NAME = "name"; 2358 } 2359 2360 /** 2361 * Contains all genres for audio files 2362 */ 2363 public static final class Genres implements BaseColumns, GenresColumns { 2364 /** 2365 * Get the content:// style URI for the audio genres table on the 2366 * given volume. 2367 * 2368 * @param volumeName the name of the volume to get the URI for 2369 * @return the URI to the audio genres table on the given volume 2370 */ getContentUri(String volumeName)2371 public static Uri getContentUri(String volumeName) { 2372 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2373 .appendPath("genres").build(); 2374 } 2375 2376 /** 2377 * Get the content:// style URI for querying the genres of an audio file. 2378 * 2379 * @param volumeName the name of the volume to get the URI for 2380 * @param audioId the ID of the audio file for which to retrieve the genres 2381 * @return the URI to for querying the genres for the audio file 2382 * with the given the volume and audioID 2383 */ getContentUriForAudioId(String volumeName, int audioId)2384 public static Uri getContentUriForAudioId(String volumeName, int audioId) { 2385 return ContentUris.withAppendedId(Audio.Media.getContentUri(volumeName), audioId) 2386 .buildUpon().appendPath("genres").build(); 2387 } 2388 2389 /** 2390 * The content:// style URI for the internal storage. 2391 */ 2392 public static final Uri INTERNAL_CONTENT_URI = 2393 getContentUri("internal"); 2394 2395 /** 2396 * The content:// style URI for the "primary" external storage 2397 * volume. 2398 */ 2399 public static final Uri EXTERNAL_CONTENT_URI = 2400 getContentUri("external"); 2401 2402 /** 2403 * The MIME type for this table. 2404 */ 2405 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre"; 2406 2407 /** 2408 * The MIME type for entries in this table. 2409 */ 2410 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre"; 2411 2412 /** 2413 * The default sort order for this table 2414 */ 2415 public static final String DEFAULT_SORT_ORDER = NAME; 2416 2417 /** 2418 * Sub-directory of each genre containing all members. 2419 */ 2420 public static final class Members implements AudioColumns { 2421 getContentUri(String volumeName, long genreId)2422 public static final Uri getContentUri(String volumeName, long genreId) { 2423 return ContentUris 2424 .withAppendedId(Audio.Genres.getContentUri(volumeName), genreId) 2425 .buildUpon().appendPath("members").build(); 2426 } 2427 2428 /** 2429 * A subdirectory of each genre containing all member audio files. 2430 */ 2431 public static final String CONTENT_DIRECTORY = "members"; 2432 2433 /** 2434 * The default sort order for this table 2435 */ 2436 public static final String DEFAULT_SORT_ORDER = TITLE_KEY; 2437 2438 /** 2439 * The ID of the audio file 2440 */ 2441 @Column(Cursor.FIELD_TYPE_INTEGER) 2442 public static final String AUDIO_ID = "audio_id"; 2443 2444 /** 2445 * The ID of the genre 2446 */ 2447 @Column(Cursor.FIELD_TYPE_INTEGER) 2448 public static final String GENRE_ID = "genre_id"; 2449 } 2450 } 2451 2452 /** 2453 * Audio playlist metadata columns. 2454 */ 2455 public interface PlaylistsColumns { 2456 /** 2457 * The name of the playlist 2458 */ 2459 @Column(Cursor.FIELD_TYPE_STRING) 2460 public static final String NAME = "name"; 2461 2462 /** 2463 * Path to the playlist file on disk. 2464 * <p> 2465 * Note that apps may not have filesystem permissions to directly 2466 * access this path. Instead of trying to open this path directly, 2467 * apps should use 2468 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 2469 * access. 2470 * 2471 * @deprecated Apps may not have filesystem permissions to directly 2472 * access this path. Instead of trying to open this path 2473 * directly, apps should use 2474 * {@link ContentResolver#openFileDescriptor(Uri, String)} 2475 * to gain access. 2476 */ 2477 @Deprecated 2478 @Column(Cursor.FIELD_TYPE_STRING) 2479 public static final String DATA = "_data"; 2480 2481 /** 2482 * The time the media item was first added. 2483 */ 2484 @CurrentTimeSecondsLong 2485 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2486 public static final String DATE_ADDED = "date_added"; 2487 2488 /** 2489 * The time the media item was last modified. 2490 */ 2491 @CurrentTimeSecondsLong 2492 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2493 public static final String DATE_MODIFIED = "date_modified"; 2494 } 2495 2496 /** 2497 * Contains playlists for audio files 2498 */ 2499 public static final class Playlists implements BaseColumns, 2500 PlaylistsColumns { 2501 /** 2502 * Get the content:// style URI for the audio playlists table on the 2503 * given volume. 2504 * 2505 * @param volumeName the name of the volume to get the URI for 2506 * @return the URI to the audio playlists table on the given volume 2507 */ getContentUri(String volumeName)2508 public static Uri getContentUri(String volumeName) { 2509 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2510 .appendPath("playlists").build(); 2511 } 2512 2513 /** 2514 * The content:// style URI for the internal storage. 2515 */ 2516 public static final Uri INTERNAL_CONTENT_URI = 2517 getContentUri("internal"); 2518 2519 /** 2520 * The content:// style URI for the "primary" external storage 2521 * volume. 2522 */ 2523 public static final Uri EXTERNAL_CONTENT_URI = 2524 getContentUri("external"); 2525 2526 /** 2527 * The MIME type for this table. 2528 */ 2529 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist"; 2530 2531 /** 2532 * The MIME type for entries in this table. 2533 */ 2534 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist"; 2535 2536 /** 2537 * The default sort order for this table 2538 */ 2539 public static final String DEFAULT_SORT_ORDER = NAME; 2540 2541 /** 2542 * Sub-directory of each playlist containing all members. 2543 */ 2544 public static final class Members implements AudioColumns { getContentUri(String volumeName, long playlistId)2545 public static final Uri getContentUri(String volumeName, long playlistId) { 2546 return ContentUris 2547 .withAppendedId(Audio.Playlists.getContentUri(volumeName), playlistId) 2548 .buildUpon().appendPath("members").build(); 2549 } 2550 2551 /** 2552 * Convenience method to move a playlist item to a new location 2553 * @param res The content resolver to use 2554 * @param playlistId The numeric id of the playlist 2555 * @param from The position of the item to move 2556 * @param to The position to move the item to 2557 * @return true on success 2558 */ moveItem(ContentResolver res, long playlistId, int from, int to)2559 public static final boolean moveItem(ContentResolver res, 2560 long playlistId, int from, int to) { 2561 Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", 2562 playlistId) 2563 .buildUpon() 2564 .appendEncodedPath(String.valueOf(from)) 2565 .appendQueryParameter("move", "true") 2566 .build(); 2567 ContentValues values = new ContentValues(); 2568 values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to); 2569 return res.update(uri, values, null, null) != 0; 2570 } 2571 2572 /** 2573 * The ID within the playlist. 2574 */ 2575 @Column(Cursor.FIELD_TYPE_INTEGER) 2576 public static final String _ID = "_id"; 2577 2578 /** 2579 * A subdirectory of each playlist containing all member audio 2580 * files. 2581 */ 2582 public static final String CONTENT_DIRECTORY = "members"; 2583 2584 /** 2585 * The ID of the audio file 2586 */ 2587 @Column(Cursor.FIELD_TYPE_INTEGER) 2588 public static final String AUDIO_ID = "audio_id"; 2589 2590 /** 2591 * The ID of the playlist 2592 */ 2593 @Column(Cursor.FIELD_TYPE_INTEGER) 2594 public static final String PLAYLIST_ID = "playlist_id"; 2595 2596 /** 2597 * The order of the songs in the playlist 2598 */ 2599 @Column(Cursor.FIELD_TYPE_INTEGER) 2600 public static final String PLAY_ORDER = "play_order"; 2601 2602 /** 2603 * The default sort order for this table 2604 */ 2605 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER; 2606 } 2607 } 2608 2609 /** 2610 * Audio artist metadata columns. 2611 */ 2612 public interface ArtistColumns { 2613 /** 2614 * The artist who created the audio file, if any 2615 */ 2616 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2617 public static final String ARTIST = "artist"; 2618 2619 /** 2620 * A non human readable key calculated from the ARTIST, used for 2621 * searching, sorting and grouping 2622 */ 2623 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2624 public static final String ARTIST_KEY = "artist_key"; 2625 2626 /** 2627 * The number of albums in the database for this artist 2628 */ 2629 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2630 public static final String NUMBER_OF_ALBUMS = "number_of_albums"; 2631 2632 /** 2633 * The number of albums in the database for this artist 2634 */ 2635 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2636 public static final String NUMBER_OF_TRACKS = "number_of_tracks"; 2637 } 2638 2639 /** 2640 * Contains artists for audio files 2641 */ 2642 public static final class Artists implements BaseColumns, ArtistColumns { 2643 /** 2644 * Get the content:// style URI for the artists table on the 2645 * given volume. 2646 * 2647 * @param volumeName the name of the volume to get the URI for 2648 * @return the URI to the audio artists table on the given volume 2649 */ getContentUri(String volumeName)2650 public static Uri getContentUri(String volumeName) { 2651 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2652 .appendPath("artists").build(); 2653 } 2654 2655 /** 2656 * The content:// style URI for the internal storage. 2657 */ 2658 public static final Uri INTERNAL_CONTENT_URI = 2659 getContentUri("internal"); 2660 2661 /** 2662 * The content:// style URI for the "primary" external storage 2663 * volume. 2664 */ 2665 public static final Uri EXTERNAL_CONTENT_URI = 2666 getContentUri("external"); 2667 2668 /** 2669 * The MIME type for this table. 2670 */ 2671 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists"; 2672 2673 /** 2674 * The MIME type for entries in this table. 2675 */ 2676 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist"; 2677 2678 /** 2679 * The default sort order for this table 2680 */ 2681 public static final String DEFAULT_SORT_ORDER = ARTIST_KEY; 2682 2683 /** 2684 * Sub-directory of each artist containing all albums on which 2685 * a song by the artist appears. 2686 */ 2687 public static final class Albums implements AlbumColumns { getContentUri(String volumeName,long artistId)2688 public static final Uri getContentUri(String volumeName,long artistId) { 2689 return ContentUris 2690 .withAppendedId(Audio.Artists.getContentUri(volumeName), artistId) 2691 .buildUpon().appendPath("albums").build(); 2692 } 2693 } 2694 } 2695 2696 /** 2697 * Audio album metadata columns. 2698 */ 2699 public interface AlbumColumns { 2700 2701 /** 2702 * The id for the album 2703 */ 2704 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2705 public static final String ALBUM_ID = "album_id"; 2706 2707 /** 2708 * The album on which the audio file appears, if any 2709 */ 2710 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2711 public static final String ALBUM = "album"; 2712 2713 /** 2714 * The ID of the artist whose songs appear on this album. 2715 */ 2716 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2717 public static final String ARTIST_ID = "artist_id"; 2718 2719 /** 2720 * The name of the artist whose songs appear on this album. 2721 */ 2722 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2723 public static final String ARTIST = "artist"; 2724 2725 /** 2726 * The number of songs on this album 2727 */ 2728 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2729 public static final String NUMBER_OF_SONGS = "numsongs"; 2730 2731 /** 2732 * This column is available when getting album info via artist, 2733 * and indicates the number of songs on the album by the given 2734 * artist. 2735 */ 2736 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2737 public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; 2738 2739 /** 2740 * The year in which the earliest songs 2741 * on this album were released. This will often 2742 * be the same as {@link #LAST_YEAR}, but for compilation albums 2743 * they might differ. 2744 */ 2745 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2746 public static final String FIRST_YEAR = "minyear"; 2747 2748 /** 2749 * The year in which the latest songs 2750 * on this album were released. This will often 2751 * be the same as {@link #FIRST_YEAR}, but for compilation albums 2752 * they might differ. 2753 */ 2754 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 2755 public static final String LAST_YEAR = "maxyear"; 2756 2757 /** 2758 * A non human readable key calculated from the ALBUM, used for 2759 * searching, sorting and grouping 2760 */ 2761 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2762 public static final String ALBUM_KEY = "album_key"; 2763 2764 /** 2765 * Cached album art. 2766 * 2767 * @deprecated Apps may not have filesystem permissions to directly 2768 * access this path. Instead of trying to open this path 2769 * directly, apps should use 2770 * {@link ContentResolver#loadThumbnail} 2771 * to gain access. 2772 */ 2773 @Deprecated 2774 @Column(Cursor.FIELD_TYPE_STRING) 2775 public static final String ALBUM_ART = "album_art"; 2776 } 2777 2778 /** 2779 * Contains artists for audio files 2780 */ 2781 public static final class Albums implements BaseColumns, AlbumColumns { 2782 /** 2783 * Get the content:// style URI for the albums table on the 2784 * given volume. 2785 * 2786 * @param volumeName the name of the volume to get the URI for 2787 * @return the URI to the audio albums table on the given volume 2788 */ getContentUri(String volumeName)2789 public static Uri getContentUri(String volumeName) { 2790 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio") 2791 .appendPath("albums").build(); 2792 } 2793 2794 /** 2795 * The content:// style URI for the internal storage. 2796 */ 2797 public static final Uri INTERNAL_CONTENT_URI = 2798 getContentUri("internal"); 2799 2800 /** 2801 * The content:// style URI for the "primary" external storage 2802 * volume. 2803 */ 2804 public static final Uri EXTERNAL_CONTENT_URI = 2805 getContentUri("external"); 2806 2807 /** 2808 * The MIME type for this table. 2809 */ 2810 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums"; 2811 2812 /** 2813 * The MIME type for entries in this table. 2814 */ 2815 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album"; 2816 2817 /** 2818 * The default sort order for this table 2819 */ 2820 public static final String DEFAULT_SORT_ORDER = ALBUM_KEY; 2821 } 2822 2823 public static final class Radio { 2824 /** 2825 * The MIME type for entries in this table. 2826 */ 2827 public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; 2828 2829 // Not instantiable. Radio()2830 private Radio() { } 2831 } 2832 2833 /** 2834 * This class provides utility methods to obtain thumbnails for various 2835 * {@link Audio} items. 2836 * 2837 * @deprecated Callers should migrate to using 2838 * {@link ContentResolver#loadThumbnail}, since it offers 2839 * richer control over requested thumbnail sizes and 2840 * cancellation behavior. 2841 * @hide 2842 */ 2843 @Deprecated 2844 public static class Thumbnails implements BaseColumns { 2845 /** 2846 * Path to the thumbnail file on disk. 2847 * <p> 2848 * Note that apps may not have filesystem permissions to directly 2849 * access this path. Instead of trying to open this path directly, 2850 * apps should use 2851 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain 2852 * access. 2853 * 2854 * @deprecated Apps may not have filesystem permissions to directly 2855 * access this path. Instead of trying to open this path 2856 * directly, apps should use 2857 * {@link ContentResolver#loadThumbnail} 2858 * to gain access. 2859 */ 2860 @Deprecated 2861 @Column(Cursor.FIELD_TYPE_STRING) 2862 public static final String DATA = "_data"; 2863 2864 @Column(Cursor.FIELD_TYPE_INTEGER) 2865 public static final String ALBUM_ID = "album_id"; 2866 } 2867 } 2868 2869 /** 2870 * Collection of all media with MIME type of {@code video/*}. 2871 */ 2872 public static final class Video { 2873 2874 /** 2875 * The default sort order for this table. 2876 */ 2877 public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME; 2878 2879 /** 2880 * @deprecated all queries should be performed through 2881 * {@link ContentResolver} directly, which offers modern 2882 * features like {@link CancellationSignal}. 2883 */ 2884 @Deprecated query(ContentResolver cr, Uri uri, String[] projection)2885 public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { 2886 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); 2887 } 2888 2889 /** 2890 * Video metadata columns. 2891 */ 2892 public interface VideoColumns extends MediaColumns { 2893 /** @removed promoted to parent interface */ 2894 public static final String DURATION = "duration"; 2895 2896 /** 2897 * The artist who created the video file, if any 2898 */ 2899 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2900 public static final String ARTIST = "artist"; 2901 2902 /** 2903 * The album the video file is from, if any 2904 */ 2905 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2906 public static final String ALBUM = "album"; 2907 2908 /** 2909 * The resolution of the video file, formatted as "XxY" 2910 */ 2911 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2912 public static final String RESOLUTION = "resolution"; 2913 2914 /** 2915 * The description of the video recording 2916 */ 2917 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 2918 public static final String DESCRIPTION = "description"; 2919 2920 /** 2921 * Whether the video should be published as public or private 2922 */ 2923 @Column(Cursor.FIELD_TYPE_INTEGER) 2924 public static final String IS_PRIVATE = "isprivate"; 2925 2926 /** 2927 * The user-added tags associated with a video 2928 */ 2929 @Column(Cursor.FIELD_TYPE_STRING) 2930 public static final String TAGS = "tags"; 2931 2932 /** 2933 * The YouTube category of the video 2934 */ 2935 @Column(Cursor.FIELD_TYPE_STRING) 2936 public static final String CATEGORY = "category"; 2937 2938 /** 2939 * The language of the video 2940 */ 2941 @Column(Cursor.FIELD_TYPE_STRING) 2942 public static final String LANGUAGE = "language"; 2943 2944 /** 2945 * The latitude where the video was captured. 2946 * 2947 * @deprecated location details are no longer indexed for privacy 2948 * reasons, and this value is now always {@code null}. 2949 * You can still manually obtain location metadata using 2950 * {@link ExifInterface#getLatLong(float[])}. 2951 */ 2952 @Deprecated 2953 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 2954 public static final String LATITUDE = "latitude"; 2955 2956 /** 2957 * The longitude where the video was captured. 2958 * 2959 * @deprecated location details are no longer indexed for privacy 2960 * reasons, and this value is now always {@code null}. 2961 * You can still manually obtain location metadata using 2962 * {@link ExifInterface#getLatLong(float[])}. 2963 */ 2964 @Deprecated 2965 @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) 2966 public static final String LONGITUDE = "longitude"; 2967 2968 /** @removed promoted to parent interface */ 2969 public static final String DATE_TAKEN = "datetaken"; 2970 2971 /** 2972 * The mini thumb id. 2973 * 2974 * @deprecated all thumbnails should be obtained via 2975 * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this 2976 * value is no longer supported. 2977 */ 2978 @Deprecated 2979 @Column(Cursor.FIELD_TYPE_INTEGER) 2980 public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; 2981 2982 /** @removed promoted to parent interface */ 2983 public static final String BUCKET_ID = "bucket_id"; 2984 /** @removed promoted to parent interface */ 2985 public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; 2986 /** @removed promoted to parent interface */ 2987 public static final String GROUP_ID = "group_id"; 2988 2989 /** 2990 * The position within the video item at which playback should be 2991 * resumed. 2992 */ 2993 @DurationMillisLong 2994 @Column(Cursor.FIELD_TYPE_INTEGER) 2995 public static final String BOOKMARK = "bookmark"; 2996 2997 /** 2998 * The standard of color aspects 2999 * @hide 3000 */ 3001 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3002 public static final String COLOR_STANDARD = "color_standard"; 3003 3004 /** 3005 * The transfer of color aspects 3006 * @hide 3007 */ 3008 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3009 public static final String COLOR_TRANSFER = "color_transfer"; 3010 3011 /** 3012 * The range of color aspects 3013 * @hide 3014 */ 3015 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3016 public static final String COLOR_RANGE = "color_range"; 3017 } 3018 3019 public static final class Media implements VideoColumns { 3020 /** 3021 * Get the content:// style URI for the video media table on the 3022 * given volume. 3023 * 3024 * @param volumeName the name of the volume to get the URI for 3025 * @return the URI to the video media table on the given volume 3026 */ getContentUri(String volumeName)3027 public static Uri getContentUri(String volumeName) { 3028 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3029 .appendPath("media").build(); 3030 } 3031 3032 /** @hide */ getContentUri(@onNull String volumeName, long id)3033 public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) { 3034 return ContentUris.withAppendedId(getContentUri(volumeName), id); 3035 } 3036 3037 /** 3038 * The content:// style URI for the internal storage. 3039 */ 3040 public static final Uri INTERNAL_CONTENT_URI = 3041 getContentUri("internal"); 3042 3043 /** 3044 * The content:// style URI for the "primary" external storage 3045 * volume. 3046 */ 3047 public static final Uri EXTERNAL_CONTENT_URI = 3048 getContentUri("external"); 3049 3050 /** 3051 * The MIME type for this table. 3052 */ 3053 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video"; 3054 3055 /** 3056 * The default sort order for this table 3057 */ 3058 public static final String DEFAULT_SORT_ORDER = TITLE; 3059 } 3060 3061 /** 3062 * This class provides utility methods to obtain thumbnails for various 3063 * {@link Video} items. 3064 * 3065 * @deprecated Callers should migrate to using 3066 * {@link ContentResolver#loadThumbnail}, since it offers 3067 * richer control over requested thumbnail sizes and 3068 * cancellation behavior. 3069 */ 3070 @Deprecated 3071 public static class Thumbnails implements BaseColumns { 3072 /** 3073 * Cancel any outstanding {@link #getThumbnail} requests, causing 3074 * them to return by throwing a {@link OperationCanceledException}. 3075 * <p> 3076 * This method has no effect on 3077 * {@link ContentResolver#loadThumbnail} calls, since they provide 3078 * their own {@link CancellationSignal}. 3079 * 3080 * @deprecated Callers should migrate to using 3081 * {@link ContentResolver#loadThumbnail}, since it 3082 * offers richer control over requested thumbnail sizes 3083 * and cancellation behavior. 3084 */ 3085 @Deprecated cancelThumbnailRequest(ContentResolver cr, long origId)3086 public static void cancelThumbnailRequest(ContentResolver cr, long origId) { 3087 final Uri uri = ContentUris.withAppendedId( 3088 Video.Media.EXTERNAL_CONTENT_URI, origId); 3089 InternalThumbnails.cancelThumbnail(cr, uri); 3090 } 3091 3092 /** 3093 * Return thumbnail representing a specific video item. If a 3094 * thumbnail doesn't exist, this method will block until it's 3095 * generated. Callers are responsible for their own in-memory 3096 * caching of returned values. 3097 * 3098 * @param videoId the video item to obtain a thumbnail for. 3099 * @param kind optimal thumbnail size desired. 3100 * @return decoded thumbnail, or {@code null} if problem was 3101 * encountered. 3102 * @deprecated Callers should migrate to using 3103 * {@link ContentResolver#loadThumbnail}, since it 3104 * offers richer control over requested thumbnail sizes 3105 * and cancellation behavior. 3106 */ 3107 @Deprecated getThumbnail(ContentResolver cr, long videoId, int kind, BitmapFactory.Options options)3108 public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind, 3109 BitmapFactory.Options options) { 3110 final Uri uri = ContentUris.withAppendedId( 3111 Video.Media.EXTERNAL_CONTENT_URI, videoId); 3112 return InternalThumbnails.getThumbnail(cr, uri, kind, options); 3113 } 3114 3115 /** 3116 * Cancel any outstanding {@link #getThumbnail} requests, causing 3117 * them to return by throwing a {@link OperationCanceledException}. 3118 * <p> 3119 * This method has no effect on 3120 * {@link ContentResolver#loadThumbnail} calls, since they provide 3121 * their own {@link CancellationSignal}. 3122 * 3123 * @deprecated Callers should migrate to using 3124 * {@link ContentResolver#loadThumbnail}, since it 3125 * offers richer control over requested thumbnail sizes 3126 * and cancellation behavior. 3127 */ 3128 @Deprecated cancelThumbnailRequest(ContentResolver cr, long videoId, long groupId)3129 public static void cancelThumbnailRequest(ContentResolver cr, long videoId, 3130 long groupId) { 3131 cancelThumbnailRequest(cr, videoId); 3132 } 3133 3134 /** 3135 * Return thumbnail representing a specific video item. If a 3136 * thumbnail doesn't exist, this method will block until it's 3137 * generated. Callers are responsible for their own in-memory 3138 * caching of returned values. 3139 * 3140 * @param videoId the video item to obtain a thumbnail for. 3141 * @param kind optimal thumbnail size desired. 3142 * @return decoded thumbnail, or {@code null} if problem was 3143 * encountered. 3144 * @deprecated Callers should migrate to using 3145 * {@link ContentResolver#loadThumbnail}, since it 3146 * offers richer control over requested thumbnail sizes 3147 * and cancellation behavior. 3148 */ 3149 @Deprecated getThumbnail(ContentResolver cr, long videoId, long groupId, int kind, BitmapFactory.Options options)3150 public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId, 3151 int kind, BitmapFactory.Options options) { 3152 return getThumbnail(cr, videoId, kind, options); 3153 } 3154 3155 /** 3156 * Get the content:// style URI for the image media table on the 3157 * given volume. 3158 * 3159 * @param volumeName the name of the volume to get the URI for 3160 * @return the URI to the image media table on the given volume 3161 */ getContentUri(String volumeName)3162 public static Uri getContentUri(String volumeName) { 3163 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video") 3164 .appendPath("thumbnails").build(); 3165 } 3166 3167 /** 3168 * The content:// style URI for the internal storage. 3169 */ 3170 public static final Uri INTERNAL_CONTENT_URI = 3171 getContentUri("internal"); 3172 3173 /** 3174 * The content:// style URI for the "primary" external storage 3175 * volume. 3176 */ 3177 public static final Uri EXTERNAL_CONTENT_URI = 3178 getContentUri("external"); 3179 3180 /** 3181 * The default sort order for this table 3182 */ 3183 public static final String DEFAULT_SORT_ORDER = "video_id ASC"; 3184 3185 /** 3186 * Path to the thumbnail file on disk. 3187 * 3188 * @deprecated Apps may not have filesystem permissions to directly 3189 * access this path. Instead of trying to open this path 3190 * directly, apps should use 3191 * {@link ContentResolver#openFileDescriptor(Uri, String)} 3192 * to gain access. 3193 */ 3194 @Deprecated 3195 @Column(Cursor.FIELD_TYPE_STRING) 3196 public static final String DATA = "_data"; 3197 3198 /** 3199 * The original image for the thumbnal 3200 */ 3201 @Column(Cursor.FIELD_TYPE_INTEGER) 3202 public static final String VIDEO_ID = "video_id"; 3203 3204 /** 3205 * The kind of the thumbnail 3206 */ 3207 @Column(Cursor.FIELD_TYPE_INTEGER) 3208 public static final String KIND = "kind"; 3209 3210 public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; 3211 public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; 3212 public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; 3213 3214 /** 3215 * The width of the thumbnal 3216 */ 3217 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3218 public static final String WIDTH = "width"; 3219 3220 /** 3221 * The height of the thumbnail 3222 */ 3223 @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) 3224 public static final String HEIGHT = "height"; 3225 } 3226 } 3227 3228 /** @removed */ 3229 @Deprecated getAllVolumeNames(@onNull Context context)3230 public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) { 3231 return getExternalVolumeNames(context); 3232 } 3233 3234 /** 3235 * Return list of all specific volume names that make up 3236 * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each 3237 * shared storage device that is currently attached, which typically 3238 * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}. 3239 * <p> 3240 * Each specific volume name can be passed to APIs like 3241 * {@link MediaStore.Images.Media#getContentUri(String)} to interact with 3242 * media on that storage device. 3243 */ getExternalVolumeNames(@onNull Context context)3244 public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) { 3245 final StorageManager sm = context.getSystemService(StorageManager.class); 3246 final Set<String> volumeNames = new ArraySet<>(); 3247 for (VolumeInfo vi : sm.getVolumes()) { 3248 if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) { 3249 if (vi.isPrimary()) { 3250 volumeNames.add(VOLUME_EXTERNAL_PRIMARY); 3251 } else { 3252 volumeNames.add(vi.getNormalizedFsUuid()); 3253 } 3254 } 3255 } 3256 return volumeNames; 3257 } 3258 3259 /** 3260 * Return the volume name that the given {@link Uri} references. 3261 */ getVolumeName(@onNull Uri uri)3262 public static @NonNull String getVolumeName(@NonNull Uri uri) { 3263 final List<String> segments = uri.getPathSegments(); 3264 if (uri.getAuthority().equals(AUTHORITY) && segments != null && segments.size() > 0) { 3265 return segments.get(0); 3266 } else { 3267 throw new IllegalArgumentException("Missing volume name: " + uri); 3268 } 3269 } 3270 3271 /** {@hide} */ checkArgumentVolumeName(@onNull String volumeName)3272 public static @NonNull String checkArgumentVolumeName(@NonNull String volumeName) { 3273 if (TextUtils.isEmpty(volumeName)) { 3274 throw new IllegalArgumentException(); 3275 } 3276 3277 if (VOLUME_INTERNAL.equals(volumeName)) { 3278 return volumeName; 3279 } else if (VOLUME_EXTERNAL.equals(volumeName)) { 3280 return volumeName; 3281 } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) { 3282 return volumeName; 3283 } 3284 3285 // When not one of the well-known values above, it must be a hex UUID 3286 for (int i = 0; i < volumeName.length(); i++) { 3287 final char c = volumeName.charAt(i); 3288 if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) { 3289 continue; 3290 } else { 3291 throw new IllegalArgumentException("Invalid volume name: " + volumeName); 3292 } 3293 } 3294 return volumeName; 3295 } 3296 3297 /** 3298 * Return path where the given specific volume is mounted. Not valid for 3299 * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are 3300 * broad collections that cover many paths. 3301 * 3302 * @hide 3303 */ 3304 @TestApi getVolumePath(@onNull String volumeName)3305 public static @NonNull File getVolumePath(@NonNull String volumeName) 3306 throws FileNotFoundException { 3307 final StorageManager sm = AppGlobals.getInitialApplication() 3308 .getSystemService(StorageManager.class); 3309 return getVolumePath(sm.getVolumes(), volumeName); 3310 } 3311 3312 /** {@hide} */ getVolumePath(@onNull List<VolumeInfo> volumes, @NonNull String volumeName)3313 public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes, 3314 @NonNull String volumeName) throws FileNotFoundException { 3315 if (TextUtils.isEmpty(volumeName)) { 3316 throw new IllegalArgumentException(); 3317 } 3318 3319 switch (volumeName) { 3320 case VOLUME_INTERNAL: 3321 case VOLUME_EXTERNAL: 3322 throw new FileNotFoundException(volumeName + " has no associated path"); 3323 } 3324 3325 final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName); 3326 for (VolumeInfo volume : volumes) { 3327 final boolean matchPrimary = wantPrimary 3328 && volume.isPrimary(); 3329 final boolean matchSecondary = !wantPrimary 3330 && Objects.equals(volume.getNormalizedFsUuid(), volumeName); 3331 if (matchPrimary || matchSecondary) { 3332 final File path = volume.getPathForUser(UserHandle.myUserId()); 3333 if (path != null) { 3334 return path; 3335 } 3336 } 3337 } 3338 throw new FileNotFoundException("Failed to find path for " + volumeName); 3339 } 3340 3341 /** 3342 * Return paths that should be scanned for the given volume. 3343 * 3344 * @hide 3345 */ 3346 @TestApi getVolumeScanPaths(@onNull String volumeName)3347 public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName) 3348 throws FileNotFoundException { 3349 if (TextUtils.isEmpty(volumeName)) { 3350 throw new IllegalArgumentException(); 3351 } 3352 3353 final Context context = AppGlobals.getInitialApplication(); 3354 final UserManager um = context.getSystemService(UserManager.class); 3355 3356 final ArrayList<File> res = new ArrayList<>(); 3357 if (VOLUME_INTERNAL.equals(volumeName)) { 3358 addCanonicalFile(res, new File(Environment.getRootDirectory(), "media")); 3359 addCanonicalFile(res, new File(Environment.getOemDirectory(), "media")); 3360 addCanonicalFile(res, new File(Environment.getProductDirectory(), "media")); 3361 } else if (VOLUME_EXTERNAL.equals(volumeName)) { 3362 for (String exactVolume : getExternalVolumeNames(context)) { 3363 addCanonicalFile(res, getVolumePath(exactVolume)); 3364 } 3365 if (um.isDemoUser()) { 3366 addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory()); 3367 } 3368 } else { 3369 addCanonicalFile(res, getVolumePath(volumeName)); 3370 if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) { 3371 addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory()); 3372 } 3373 } 3374 return res; 3375 } 3376 addCanonicalFile(List<File> list, File file)3377 private static void addCanonicalFile(List<File> list, File file) { 3378 try { 3379 list.add(file.getCanonicalFile()); 3380 } catch (IOException e) { 3381 Log.w(TAG, "Failed to resolve " + file + ": " + e); 3382 list.add(file); 3383 } 3384 } 3385 3386 /** 3387 * Uri for querying the state of the media scanner. 3388 */ getMediaScannerUri()3389 public static Uri getMediaScannerUri() { 3390 return AUTHORITY_URI.buildUpon().appendPath("none").appendPath("media_scanner").build(); 3391 } 3392 3393 /** 3394 * Name of current volume being scanned by the media scanner. 3395 */ 3396 public static final String MEDIA_SCANNER_VOLUME = "volume"; 3397 3398 /** 3399 * Name of the file signaling the media scanner to ignore media in the containing directory 3400 * and its subdirectories. Developers should use this to avoid application graphics showing 3401 * up in the Gallery and likewise prevent application sounds and music from showing up in 3402 * the Music app. 3403 */ 3404 public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; 3405 3406 /** 3407 * Return an opaque version string describing the {@link MediaStore} state. 3408 * <p> 3409 * Applications that import data from {@link MediaStore} into their own 3410 * caches can use this to detect that {@link MediaStore} has undergone 3411 * substantial changes, and that data should be rescanned. 3412 * <p> 3413 * No other assumptions should be made about the meaning of the version. 3414 * <p> 3415 * This method returns the version for 3416 * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a 3417 * different volume, use {@link #getVersion(Context, String)}. 3418 */ getVersion(@onNull Context context)3419 public static @NonNull String getVersion(@NonNull Context context) { 3420 return getVersion(context, VOLUME_EXTERNAL_PRIMARY); 3421 } 3422 3423 /** 3424 * Return an opaque version string describing the {@link MediaStore} state. 3425 * <p> 3426 * Applications that import data from {@link MediaStore} into their own 3427 * caches can use this to detect that {@link MediaStore} has undergone 3428 * substantial changes, and that data should be rescanned. 3429 * <p> 3430 * No other assumptions should be made about the meaning of the version. 3431 * 3432 * @param volumeName specific volume to obtain an opaque version string for. 3433 * Must be one of the values returned from 3434 * {@link #getExternalVolumeNames(Context)}. 3435 */ getVersion(@onNull Context context, @NonNull String volumeName)3436 public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) { 3437 final ContentResolver resolver = context.getContentResolver(); 3438 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3439 final Bundle in = new Bundle(); 3440 in.putString(Intent.EXTRA_TEXT, volumeName); 3441 final Bundle out = client.call(GET_VERSION_CALL, null, in); 3442 return out.getString(Intent.EXTRA_TEXT); 3443 } catch (RemoteException e) { 3444 throw e.rethrowAsRuntimeException(); 3445 } 3446 } 3447 3448 /** 3449 * Return a {@link DocumentsProvider} Uri that is an equivalent to the given 3450 * {@link MediaStore} Uri. 3451 * <p> 3452 * This allows apps with Storage Access Framework permissions to convert 3453 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 3454 * to the same underlying item. Note that this method doesn't grant any new 3455 * permissions; callers must already hold permissions obtained with 3456 * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs. 3457 * 3458 * @param mediaUri The {@link MediaStore} Uri to convert. 3459 * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null} 3460 * if no equivalent was found. 3461 * @see #getMediaUri(Context, Uri) 3462 */ getDocumentUri(@onNull Context context, @NonNull Uri mediaUri)3463 public static @Nullable Uri getDocumentUri(@NonNull Context context, @NonNull Uri mediaUri) { 3464 final ContentResolver resolver = context.getContentResolver(); 3465 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 3466 3467 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3468 final Bundle in = new Bundle(); 3469 in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri); 3470 in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions); 3471 final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in); 3472 return out.getParcelable(DocumentsContract.EXTRA_URI); 3473 } catch (RemoteException e) { 3474 throw e.rethrowAsRuntimeException(); 3475 } 3476 } 3477 3478 /** 3479 * Return a {@link MediaStore} Uri that is an equivalent to the given 3480 * {@link DocumentsProvider} Uri. 3481 * <p> 3482 * This allows apps with Storage Access Framework permissions to convert 3483 * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer 3484 * to the same underlying item. Note that this method doesn't grant any new 3485 * permissions; callers must already hold permissions obtained with 3486 * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs. 3487 * 3488 * @param documentUri The {@link DocumentsProvider} Uri to convert. 3489 * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no 3490 * equivalent was found. 3491 * @see #getDocumentUri(Context, Uri) 3492 */ getMediaUri(@onNull Context context, @NonNull Uri documentUri)3493 public static @Nullable Uri getMediaUri(@NonNull Context context, @NonNull Uri documentUri) { 3494 final ContentResolver resolver = context.getContentResolver(); 3495 final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions(); 3496 3497 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3498 final Bundle in = new Bundle(); 3499 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 3500 in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions); 3501 final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in); 3502 return out.getParcelable(DocumentsContract.EXTRA_URI); 3503 } catch (RemoteException e) { 3504 throw e.rethrowAsRuntimeException(); 3505 } 3506 } 3507 3508 /** 3509 * Calculate size of media contributed by given package under the calling 3510 * user. The meaning of "contributed" means it won't automatically be 3511 * deleted when the app is uninstalled. 3512 * 3513 * @hide 3514 */ 3515 @TestApi 3516 @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) getContributedMediaSize(Context context, String packageName, UserHandle user)3517 public static @BytesLong long getContributedMediaSize(Context context, String packageName, 3518 UserHandle user) throws IOException { 3519 final UserManager um = context.getSystemService(UserManager.class); 3520 if (um.isUserUnlocked(user) && um.isUserRunning(user)) { 3521 try { 3522 final ContentResolver resolver = context 3523 .createPackageContextAsUser(packageName, 0, user).getContentResolver(); 3524 final Bundle in = new Bundle(); 3525 in.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 3526 final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in); 3527 return out.getLong(Intent.EXTRA_INDEX); 3528 } catch (Exception e) { 3529 throw new IOException(e); 3530 } 3531 } else { 3532 throw new IOException("User " + user + " must be unlocked and running"); 3533 } 3534 } 3535 3536 /** 3537 * Delete all media contributed by given package under the calling user. The 3538 * meaning of "contributed" means it won't automatically be deleted when the 3539 * app is uninstalled. 3540 * 3541 * @hide 3542 */ 3543 @TestApi 3544 @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) deleteContributedMedia(Context context, String packageName, UserHandle user)3545 public static void deleteContributedMedia(Context context, String packageName, 3546 UserHandle user) throws IOException { 3547 final UserManager um = context.getSystemService(UserManager.class); 3548 if (um.isUserUnlocked(user) && um.isUserRunning(user)) { 3549 try { 3550 final ContentResolver resolver = context 3551 .createPackageContextAsUser(packageName, 0, user).getContentResolver(); 3552 final Bundle in = new Bundle(); 3553 in.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 3554 resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in); 3555 } catch (Exception e) { 3556 throw new IOException(e); 3557 } 3558 } else { 3559 throw new IOException("User " + user + " must be unlocked and running"); 3560 } 3561 } 3562 3563 /** @hide */ 3564 @TestApi scanFile(Context context, File file)3565 public static Uri scanFile(Context context, File file) { 3566 return scan(context, SCAN_FILE_CALL, file, false); 3567 } 3568 3569 /** @hide */ 3570 @TestApi scanFileFromShell(Context context, File file)3571 public static Uri scanFileFromShell(Context context, File file) { 3572 return scan(context, SCAN_FILE_CALL, file, true); 3573 } 3574 3575 /** @hide */ 3576 @TestApi scanVolume(Context context, File file)3577 public static void scanVolume(Context context, File file) { 3578 scan(context, SCAN_VOLUME_CALL, file, false); 3579 } 3580 3581 /** @hide */ scan(Context context, String method, File file, boolean originatedFromShell)3582 private static Uri scan(Context context, String method, File file, 3583 boolean originatedFromShell) { 3584 final ContentResolver resolver = context.getContentResolver(); 3585 try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { 3586 final Bundle in = new Bundle(); 3587 in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); 3588 in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell); 3589 final Bundle out = client.call(method, null, in); 3590 return out.getParcelable(Intent.EXTRA_STREAM); 3591 } catch (RemoteException e) { 3592 throw e.rethrowAsRuntimeException(); 3593 } 3594 } 3595 } 3596