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