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