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