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