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