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