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