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