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