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.IntRange;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SdkConstant;
28 import android.annotation.SdkConstant.SdkConstantType;
29 import android.annotation.TestApi;
30 import android.app.Activity;
31 import android.app.AppGlobals;
32 import android.compat.annotation.UnsupportedAppUsage;
33 import android.content.ClipData;
34 import android.content.ContentProviderClient;
35 import android.content.ContentResolver;
36 import android.content.ContentUris;
37 import android.content.ContentValues;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.UriPermission;
41 import android.database.Cursor;
42 import android.database.DatabaseUtils;
43 import android.graphics.Bitmap;
44 import android.graphics.BitmapFactory;
45 import android.graphics.ImageDecoder;
46 import android.graphics.Point;
47 import android.graphics.PostProcessor;
48 import android.media.ExifInterface;
49 import android.media.MediaFile;
50 import android.net.Uri;
51 import android.os.Bundle;
52 import android.os.CancellationSignal;
53 import android.os.Environment;
54 import android.os.FileUtils;
55 import android.os.OperationCanceledException;
56 import android.os.ParcelFileDescriptor;
57 import android.os.RemoteException;
58 import android.os.UserHandle;
59 import android.os.UserManager;
60 import android.os.storage.StorageManager;
61 import android.os.storage.StorageVolume;
62 import android.os.storage.VolumeInfo;
63 import android.service.media.CameraPrewarmService;
64 import android.text.TextUtils;
65 import android.text.format.DateUtils;
66 import android.util.ArrayMap;
67 import android.util.ArraySet;
68 import android.util.Log;
69 
70 import com.android.internal.annotations.GuardedBy;
71 
72 import java.io.File;
73 import java.io.FileInputStream;
74 import java.io.FileNotFoundException;
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.io.OutputStream;
78 import java.util.ArrayList;
79 import java.util.Collection;
80 import java.util.List;
81 import java.util.Objects;
82 import java.util.Set;
83 import java.util.regex.Pattern;
84 
85 /**
86  * The contract between the media provider and applications. Contains
87  * definitions for the supported URIs and columns.
88  * <p>
89  * The media provider provides an indexed collection of common media types, such
90  * as {@link Audio}, {@link Video}, and {@link Images}, from any attached
91  * storage devices. Each collection is organized based on the primary MIME type
92  * of the underlying content; for example, {@code image/*} content is indexed
93  * under {@link Images}. The {@link Files} collection provides a broad view
94  * across all collections, and does not filter by MIME type.
95  */
96 public final class MediaStore {
97     private final static String TAG = "MediaStore";
98 
99     /** The authority for the media provider */
100     public static final String AUTHORITY = "media";
101     /** A content:// style uri to the authority for the media provider */
102     public static final @NonNull Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
103 
104     /**
105      * Synthetic volume name that provides a view of all content across the
106      * "internal" storage of the device.
107      * <p>
108      * This synthetic volume provides a merged view of all media distributed
109      * with the device, such as built-in ringtones and wallpapers.
110      * <p>
111      * Because this is a synthetic volume, you can't insert new content into
112      * this volume.
113      */
114     public static final String VOLUME_INTERNAL = "internal";
115 
116     /**
117      * Synthetic volume name that provides a view of all content across the
118      * "external" storage of the device.
119      * <p>
120      * This synthetic volume provides a merged view of all media across all
121      * currently attached external storage devices.
122      * <p>
123      * Because this is a synthetic volume, you can't insert new content into
124      * this volume. Instead, you can insert content into a specific storage
125      * volume obtained from {@link #getExternalVolumeNames(Context)}.
126      */
127     public static final String VOLUME_EXTERNAL = "external";
128 
129     /**
130      * Specific volume name that represents the primary external storage device
131      * at {@link Environment#getExternalStorageDirectory()}.
132      * <p>
133      * This volume may not always be available, such as when the user has
134      * ejected the device. You can find a list of all specific volume names
135      * using {@link #getExternalVolumeNames(Context)}.
136      */
137     public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary";
138 
139     /** {@hide} */
140     public static final String WAIT_FOR_IDLE_CALL = "wait_for_idle";
141     /** {@hide} */
142     public static final String SCAN_FILE_CALL = "scan_file";
143     /** {@hide} */
144     public static final String SCAN_VOLUME_CALL = "scan_volume";
145 
146     /**
147      * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
148      * the file path originated from shell.
149      *
150      * {@hide}
151      */
152     public static final String EXTRA_ORIGINATED_FROM_SHELL =
153             "android.intent.extra.originated_from_shell";
154 
155     /**
156      * The method name used by the media scanner and mtp to tell the media provider to
157      * rescan and reclassify that have become unhidden because of renaming folders or
158      * removing nomedia files
159      * @hide
160      */
161     @Deprecated
162     public static final String UNHIDE_CALL = "unhide";
163 
164     /**
165      * The method name used by the media scanner service to reload all localized ringtone titles due
166      * to a locale change.
167      * @hide
168      */
169     public static final String RETRANSLATE_CALL = "update_titles";
170 
171     /** {@hide} */
172     public static final String GET_VERSION_CALL = "get_version";
173     /** {@hide} */
174     public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
175     /** {@hide} */
176     public static final String GET_MEDIA_URI_CALL = "get_media_uri";
177 
178     /** {@hide} */
179     public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
180     /** {@hide} */
181     public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
182 
183     /**
184      * This is for internal use by the media scanner only.
185      * Name of the (optional) Uri parameter that determines whether to skip deleting
186      * the file pointed to by the _data column, when deleting the database entry.
187      * The only appropriate value for this parameter is "false", in which case the
188      * delete will be skipped. Note especially that setting this to true, or omitting
189      * the parameter altogether, will perform the default action, which is different
190      * for different types of media.
191      * @hide
192      */
193     public static final String PARAM_DELETE_DATA = "deletedata";
194 
195     /** {@hide} */
196     public static final String PARAM_INCLUDE_PENDING = "includePending";
197     /** {@hide} */
198     public static final String PARAM_INCLUDE_TRASHED = "includeTrashed";
199     /** {@hide} */
200     public static final String PARAM_PROGRESS = "progress";
201     /** {@hide} */
202     public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal";
203     /** {@hide} */
204     public static final String PARAM_LIMIT = "limit";
205 
206     /**
207      * Activity Action: Launch a music player.
208      * The activity should be able to play, browse, or manipulate music files stored on the device.
209      *
210      * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead.
211      */
212     @Deprecated
213     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
214     public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
215 
216     /**
217      * Activity Action: Perform a search for media.
218      * Contains at least the {@link android.app.SearchManager#QUERY} extra.
219      * May also contain any combination of the following extras:
220      * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
221      *
222      * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
223      * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
224      * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
225      * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
226      */
227     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
228     public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
229 
230     /**
231      * An intent to perform a search for music media and automatically play content from the
232      * result when possible. This can be fired, for example, by the result of a voice recognition
233      * command to listen to music.
234      * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}
235      * and {@link android.app.SearchManager#QUERY} extras. The
236      * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and
237      * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode.
238      * For more information about the search modes for this intent, see
239      * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based
240      * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common
241      * Intents</a>.</p>
242      *
243      * <p>This intent makes the most sense for apps that can support large-scale search of music,
244      * such as services connected to an online database of music which can be streamed and played
245      * on the device.</p>
246      */
247     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
248     public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
249             "android.media.action.MEDIA_PLAY_FROM_SEARCH";
250 
251     /**
252      * An intent to perform a search for readable media and automatically play content from the
253      * result when possible. This can be fired, for example, by the result of a voice recognition
254      * command to read a book or magazine.
255      * <p>
256      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
257      * contain any type of unstructured text search, like the name of a book or magazine, an author
258      * a genre, a publisher, or any combination of these.
259      * <p>
260      * Because this intent includes an open-ended unstructured search string, it makes the most
261      * sense for apps that can support large-scale search of text media, such as services connected
262      * to an online database of books and/or magazines which can be read on the device.
263      */
264     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
265     public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH =
266             "android.media.action.TEXT_OPEN_FROM_SEARCH";
267 
268     /**
269      * An intent to perform a search for video media and automatically play content from the
270      * result when possible. This can be fired, for example, by the result of a voice recognition
271      * command to play movies.
272      * <p>
273      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
274      * contain any type of unstructured video search, like the name of a movie, one or more actors,
275      * a genre, or any combination of these.
276      * <p>
277      * Because this intent includes an open-ended unstructured search string, it makes the most
278      * sense for apps that can support large-scale search of video, such as services connected to an
279      * online database of videos which can be streamed and played on the device.
280      */
281     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
282     public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH =
283             "android.media.action.VIDEO_PLAY_FROM_SEARCH";
284 
285     /**
286      * The name of the Intent-extra used to define the artist
287      */
288     public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
289     /**
290      * The name of the Intent-extra used to define the album
291      */
292     public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
293     /**
294      * The name of the Intent-extra used to define the song title
295      */
296     public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
297     /**
298      * The name of the Intent-extra used to define the genre.
299      */
300     public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
301     /**
302      * The name of the Intent-extra used to define the playlist.
303      */
304     public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
305     /**
306      * The name of the Intent-extra used to define the radio channel.
307      */
308     public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
309     /**
310      * The name of the Intent-extra used to define the search focus. The search focus
311      * indicates whether the search should be for things related to the artist, album
312      * or song that is identified by the other extras.
313      */
314     public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
315 
316     /**
317      * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView.
318      * This is an int property that overrides the activity's requestedOrientation.
319      * @see android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED
320      */
321     public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
322 
323     /**
324      * The name of an Intent-extra used to control the UI of a ViewImage.
325      * This is a boolean property that overrides the activity's default fullscreen state.
326      */
327     public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
328 
329     /**
330      * The name of an Intent-extra used to control the UI of a ViewImage.
331      * This is a boolean property that specifies whether or not to show action icons.
332      */
333     public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
334 
335     /**
336      * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
337      * This is a boolean property that specifies whether or not to finish the MovieView activity
338      * when the movie completes playing. The default value is true, which means to automatically
339      * exit the movie player activity when the movie completes playing.
340      */
341     public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
342 
343     /**
344      * The name of the Intent action used to launch a camera in still image mode.
345      */
346     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
347     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
348 
349     /**
350      * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
351      * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm
352      * service.
353      * <p>
354      * This meta-data should reference the fully qualified class name of the prewarm service
355      * extending {@link CameraPrewarmService}.
356      * <p>
357      * The prewarm service will get bound and receive a prewarm signal
358      * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
359      * An application implementing a prewarm service should do the absolute minimum amount of work
360      * to initialize the camera in order to reduce startup time in likely case that shortly after a
361      * camera launch intent would be sent.
362      */
363     public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE =
364             "android.media.still_image_camera_preview_service";
365 
366     /**
367      * The name of the Intent action used to launch a camera in still image mode
368      * for use when the device is secured (e.g. with a pin, password, pattern,
369      * or face unlock). Applications responding to this intent must not expose
370      * any personal content like existing photos or videos on the device. The
371      * applications should be careful not to share any photo or video with other
372      * applications or internet. The activity should use {@link
373      * Activity#setShowWhenLocked} to display
374      * on top of the lock screen while secured. There is no activity stack when
375      * this flag is used, so launching more than one activity is strongly
376      * discouraged.
377      */
378     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
379     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
380             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
381 
382     /**
383      * The name of the Intent action used to launch a camera in video mode.
384      */
385     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
386     public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
387 
388     /**
389      * Standard Intent action that can be sent to have the camera application
390      * capture an image and return it.
391      * <p>
392      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
393      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
394      * object in the extra field. This is useful for applications that only need a small image.
395      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
396      * value of EXTRA_OUTPUT.
397      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
398      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
399      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
400      * If you don't set a ClipData, it will be copied there for you when calling
401      * {@link Context#startActivity(Intent)}.
402      *
403      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
404      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
405      * is not granted, then attempting to use this action will result in a {@link
406      * java.lang.SecurityException}.
407      *
408      *  @see #EXTRA_OUTPUT
409      */
410     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
411     public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
412 
413     /**
414      * Intent action that can be sent to have the camera application capture an image and return
415      * it when the device is secured (e.g. with a pin, password, pattern, or face unlock).
416      * Applications responding to this intent must not expose any personal content like existing
417      * photos or videos on the device. The applications should be careful not to share any photo
418      * or video with other applications or Internet. The activity should use {@link
419      * Activity#setShowWhenLocked} to display on top of the
420      * lock screen while secured. There is no activity stack when this flag is used, so
421      * launching more than one activity is strongly discouraged.
422      * <p>
423      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
424      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
425      * object in the extra field. This is useful for applications that only need a small image.
426      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
427      * value of EXTRA_OUTPUT.
428      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
429      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
430      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
431      * If you don't set a ClipData, it will be copied there for you when calling
432      * {@link Context#startActivity(Intent)}.
433      *
434      * @see #ACTION_IMAGE_CAPTURE
435      * @see #EXTRA_OUTPUT
436      */
437     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
438     public static final String ACTION_IMAGE_CAPTURE_SECURE =
439             "android.media.action.IMAGE_CAPTURE_SECURE";
440 
441     /**
442      * Standard Intent action that can be sent to have the camera application
443      * capture a video and return it.
444      * <p>
445      * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
446      * <p>
447      * The caller may pass in an extra EXTRA_OUTPUT to control
448      * where the video is written. If EXTRA_OUTPUT is not present the video will be
449      * written to the standard location for videos, and the Uri of that location will be
450      * returned in the data field of the Uri.
451      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
452      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
453      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
454      * If you don't set a ClipData, it will be copied there for you when calling
455      * {@link Context#startActivity(Intent)}.
456      *
457      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
458      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
459      * is not granted, then atempting to use this action will result in a {@link
460      * java.lang.SecurityException}.
461      *
462      * @see #EXTRA_OUTPUT
463      * @see #EXTRA_VIDEO_QUALITY
464      * @see #EXTRA_SIZE_LIMIT
465      * @see #EXTRA_DURATION_LIMIT
466      */
467     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
468     public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
469 
470     /**
471      * Standard action that can be sent to review the given media file.
472      * <p>
473      * The launched application is expected to provide a large-scale view of the
474      * given media file, while allowing the user to quickly access other
475      * recently captured media files.
476      * <p>
477      * Input: {@link Intent#getData} is URI of the primary media item to
478      * initially display.
479      *
480      * @see #ACTION_REVIEW_SECURE
481      * @see #EXTRA_BRIGHTNESS
482      */
483     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
484     public final static String ACTION_REVIEW = "android.provider.action.REVIEW";
485 
486     /**
487      * Standard action that can be sent to review the given media file when the
488      * device is secured (e.g. with a pin, password, pattern, or face unlock).
489      * The applications should be careful not to share any media with other
490      * applications or Internet. The activity should use
491      * {@link Activity#setShowWhenLocked} to display on top of the lock screen
492      * while secured. There is no activity stack when this flag is used, so
493      * launching more than one activity is strongly discouraged.
494      * <p>
495      * The launched application is expected to provide a large-scale view of the
496      * given primary media file, while only allowing the user to quickly access
497      * other media from an explicit secondary list.
498      * <p>
499      * Input: {@link Intent#getData} is URI of the primary media item to
500      * initially display. {@link Intent#getClipData} is the limited list of
501      * secondary media items that the user is allowed to review. If
502      * {@link Intent#getClipData} is undefined, then no other media access
503      * should be allowed.
504      *
505      * @see #EXTRA_BRIGHTNESS
506      */
507     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
508     public final static String ACTION_REVIEW_SECURE = "android.provider.action.REVIEW_SECURE";
509 
510     /**
511      * When defined, the launched application is requested to set the given
512      * brightness value via
513      * {@link android.view.WindowManager.LayoutParams#screenBrightness} to help
514      * ensure a smooth transition when launching {@link #ACTION_REVIEW} or
515      * {@link #ACTION_REVIEW_SECURE} intents.
516      */
517     public final static String EXTRA_BRIGHTNESS = "android.provider.extra.BRIGHTNESS";
518 
519     /**
520      * The name of the Intent-extra used to control the quality of a recorded video. This is an
521      * integer property. Currently value 0 means low quality, suitable for MMS messages, and
522      * value 1 means high quality. In the future other quality levels may be added.
523      */
524     public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
525 
526     /**
527      * Specify the maximum allowed size.
528      */
529     public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
530 
531     /**
532      * Specify the maximum allowed recording duration in seconds.
533      */
534     public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
535 
536     /**
537      * The name of the Intent-extra used to indicate a content resolver Uri to be used to
538      * store the requested image or video.
539      */
540     public final static String EXTRA_OUTPUT = "output";
541 
542     /**
543       * The string that is used when a media attribute is not known. For example,
544       * if an audio file does not have any meta data, the artist and album columns
545       * will be set to this value.
546       */
547     public static final String UNKNOWN_STRING = "<unknown>";
548 
549     /**
550      * Update the given {@link Uri} to also include any pending media items from
551      * calls such as
552      * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
553      * By default no pending items are returned.
554      *
555      * @see MediaColumns#IS_PENDING
556      * @see MediaStore#setIncludePending(Uri)
557      */
setIncludePending(@onNull Uri uri)558     public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
559         return setIncludePending(uri.buildUpon()).build();
560     }
561 
562     /** @hide */
setIncludePending(@onNull Uri.Builder uriBuilder)563     public static @NonNull Uri.Builder setIncludePending(@NonNull Uri.Builder uriBuilder) {
564         return uriBuilder.appendQueryParameter(PARAM_INCLUDE_PENDING, "1");
565     }
566 
567     /**
568      * Update the given {@link Uri} to also include any trashed media items from
569      * calls such as
570      * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
571      * By default no trashed items are returned.
572      *
573      * @see MediaColumns#IS_TRASHED
574      * @see MediaStore#setIncludeTrashed(Uri)
575      * @see MediaStore#trash(Context, Uri)
576      * @see MediaStore#untrash(Context, Uri)
577      * @removed
578      */
579     @Deprecated
setIncludeTrashed(@onNull Uri uri)580     public static @NonNull Uri setIncludeTrashed(@NonNull Uri uri) {
581         return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_TRASHED, "1").build();
582     }
583 
584     /**
585      * Update the given {@link Uri} to indicate that the caller requires the
586      * original file contents when calling
587      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
588      * <p>
589      * This can be useful when the caller wants to ensure they're backing up the
590      * exact bytes of the underlying media, without any Exif redaction being
591      * performed.
592      * <p>
593      * If the original file contents cannot be provided, a
594      * {@link UnsupportedOperationException} will be thrown when the returned
595      * {@link Uri} is used, such as when the caller doesn't hold
596      * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}.
597      */
setRequireOriginal(@onNull Uri uri)598     public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) {
599         return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build();
600     }
601 
602     /**
603      * Create a new pending media item using the given parameters. Pending items
604      * are expected to have a short lifetime, and owners should either
605      * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
606      * pending item within a few hours after first creating it.
607      *
608      * @return token which can be passed to {@link #openPending(Context, Uri)}
609      *         to work with this pending item.
610      * @see MediaColumns#IS_PENDING
611      * @see MediaStore#setIncludePending(Uri)
612      * @see MediaStore#createPending(Context, PendingParams)
613      * @removed
614      */
615     @Deprecated
createPending(@onNull Context context, @NonNull PendingParams params)616     public static @NonNull Uri createPending(@NonNull Context context,
617             @NonNull PendingParams params) {
618         return context.getContentResolver().insert(params.insertUri, params.insertValues);
619     }
620 
621     /**
622      * Open a pending media item to make progress on it. You can open a pending
623      * item multiple times before finally calling either
624      * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
625      *
626      * @param uri token which was previously returned from
627      *            {@link #createPending(Context, PendingParams)}.
628      * @removed
629      */
630     @Deprecated
openPending(@onNull Context context, @NonNull Uri uri)631     public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
632         return new PendingSession(context, uri);
633     }
634 
635     /**
636      * Parameters that describe a pending media item.
637      *
638      * @removed
639      */
640     @Deprecated
641     public static class PendingParams {
642         /** {@hide} */
643         public final Uri insertUri;
644         /** {@hide} */
645         public final ContentValues insertValues;
646 
647         /**
648          * Create parameters that describe a pending media item.
649          *
650          * @param insertUri the {@code content://} Uri where this pending item
651          *            should be inserted when finally published. For example, to
652          *            publish an image, use
653          *            {@link MediaStore.Images.Media#getContentUri(String)}.
654          */
PendingParams(@onNull Uri insertUri, @NonNull String displayName, @NonNull String mimeType)655         public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
656                 @NonNull String mimeType) {
657             this.insertUri = Objects.requireNonNull(insertUri);
658             final long now = System.currentTimeMillis() / 1000;
659             this.insertValues = new ContentValues();
660             this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
661             this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
662             this.insertValues.put(MediaColumns.DATE_ADDED, now);
663             this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
664             this.insertValues.put(MediaColumns.IS_PENDING, 1);
665             this.insertValues.put(MediaColumns.DATE_EXPIRES,
666                     (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
667         }
668 
669         /**
670          * Optionally set the primary directory under which this pending item
671          * should be persisted. Only specific well-defined directories from
672          * {@link Environment} are allowed based on the media type being
673          * inserted.
674          * <p>
675          * For example, when creating pending {@link MediaStore.Images.Media}
676          * items, only {@link Environment#DIRECTORY_PICTURES} or
677          * {@link Environment#DIRECTORY_DCIM} are allowed.
678          * <p>
679          * You may leave this value undefined to store the media in a default
680          * location. For example, when this value is left undefined, pending
681          * {@link MediaStore.Audio.Media} items are stored under
682          * {@link Environment#DIRECTORY_MUSIC}.
683          *
684          * @see MediaColumns#PRIMARY_DIRECTORY
685          */
setPrimaryDirectory(@ullable String primaryDirectory)686         public void setPrimaryDirectory(@Nullable String primaryDirectory) {
687             if (primaryDirectory == null) {
688                 this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY);
689             } else {
690                 this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory);
691             }
692         }
693 
694         /**
695          * Optionally set the secondary directory under which this pending item
696          * should be persisted. Any valid directory name is allowed.
697          * <p>
698          * You may leave this value undefined to store the media as a direct
699          * descendant of the {@link #setPrimaryDirectory(String)} location.
700          *
701          * @see MediaColumns#SECONDARY_DIRECTORY
702          */
setSecondaryDirectory(@ullable String secondaryDirectory)703         public void setSecondaryDirectory(@Nullable String secondaryDirectory) {
704             if (secondaryDirectory == null) {
705                 this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY);
706             } else {
707                 this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory);
708             }
709         }
710 
711         /**
712          * Optionally set the Uri from where the file has been downloaded. This is used
713          * for files being added to {@link Downloads} table.
714          *
715          * @see DownloadColumns#DOWNLOAD_URI
716          */
setDownloadUri(@ullable Uri downloadUri)717         public void setDownloadUri(@Nullable Uri downloadUri) {
718             if (downloadUri == null) {
719                 this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
720             } else {
721                 this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
722             }
723         }
724 
725         /**
726          * Optionally set the Uri indicating HTTP referer of the file. This is used for
727          * files being added to {@link Downloads} table.
728          *
729          * @see DownloadColumns#REFERER_URI
730          */
setRefererUri(@ullable Uri refererUri)731         public void setRefererUri(@Nullable Uri refererUri) {
732             if (refererUri == null) {
733                 this.insertValues.remove(DownloadColumns.REFERER_URI);
734             } else {
735                 this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
736             }
737         }
738     }
739 
740     /**
741      * Session actively working on a pending media item. Pending items are
742      * expected to have a short lifetime, and owners should either
743      * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
744      * pending item within a few hours after first creating it.
745      *
746      * @removed
747      */
748     @Deprecated
749     public static class PendingSession implements AutoCloseable {
750         /** {@hide} */
751         private final Context mContext;
752         /** {@hide} */
753         private final Uri mUri;
754 
755         /** {@hide} */
PendingSession(Context context, Uri uri)756         public PendingSession(Context context, Uri uri) {
757             mContext = Objects.requireNonNull(context);
758             mUri = Objects.requireNonNull(uri);
759         }
760 
761         /**
762          * Open the underlying file representing this media item. When a media
763          * item is successfully completed, you should
764          * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
765          *
766          * @see #notifyProgress(int)
767          */
open()768         public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
769             return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
770         }
771 
772         /**
773          * Open the underlying file representing this media item. When a media
774          * item is successfully completed, you should
775          * {@link OutputStream#close()} and then {@link #publish()} it.
776          *
777          * @see #notifyProgress(int)
778          */
openOutputStream()779         public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
780             return mContext.getContentResolver().openOutputStream(mUri);
781         }
782 
783         /**
784          * Notify of current progress on this pending media item. Gallery
785          * applications may choose to surface progress information of this
786          * pending item.
787          *
788          * @param progress a percentage between 0 and 100.
789          */
notifyProgress(@ntRangefrom = 0, to = 100) int progress)790         public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
791             final Uri withProgress = mUri.buildUpon()
792                     .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
793             mContext.getContentResolver().notifyChange(withProgress, null, 0);
794         }
795 
796         /**
797          * When this media item is successfully completed, call this method to
798          * publish and make the final item visible to the user.
799          *
800          * @return the final {@code content://} Uri representing the newly
801          *         published media.
802          */
publish()803         public @NonNull Uri publish() {
804             final ContentValues values = new ContentValues();
805             values.put(MediaColumns.IS_PENDING, 0);
806             values.putNull(MediaColumns.DATE_EXPIRES);
807             mContext.getContentResolver().update(mUri, values, null, null);
808             return mUri;
809         }
810 
811         /**
812          * When this media item has failed to be completed, call this method to
813          * destroy the pending item record and any data related to it.
814          */
abandon()815         public void abandon() {
816             mContext.getContentResolver().delete(mUri, null, null);
817         }
818 
819         @Override
close()820         public void close() {
821             // No resources to close, but at least we can inform people that no
822             // progress is being actively made.
823             notifyProgress(-1);
824         }
825     }
826 
827     /**
828      * Mark the given item as being "trashed", meaning it should be deleted at
829      * some point in the future. This is a more gentle operation than simply
830      * calling {@link ContentResolver#delete(Uri, String, String[])}, which
831      * would take effect immediately.
832      * <p>
833      * This method preserves trashed items for at least 48 hours before erasing
834      * them, giving the user a chance to untrash the item.
835      *
836      * @see MediaColumns#IS_TRASHED
837      * @see MediaStore#setIncludeTrashed(Uri)
838      * @see MediaStore#trash(Context, Uri)
839      * @see MediaStore#untrash(Context, Uri)
840      * @removed
841      */
842     @Deprecated
trash(@onNull Context context, @NonNull Uri uri)843     public static void trash(@NonNull Context context, @NonNull Uri uri) {
844         trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
845     }
846 
847     /**
848      * Mark the given item as being "trashed", meaning it should be deleted at
849      * some point in the future. This is a more gentle operation than simply
850      * calling {@link ContentResolver#delete(Uri, String, String[])}, which
851      * would take effect immediately.
852      * <p>
853      * This method preserves trashed items for at least the given timeout before
854      * erasing them, giving the user a chance to untrash the item.
855      *
856      * @see MediaColumns#IS_TRASHED
857      * @see MediaStore#setIncludeTrashed(Uri)
858      * @see MediaStore#trash(Context, Uri)
859      * @see MediaStore#untrash(Context, Uri)
860      * @removed
861      */
862     @Deprecated
trash(@onNull Context context, @NonNull Uri uri, @DurationMillisLong long timeoutMillis)863     public static void trash(@NonNull Context context, @NonNull Uri uri,
864             @DurationMillisLong long timeoutMillis) {
865         if (timeoutMillis < 0) {
866             throw new IllegalArgumentException();
867         }
868 
869         final ContentValues values = new ContentValues();
870         values.put(MediaColumns.IS_TRASHED, 1);
871         values.put(MediaColumns.DATE_EXPIRES,
872                 (System.currentTimeMillis() + timeoutMillis) / 1000);
873         context.getContentResolver().update(uri, values, null, null);
874     }
875 
876     /**
877      * Mark the given item as being "untrashed", meaning it should no longer be
878      * deleted as previously requested through {@link #trash(Context, Uri)}.
879      *
880      * @see MediaColumns#IS_TRASHED
881      * @see MediaStore#setIncludeTrashed(Uri)
882      * @see MediaStore#trash(Context, Uri)
883      * @see MediaStore#untrash(Context, Uri)
884      * @removed
885      */
886     @Deprecated
untrash(@onNull Context context, @NonNull Uri uri)887     public static void untrash(@NonNull Context context, @NonNull Uri uri) {
888         final ContentValues values = new ContentValues();
889         values.put(MediaColumns.IS_TRASHED, 0);
890         values.putNull(MediaColumns.DATE_EXPIRES);
891         context.getContentResolver().update(uri, values, null, null);
892     }
893 
894     /**
895      * Common media metadata columns.
896      */
897     public interface MediaColumns extends BaseColumns {
898         /**
899          * Absolute filesystem path to the media item on disk.
900          * <p>
901          * Note that apps may not have filesystem permissions to directly access
902          * this path. Instead of trying to open this path directly, apps should
903          * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
904          * access.
905          *
906          * @deprecated Apps may not have filesystem permissions to directly
907          *             access this path. Instead of trying to open this path
908          *             directly, apps should use
909          *             {@link ContentResolver#openFileDescriptor(Uri, String)}
910          *             to gain access.
911          */
912         @Deprecated
913         @Column(Cursor.FIELD_TYPE_STRING)
914         public static final String DATA = "_data";
915 
916         /**
917          * Hash of the media item on disk.
918          * <p>
919          * Contains a 20-byte binary blob which is the SHA-1 hash of the file as
920          * persisted on disk. For performance reasons, the hash may not be
921          * immediately available, in which case a {@code NULL} value will be
922          * returned. If the underlying file is modified, this value will be
923          * cleared and recalculated.
924          * <p>
925          * If you require the hash of a specific item, you can call
926          * {@link ContentResolver#canonicalize(Uri)}, which will block until the
927          * hash is calculated.
928          *
929          * @removed
930          */
931         @Deprecated
932         @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true)
933         public static final String HASH = "_hash";
934 
935         /**
936          * The size of the media item.
937          */
938         @BytesLong
939         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
940         public static final String SIZE = "_size";
941 
942         /**
943          * The display name of the media item.
944          * <p>
945          * For example, an item stored at
946          * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a
947          * display name of {@code IMG1024.JPG}.
948          */
949         @Column(Cursor.FIELD_TYPE_STRING)
950         public static final String DISPLAY_NAME = "_display_name";
951 
952         /**
953          * The title of the media item.
954          */
955         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
956         public static final String TITLE = "title";
957 
958         /**
959          * The time the media item was first added.
960          */
961         @CurrentTimeSecondsLong
962         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
963         public static final String DATE_ADDED = "date_added";
964 
965         /**
966          * The time the media item was last modified.
967          */
968         @CurrentTimeSecondsLong
969         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
970         public static final String DATE_MODIFIED = "date_modified";
971 
972         /**
973          * The time the media item was taken.
974          */
975         @CurrentTimeMillisLong
976         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
977         public static final String DATE_TAKEN = "datetaken";
978 
979         /**
980          * The MIME type of the media item.
981          * <p>
982          * This is typically defined based on the file extension of the media
983          * item. However, it may be the value of the {@code format} attribute
984          * defined by the <em>Dublin Core Media Initiative</em> standard,
985          * extracted from any XMP metadata contained within this media item.
986          * <p class="note">
987          * Note: the {@code format} attribute may be ignored if the top-level
988          * MIME type disagrees with the file extension. For example, it's
989          * reasonable for an {@code image/jpeg} file to declare a {@code format}
990          * of {@code image/vnd.google.panorama360+jpg}, but declaring a
991          * {@code format} of {@code audio/ogg} would be ignored.
992          * <p>
993          * This is a read-only column that is automatically computed.
994          */
995         @Column(Cursor.FIELD_TYPE_STRING)
996         public static final String MIME_TYPE = "mime_type";
997 
998         /**
999          * The MTP object handle of a newly transfered file.
1000          * Used to pass the new file's object handle through the media scanner
1001          * from MTP to the media provider
1002          * For internal use only by MTP, media scanner and media provider.
1003          * @hide
1004          */
1005         @Deprecated
1006         // @Column(Cursor.FIELD_TYPE_INTEGER)
1007         public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id";
1008 
1009         /**
1010          * Non-zero if the media file is drm-protected
1011          * @hide
1012          */
1013         @UnsupportedAppUsage
1014         @Deprecated
1015         @Column(Cursor.FIELD_TYPE_INTEGER)
1016         public static final String IS_DRM = "is_drm";
1017 
1018         /**
1019          * Flag indicating if a media item is pending, and still being inserted
1020          * by its owner. While this flag is set, only the owner of the item can
1021          * open the underlying file; requests from other apps will be rejected.
1022          *
1023          * @see MediaStore#setIncludePending(Uri)
1024          */
1025         @Column(Cursor.FIELD_TYPE_INTEGER)
1026         public static final String IS_PENDING = "is_pending";
1027 
1028         /**
1029          * Flag indicating if a media item is trashed.
1030          *
1031          * @see MediaColumns#IS_TRASHED
1032          * @see MediaStore#setIncludeTrashed(Uri)
1033          * @see MediaStore#trash(Context, Uri)
1034          * @see MediaStore#untrash(Context, Uri)
1035          * @removed
1036          */
1037         @Deprecated
1038         @Column(Cursor.FIELD_TYPE_INTEGER)
1039         public static final String IS_TRASHED = "is_trashed";
1040 
1041         /**
1042          * The time the media item should be considered expired. Typically only
1043          * meaningful in the context of {@link #IS_PENDING}.
1044          */
1045         @CurrentTimeSecondsLong
1046         @Column(Cursor.FIELD_TYPE_INTEGER)
1047         public static final String DATE_EXPIRES = "date_expires";
1048 
1049         /**
1050          * The width of the media item, in pixels.
1051          */
1052         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1053         public static final String WIDTH = "width";
1054 
1055         /**
1056          * The height of the media item, in pixels.
1057          */
1058         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1059         public static final String HEIGHT = "height";
1060 
1061         /**
1062          * Package name that contributed this media. The value may be
1063          * {@code NULL} if ownership cannot be reliably determined.
1064          */
1065         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1066         public static final String OWNER_PACKAGE_NAME = "owner_package_name";
1067 
1068         /**
1069          * Volume name of the specific storage device where this media item is
1070          * persisted. The value is typically one of the volume names returned
1071          * from {@link MediaStore#getExternalVolumeNames(Context)}.
1072          * <p>
1073          * This is a read-only column that is automatically computed.
1074          */
1075         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1076         public static final String VOLUME_NAME = "volume_name";
1077 
1078         /**
1079          * Relative path of this media item within the storage device where it
1080          * is persisted. For example, an item stored at
1081          * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a
1082          * path of {@code DCIM/Vacation/}.
1083          * <p>
1084          * This value should only be used for organizational purposes, and you
1085          * should not attempt to construct or access a raw filesystem path using
1086          * this value. If you need to open a media item, use an API like
1087          * {@link ContentResolver#openFileDescriptor(Uri, String)}.
1088          * <p>
1089          * When this value is set to {@code NULL} during an
1090          * {@link ContentResolver#insert} operation, the newly created item will
1091          * be placed in a relevant default location based on the type of media
1092          * being inserted. For example, a {@code image/jpeg} item will be placed
1093          * under {@link Environment#DIRECTORY_PICTURES}.
1094          * <p>
1095          * You can modify this column during an {@link ContentResolver#update}
1096          * call, which will move the underlying file on disk.
1097          * <p>
1098          * In both cases above, content must be placed under a top-level
1099          * directory that is relevant to the media type. For example, attempting
1100          * to place a {@code audio/mpeg} file under
1101          * {@link Environment#DIRECTORY_PICTURES} will be rejected.
1102          */
1103         @Column(Cursor.FIELD_TYPE_STRING)
1104         public static final String RELATIVE_PATH = "relative_path";
1105 
1106         /**
1107          * The primary directory name this media exists under. The value may be
1108          * {@code NULL} if the media doesn't have a primary directory name.
1109          *
1110          * @removed
1111          * @deprecated Replaced by {@link #RELATIVE_PATH}.
1112          */
1113         @Column(Cursor.FIELD_TYPE_STRING)
1114         @Deprecated
1115         public static final String PRIMARY_DIRECTORY = "primary_directory";
1116 
1117         /**
1118          * The secondary directory name this media exists under. The value may
1119          * be {@code NULL} if the media doesn't have a secondary directory name.
1120          *
1121          * @removed
1122          * @deprecated Replaced by {@link #RELATIVE_PATH}.
1123          */
1124         @Column(Cursor.FIELD_TYPE_STRING)
1125         @Deprecated
1126         public static final String SECONDARY_DIRECTORY = "secondary_directory";
1127 
1128         /**
1129          * The primary bucket ID of this media item. This can be useful to
1130          * present the user a first-level clustering of related media items.
1131          * This is a read-only column that is automatically computed.
1132          */
1133         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1134         public static final String BUCKET_ID = "bucket_id";
1135 
1136         /**
1137          * The primary bucket display name of this media item. This can be
1138          * useful to present the user a first-level clustering of related
1139          * media items. This is a read-only column that is automatically
1140          * computed.
1141          */
1142         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1143         public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
1144 
1145         /**
1146          * The group ID of this media item. This can be useful to present
1147          * the user a grouping of related media items, such a burst of
1148          * images, or a {@code JPG} and {@code DNG} version of the same
1149          * image.
1150          * <p>
1151          * This is a read-only column that is automatically computed based
1152          * on the first portion of the filename. For example,
1153          * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG}
1154          * will have the same {@link #GROUP_ID} because the first portion of
1155          * their filenames is identical.
1156          *
1157          * @removed
1158          */
1159         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1160         @Deprecated
1161         public static final String GROUP_ID = "group_id";
1162 
1163         /**
1164          * The "document ID" GUID as defined by the <em>XMP Media
1165          * Management</em> standard, extracted from any XMP metadata contained
1166          * within this media item. The value is {@code null} when no metadata
1167          * was found.
1168          * <p>
1169          * Each "document ID" is created once for each new resource. Different
1170          * renditions of that resource are expected to have different IDs.
1171          */
1172         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1173         public static final String DOCUMENT_ID = "document_id";
1174 
1175         /**
1176          * The "instance ID" GUID as defined by the <em>XMP Media
1177          * Management</em> standard, extracted from any XMP metadata contained
1178          * within this media item. The value is {@code null} when no metadata
1179          * was found.
1180          * <p>
1181          * This "instance ID" changes with each save operation of a specific
1182          * "document ID".
1183          */
1184         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1185         public static final String INSTANCE_ID = "instance_id";
1186 
1187         /**
1188          * The "original document ID" GUID as defined by the <em>XMP Media
1189          * Management</em> standard, extracted from any XMP metadata contained
1190          * within this media item.
1191          * <p>
1192          * This "original document ID" links a resource to its original source.
1193          * For example, when you save a PSD document as a JPEG, then convert the
1194          * JPEG to GIF format, the "original document ID" of both the JPEG and
1195          * GIF files is the "document ID" of the original PSD file.
1196          */
1197         @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1198         public static final String ORIGINAL_DOCUMENT_ID = "original_document_id";
1199 
1200         /**
1201          * The duration of the media item.
1202          */
1203         @DurationMillisLong
1204         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1205         public static final String DURATION = "duration";
1206 
1207         /**
1208          * The orientation for the media item, expressed in degrees. For
1209          * example, 0, 90, 180, or 270 degrees.
1210          */
1211         @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1212         public static final String ORIENTATION = "orientation";
1213     }
1214 
1215     /**
1216      * Media provider table containing an index of all files in the media storage,
1217      * including non-media files.  This should be used by applications that work with
1218      * non-media file types (text, HTML, PDF, etc) as well as applications that need to
1219      * work with multiple media file types in a single query.
1220      */
1221     public static final class Files {
1222         /** @hide */
1223         public static final String TABLE = "files";
1224 
1225         /** @hide */
1226         public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL);
1227 
1228         /**
1229          * Get the content:// style URI for the files table on the
1230          * given volume.
1231          *
1232          * @param volumeName the name of the volume to get the URI for
1233          * @return the URI to the files table on the given volume
1234          */
getContentUri(String volumeName)1235         public static Uri getContentUri(String volumeName) {
1236             return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("file").build();
1237         }
1238 
1239         /**
1240          * Get the content:// style URI for a single row in the files table on the
1241          * given volume.
1242          *
1243          * @param volumeName the name of the volume to get the URI for
1244          * @param rowId the file to get the URI for
1245          * @return the URI to the files table on the given volume
1246          */
getContentUri(String volumeName, long rowId)1247         public static final Uri getContentUri(String volumeName,
1248                 long rowId) {
1249             return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
1250         }
1251 
1252         /**
1253          * For use only by the MTP implementation.
1254          * @hide
1255          */
1256         @UnsupportedAppUsage
getMtpObjectsUri(String volumeName)1257         public static Uri getMtpObjectsUri(String volumeName) {
1258             return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
1259         }
1260 
1261         /**
1262          * For use only by the MTP implementation.
1263          * @hide
1264          */
1265         @UnsupportedAppUsage
getMtpObjectsUri(String volumeName, long fileId)1266         public static final Uri getMtpObjectsUri(String volumeName,
1267                 long fileId) {
1268             return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
1269         }
1270 
1271         /**
1272          * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
1273          * @hide
1274          */
1275         @UnsupportedAppUsage
getMtpReferencesUri(String volumeName, long fileId)1276         public static final Uri getMtpReferencesUri(String volumeName,
1277                 long fileId) {
1278             return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
1279                     .build();
1280         }
1281 
1282         /**
1283          * Used to trigger special logic for directories.
1284          * @hide
1285          */
getDirectoryUri(String volumeName)1286         public static final Uri getDirectoryUri(String volumeName) {
1287             return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build();
1288         }
1289 
1290         /** @hide */
getContentUriForPath(String path)1291         public static final Uri getContentUriForPath(String path) {
1292             return getContentUri(getVolumeName(new File(path)));
1293         }
1294 
1295         /**
1296          * File metadata columns.
1297          */
1298         public interface FileColumns extends MediaColumns {
1299             /**
1300              * The MTP storage ID of the file
1301              * @hide
1302              */
1303             @UnsupportedAppUsage
1304             @Deprecated
1305             // @Column(Cursor.FIELD_TYPE_INTEGER)
1306             public static final String STORAGE_ID = "storage_id";
1307 
1308             /**
1309              * The MTP format code of the file
1310              * @hide
1311              */
1312             @UnsupportedAppUsage
1313             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1314             public static final String FORMAT = "format";
1315 
1316             /**
1317              * The index of the parent directory of the file
1318              */
1319             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1320             public static final String PARENT = "parent";
1321 
1322             /**
1323              * The MIME type of the media item.
1324              * <p>
1325              * This is typically defined based on the file extension of the media
1326              * item. However, it may be the value of the {@code format} attribute
1327              * defined by the <em>Dublin Core Media Initiative</em> standard,
1328              * extracted from any XMP metadata contained within this media item.
1329              * <p class="note">
1330              * Note: the {@code format} attribute may be ignored if the top-level
1331              * MIME type disagrees with the file extension. For example, it's
1332              * reasonable for an {@code image/jpeg} file to declare a {@code format}
1333              * of {@code image/vnd.google.panorama360+jpg}, but declaring a
1334              * {@code format} of {@code audio/ogg} would be ignored.
1335              * <p>
1336              * This is a read-only column that is automatically computed.
1337              */
1338             @Column(Cursor.FIELD_TYPE_STRING)
1339             public static final String MIME_TYPE = "mime_type";
1340 
1341             /**
1342              * The title of the media item.
1343              */
1344             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1345             public static final String TITLE = "title";
1346 
1347             /**
1348              * The media type (audio, video, image or playlist)
1349              * of the file, or 0 for not a media file
1350              */
1351             @Column(Cursor.FIELD_TYPE_INTEGER)
1352             public static final String MEDIA_TYPE = "media_type";
1353 
1354             /**
1355              * Constant for the {@link #MEDIA_TYPE} column indicating that file
1356              * is not an audio, image, video or playlist file.
1357              */
1358             public static final int MEDIA_TYPE_NONE = 0;
1359 
1360             /**
1361              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an image file.
1362              */
1363             public static final int MEDIA_TYPE_IMAGE = 1;
1364 
1365             /**
1366              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an audio file.
1367              */
1368             public static final int MEDIA_TYPE_AUDIO = 2;
1369 
1370             /**
1371              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a video file.
1372              */
1373             public static final int MEDIA_TYPE_VIDEO = 3;
1374 
1375             /**
1376              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a playlist file.
1377              */
1378             public static final int MEDIA_TYPE_PLAYLIST = 4;
1379 
1380             /**
1381              * Column indicating if the file is part of Downloads collection.
1382              * @hide
1383              */
1384             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
1385             public static final String IS_DOWNLOAD = "is_download";
1386         }
1387     }
1388 
1389     /** @hide */
1390     public static class ThumbnailConstants {
1391         public static final int MINI_KIND = 1;
1392         public static final int FULL_SCREEN_KIND = 2;
1393         public static final int MICRO_KIND = 3;
1394 
1395         public static final Point MINI_SIZE = new Point(512, 384);
1396         public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
1397         public static final Point MICRO_SIZE = new Point(96, 96);
1398     }
1399 
1400     /**
1401      * Download metadata columns.
1402      */
1403     public interface DownloadColumns extends MediaColumns {
1404         /**
1405          * Uri indicating where the item has been downloaded from.
1406          */
1407         @Column(Cursor.FIELD_TYPE_STRING)
1408         String DOWNLOAD_URI = "download_uri";
1409 
1410         /**
1411          * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}.
1412          */
1413         @Column(Cursor.FIELD_TYPE_STRING)
1414         String REFERER_URI = "referer_uri";
1415 
1416         /**
1417          * The description of the download.
1418          *
1419          * @removed
1420          */
1421         @Deprecated
1422         @Column(Cursor.FIELD_TYPE_STRING)
1423         String DESCRIPTION = "description";
1424     }
1425 
1426     /**
1427      * Collection of downloaded items.
1428      */
1429     public static final class Downloads implements DownloadColumns {
Downloads()1430         private Downloads() {}
1431 
1432         /**
1433          * The content:// style URI for the internal storage.
1434          */
1435         @NonNull
1436         public static final Uri INTERNAL_CONTENT_URI =
1437                 getContentUri("internal");
1438 
1439         /**
1440          * The content:// style URI for the "primary" external storage
1441          * volume.
1442          */
1443         @NonNull
1444         public static final Uri EXTERNAL_CONTENT_URI =
1445                 getContentUri("external");
1446 
1447         /**
1448          * The MIME type for this table.
1449          */
1450         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download";
1451 
1452         /**
1453          * Regex that matches paths that needs to be considered part of downloads collection.
1454          * @hide
1455          */
1456         public static final Pattern PATTERN_DOWNLOADS_FILE = Pattern.compile(
1457                 "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/.+");
1458         private static final Pattern PATTERN_DOWNLOADS_DIRECTORY = Pattern.compile(
1459                 "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/?");
1460 
1461         /**
1462          * Get the content:// style URI for the downloads table on the
1463          * given volume.
1464          *
1465          * @param volumeName the name of the volume to get the URI for
1466          * @return the URI to the image media table on the given volume
1467          */
getContentUri(@onNull String volumeName)1468         public static @NonNull Uri getContentUri(@NonNull String volumeName) {
1469             return AUTHORITY_URI.buildUpon().appendPath(volumeName)
1470                     .appendPath("downloads").build();
1471         }
1472 
1473         /** @hide */
getContentUri(@onNull String volumeName, long id)1474         public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
1475             return ContentUris.withAppendedId(getContentUri(volumeName), id);
1476         }
1477 
1478         /** @hide */
getContentUriForPath(@onNull String path)1479         public static @NonNull Uri getContentUriForPath(@NonNull String path) {
1480             return getContentUri(getVolumeName(new File(path)));
1481         }
1482 
1483         /** @hide */
isDownload(@onNull String path)1484         public static boolean isDownload(@NonNull String path) {
1485             return PATTERN_DOWNLOADS_FILE.matcher(path).matches();
1486         }
1487 
1488         /** @hide */
isDownloadDir(@onNull String path)1489         public static boolean isDownloadDir(@NonNull String path) {
1490             return PATTERN_DOWNLOADS_DIRECTORY.matcher(path).matches();
1491         }
1492     }
1493 
1494     /** {@hide} */
getVolumeName(@onNull File path)1495     public static @NonNull String getVolumeName(@NonNull File path) {
1496         if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
1497             final StorageManager sm = AppGlobals.getInitialApplication()
1498                     .getSystemService(StorageManager.class);
1499             final StorageVolume sv = sm.getStorageVolume(path);
1500             if (sv != null) {
1501                 if (sv.isPrimary()) {
1502                     return VOLUME_EXTERNAL_PRIMARY;
1503                 } else {
1504                     return checkArgumentVolumeName(sv.getNormalizedUuid());
1505                 }
1506             }
1507             throw new IllegalStateException("Unknown volume at " + path);
1508         } else {
1509             return VOLUME_INTERNAL;
1510         }
1511     }
1512 
1513     /**
1514      * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
1515      * to be accessed elsewhere.
1516      */
1517     @Deprecated
1518     private static class InternalThumbnails implements BaseColumns {
1519         /**
1520          * Currently outstanding thumbnail requests that can be cancelled.
1521          */
1522         @GuardedBy("sPending")
1523         private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
1524 
1525         /**
1526          * Make a blocking request to obtain the given thumbnail, generating it
1527          * if needed.
1528          *
1529          * @see #cancelThumbnail(ContentResolver, Uri)
1530          */
1531         @Deprecated
getThumbnail(@onNull ContentResolver cr, @NonNull Uri uri, int kind, @Nullable BitmapFactory.Options opts)1532         static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
1533                 int kind, @Nullable BitmapFactory.Options opts) {
1534             final Point size;
1535             if (kind == ThumbnailConstants.MICRO_KIND) {
1536                 size = ThumbnailConstants.MICRO_SIZE;
1537             } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
1538                 size = ThumbnailConstants.FULL_SCREEN_SIZE;
1539             } else if (kind == ThumbnailConstants.MINI_KIND) {
1540                 size = ThumbnailConstants.MINI_SIZE;
1541             } else {
1542                 throw new IllegalArgumentException("Unsupported kind: " + kind);
1543             }
1544 
1545             CancellationSignal signal = null;
1546             synchronized (sPending) {
1547                 signal = sPending.get(uri);
1548                 if (signal == null) {
1549                     signal = new CancellationSignal();
1550                     sPending.put(uri, signal);
1551                 }
1552             }
1553 
1554             try {
1555                 return cr.loadThumbnail(uri, Point.convert(size), signal);
1556             } catch (IOException e) {
1557                 Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
1558                 return null;
1559             } finally {
1560                 synchronized (sPending) {
1561                     sPending.remove(uri);
1562                 }
1563             }
1564         }
1565 
1566         /**
1567          * This method cancels the thumbnail request so clients waiting for
1568          * {@link #getThumbnail} will be interrupted and return immediately.
1569          * Only the original process which made the request can cancel their own
1570          * requests.
1571          */
1572         @Deprecated
cancelThumbnail(@onNull ContentResolver cr, @NonNull Uri uri)1573         static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) {
1574             synchronized (sPending) {
1575                 final CancellationSignal signal = sPending.get(uri);
1576                 if (signal != null) {
1577                     signal.cancel();
1578                 }
1579             }
1580         }
1581     }
1582 
1583     /**
1584      * Collection of all media with MIME type of {@code image/*}.
1585      */
1586     public static final class Images {
1587         /**
1588          * Image metadata columns.
1589          */
1590         public interface ImageColumns extends MediaColumns {
1591             /**
1592              * The description of the image
1593              */
1594             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
1595             public static final String DESCRIPTION = "description";
1596 
1597             /**
1598              * The picasa id of the image
1599              *
1600              * @deprecated this value was only relevant for images hosted on
1601              *             Picasa, which are no longer supported.
1602              */
1603             @Deprecated
1604             @Column(Cursor.FIELD_TYPE_STRING)
1605             public static final String PICASA_ID = "picasa_id";
1606 
1607             /**
1608              * Whether the video should be published as public or private
1609              */
1610             @Column(Cursor.FIELD_TYPE_INTEGER)
1611             public static final String IS_PRIVATE = "isprivate";
1612 
1613             /**
1614              * The latitude where the image was captured.
1615              *
1616              * @deprecated location details are no longer indexed for privacy
1617              *             reasons, and this value is now always {@code null}.
1618              *             You can still manually obtain location metadata using
1619              *             {@link ExifInterface#getLatLong(float[])}.
1620              */
1621             @Deprecated
1622             @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
1623             public static final String LATITUDE = "latitude";
1624 
1625             /**
1626              * The longitude where the image was captured.
1627              *
1628              * @deprecated location details are no longer indexed for privacy
1629              *             reasons, and this value is now always {@code null}.
1630              *             You can still manually obtain location metadata using
1631              *             {@link ExifInterface#getLatLong(float[])}.
1632              */
1633             @Deprecated
1634             @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
1635             public static final String LONGITUDE = "longitude";
1636 
1637             /** @removed promoted to parent interface */
1638             public static final String DATE_TAKEN = "datetaken";
1639             /** @removed promoted to parent interface */
1640             public static final String ORIENTATION = "orientation";
1641 
1642             /**
1643              * The mini thumb id.
1644              *
1645              * @deprecated all thumbnails should be obtained via
1646              *             {@link MediaStore.Images.Thumbnails#getThumbnail}, as this
1647              *             value is no longer supported.
1648              */
1649             @Deprecated
1650             @Column(Cursor.FIELD_TYPE_INTEGER)
1651             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
1652 
1653             /** @removed promoted to parent interface */
1654             public static final String BUCKET_ID = "bucket_id";
1655             /** @removed promoted to parent interface */
1656             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
1657             /** @removed promoted to parent interface */
1658             public static final String GROUP_ID = "group_id";
1659         }
1660 
1661         public static final class Media implements ImageColumns {
1662             /**
1663              * @deprecated all queries should be performed through
1664              *             {@link ContentResolver} directly, which offers modern
1665              *             features like {@link CancellationSignal}.
1666              */
1667             @Deprecated
query(ContentResolver cr, Uri uri, String[] projection)1668             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1669                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1670             }
1671 
1672             /**
1673              * @deprecated all queries should be performed through
1674              *             {@link ContentResolver} directly, which offers modern
1675              *             features like {@link CancellationSignal}.
1676              */
1677             @Deprecated
query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)1678             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
1679                     String where, String orderBy) {
1680                 return cr.query(uri, projection, where,
1681                                              null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
1682             }
1683 
1684             /**
1685              * @deprecated all queries should be performed through
1686              *             {@link ContentResolver} directly, which offers modern
1687              *             features like {@link CancellationSignal}.
1688              */
1689             @Deprecated
query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)1690             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
1691                     String selection, String [] selectionArgs, String orderBy) {
1692                 return cr.query(uri, projection, selection,
1693                         selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
1694             }
1695 
1696             /**
1697              * Retrieves an image for the given url as a {@link Bitmap}.
1698              *
1699              * @param cr The content resolver to use
1700              * @param url The url of the image
1701              * @deprecated loading of images should be performed through
1702              *             {@link ImageDecoder#createSource(ContentResolver, Uri)},
1703              *             which offers modern features like
1704              *             {@link PostProcessor}.
1705              */
1706             @Deprecated
getBitmap(ContentResolver cr, Uri url)1707             public static final Bitmap getBitmap(ContentResolver cr, Uri url)
1708                     throws FileNotFoundException, IOException {
1709                 InputStream input = cr.openInputStream(url);
1710                 Bitmap bitmap = BitmapFactory.decodeStream(input);
1711                 input.close();
1712                 return bitmap;
1713             }
1714 
1715             /**
1716              * Insert an image and create a thumbnail for it.
1717              *
1718              * @param cr The content resolver to use
1719              * @param imagePath The path to the image to insert
1720              * @param name The name of the image
1721              * @param description The description of the image
1722              * @return The URL to the newly created image
1723              * @deprecated inserting of images should be performed using
1724              *             {@link MediaColumns#IS_PENDING}, which offers richer
1725              *             control over lifecycle.
1726              */
1727             @Deprecated
insertImage(ContentResolver cr, String imagePath, String name, String description)1728             public static final String insertImage(ContentResolver cr, String imagePath,
1729                     String name, String description) throws FileNotFoundException {
1730                 final File file = new File(imagePath);
1731                 final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
1732 
1733                 if (TextUtils.isEmpty(name)) name = "Image";
1734                 final PendingParams params = new PendingParams(
1735                         MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
1736 
1737                 final Context context = AppGlobals.getInitialApplication();
1738                 final Uri pendingUri = createPending(context, params);
1739                 try (PendingSession session = openPending(context, pendingUri)) {
1740                     try (InputStream in = new FileInputStream(file);
1741                          OutputStream out = session.openOutputStream()) {
1742                         FileUtils.copy(in, out);
1743                     }
1744                     return session.publish().toString();
1745                 } catch (Exception e) {
1746                     Log.w(TAG, "Failed to insert image", e);
1747                     context.getContentResolver().delete(pendingUri, null, null);
1748                     return null;
1749                 }
1750             }
1751 
1752             /**
1753              * Insert an image and create a thumbnail for it.
1754              *
1755              * @param cr The content resolver to use
1756              * @param source The stream to use for the image
1757              * @param title The name of the image
1758              * @param description The description of the image
1759              * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
1760              *              for any reason.
1761              * @deprecated inserting of images should be performed using
1762              *             {@link MediaColumns#IS_PENDING}, which offers richer
1763              *             control over lifecycle.
1764              */
1765             @Deprecated
insertImage(ContentResolver cr, Bitmap source, String title, String description)1766             public static final String insertImage(ContentResolver cr, Bitmap source,
1767                                                    String title, String description) {
1768                 if (TextUtils.isEmpty(title)) title = "Image";
1769                 final PendingParams params = new PendingParams(
1770                         MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
1771 
1772                 final Context context = AppGlobals.getInitialApplication();
1773                 final Uri pendingUri = createPending(context, params);
1774                 try (PendingSession session = openPending(context, pendingUri)) {
1775                     try (OutputStream out = session.openOutputStream()) {
1776                         source.compress(Bitmap.CompressFormat.JPEG, 90, out);
1777                     }
1778                     return session.publish().toString();
1779                 } catch (Exception e) {
1780                     Log.w(TAG, "Failed to insert image", e);
1781                     context.getContentResolver().delete(pendingUri, null, null);
1782                     return null;
1783                 }
1784             }
1785 
1786             /**
1787              * Get the content:// style URI for the image media table on the
1788              * given volume.
1789              *
1790              * @param volumeName the name of the volume to get the URI for
1791              * @return the URI to the image media table on the given volume
1792              */
getContentUri(String volumeName)1793             public static Uri getContentUri(String volumeName) {
1794                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images")
1795                         .appendPath("media").build();
1796             }
1797 
1798             /** @hide */
getContentUri(@onNull String volumeName, long id)1799             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
1800                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
1801             }
1802 
1803             /**
1804              * The content:// style URI for the internal storage.
1805              */
1806             public static final Uri INTERNAL_CONTENT_URI =
1807                     getContentUri("internal");
1808 
1809             /**
1810              * The content:// style URI for the "primary" external storage
1811              * volume.
1812              */
1813             public static final Uri EXTERNAL_CONTENT_URI =
1814                     getContentUri("external");
1815 
1816             /**
1817              * The MIME type of of this directory of
1818              * images.  Note that each entry in this directory will have a standard
1819              * image MIME type as appropriate -- for example, image/jpeg.
1820              */
1821             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
1822 
1823             /**
1824              * The default sort order for this table
1825              */
1826             public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
1827         }
1828 
1829         /**
1830          * This class provides utility methods to obtain thumbnails for various
1831          * {@link Images} items.
1832          *
1833          * @deprecated Callers should migrate to using
1834          *             {@link ContentResolver#loadThumbnail}, since it offers
1835          *             richer control over requested thumbnail sizes and
1836          *             cancellation behavior.
1837          */
1838         @Deprecated
1839         public static class Thumbnails implements BaseColumns {
1840             /**
1841              * @deprecated all queries should be performed through
1842              *             {@link ContentResolver} directly, which offers modern
1843              *             features like {@link CancellationSignal}.
1844              */
1845             @Deprecated
query(ContentResolver cr, Uri uri, String[] projection)1846             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1847                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1848             }
1849 
1850             /**
1851              * @deprecated all queries should be performed through
1852              *             {@link ContentResolver} directly, which offers modern
1853              *             features like {@link CancellationSignal}.
1854              */
1855             @Deprecated
queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)1856             public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
1857                     String[] projection) {
1858                 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
1859             }
1860 
1861             /**
1862              * @deprecated all queries should be performed through
1863              *             {@link ContentResolver} directly, which offers modern
1864              *             features like {@link CancellationSignal}.
1865              */
1866             @Deprecated
queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)1867             public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
1868                     String[] projection) {
1869                 return cr.query(EXTERNAL_CONTENT_URI, projection,
1870                         IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
1871                         kind, null, null);
1872             }
1873 
1874             /**
1875              * Cancel any outstanding {@link #getThumbnail} requests, causing
1876              * them to return by throwing a {@link OperationCanceledException}.
1877              * <p>
1878              * This method has no effect on
1879              * {@link ContentResolver#loadThumbnail} calls, since they provide
1880              * their own {@link CancellationSignal}.
1881              *
1882              * @deprecated Callers should migrate to using
1883              *             {@link ContentResolver#loadThumbnail}, since it
1884              *             offers richer control over requested thumbnail sizes
1885              *             and cancellation behavior.
1886              */
1887             @Deprecated
cancelThumbnailRequest(ContentResolver cr, long origId)1888             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
1889                 final Uri uri = ContentUris.withAppendedId(
1890                         Images.Media.EXTERNAL_CONTENT_URI, origId);
1891                 InternalThumbnails.cancelThumbnail(cr, uri);
1892             }
1893 
1894             /**
1895              * Return thumbnail representing a specific image item. If a
1896              * thumbnail doesn't exist, this method will block until it's
1897              * generated. Callers are responsible for their own in-memory
1898              * caching of returned values.
1899              *
1900              * @param imageId the image item to obtain a thumbnail for.
1901              * @param kind optimal thumbnail size desired.
1902              * @return decoded thumbnail, or {@code null} if problem was
1903              *         encountered.
1904              * @deprecated Callers should migrate to using
1905              *             {@link ContentResolver#loadThumbnail}, since it
1906              *             offers richer control over requested thumbnail sizes
1907              *             and cancellation behavior.
1908              */
1909             @Deprecated
getThumbnail(ContentResolver cr, long imageId, int kind, BitmapFactory.Options options)1910             public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind,
1911                     BitmapFactory.Options options) {
1912                 final Uri uri = ContentUris.withAppendedId(
1913                         Images.Media.EXTERNAL_CONTENT_URI, imageId);
1914                 return InternalThumbnails.getThumbnail(cr, uri, kind, options);
1915             }
1916 
1917             /**
1918              * Cancel any outstanding {@link #getThumbnail} requests, causing
1919              * them to return by throwing a {@link OperationCanceledException}.
1920              * <p>
1921              * This method has no effect on
1922              * {@link ContentResolver#loadThumbnail} calls, since they provide
1923              * their own {@link CancellationSignal}.
1924              *
1925              * @deprecated Callers should migrate to using
1926              *             {@link ContentResolver#loadThumbnail}, since it
1927              *             offers richer control over requested thumbnail sizes
1928              *             and cancellation behavior.
1929              */
1930             @Deprecated
cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)1931             public static void cancelThumbnailRequest(ContentResolver cr, long origId,
1932                     long groupId) {
1933                 cancelThumbnailRequest(cr, origId);
1934             }
1935 
1936             /**
1937              * Return thumbnail representing a specific image item. If a
1938              * thumbnail doesn't exist, this method will block until it's
1939              * generated. Callers are responsible for their own in-memory
1940              * caching of returned values.
1941              *
1942              * @param imageId the image item to obtain a thumbnail for.
1943              * @param kind optimal thumbnail size desired.
1944              * @return decoded thumbnail, or {@code null} if problem was
1945              *         encountered.
1946              * @deprecated Callers should migrate to using
1947              *             {@link ContentResolver#loadThumbnail}, since it
1948              *             offers richer control over requested thumbnail sizes
1949              *             and cancellation behavior.
1950              */
1951             @Deprecated
getThumbnail(ContentResolver cr, long imageId, long groupId, int kind, BitmapFactory.Options options)1952             public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId,
1953                     int kind, BitmapFactory.Options options) {
1954                 return getThumbnail(cr, imageId, kind, options);
1955             }
1956 
1957             /**
1958              * Get the content:// style URI for the image media table on the
1959              * given volume.
1960              *
1961              * @param volumeName the name of the volume to get the URI for
1962              * @return the URI to the image media table on the given volume
1963              */
getContentUri(String volumeName)1964             public static Uri getContentUri(String volumeName) {
1965                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images")
1966                         .appendPath("thumbnails").build();
1967             }
1968 
1969             /**
1970              * The content:// style URI for the internal storage.
1971              */
1972             public static final Uri INTERNAL_CONTENT_URI =
1973                     getContentUri("internal");
1974 
1975             /**
1976              * The content:// style URI for the "primary" external storage
1977              * volume.
1978              */
1979             public static final Uri EXTERNAL_CONTENT_URI =
1980                     getContentUri("external");
1981 
1982             /**
1983              * The default sort order for this table
1984              */
1985             public static final String DEFAULT_SORT_ORDER = "image_id ASC";
1986 
1987             /**
1988              * Path to the thumbnail file on disk.
1989              * <p>
1990              * Note that apps may not have filesystem permissions to directly
1991              * access this path. Instead of trying to open this path directly,
1992              * apps should use
1993              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
1994              * access.
1995              *
1996              * @deprecated Apps may not have filesystem permissions to directly
1997              *             access this path. Instead of trying to open this path
1998              *             directly, apps should use
1999              *             {@link ContentResolver#loadThumbnail}
2000              *             to gain access.
2001              */
2002             @Deprecated
2003             @Column(Cursor.FIELD_TYPE_STRING)
2004             public static final String DATA = "_data";
2005 
2006             /**
2007              * The original image for the thumbnal
2008              */
2009             @Column(Cursor.FIELD_TYPE_INTEGER)
2010             public static final String IMAGE_ID = "image_id";
2011 
2012             /**
2013              * The kind of the thumbnail
2014              */
2015             @Column(Cursor.FIELD_TYPE_INTEGER)
2016             public static final String KIND = "kind";
2017 
2018             public static final int MINI_KIND = ThumbnailConstants.MINI_KIND;
2019             public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND;
2020             public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
2021 
2022             /**
2023              * The blob raw data of thumbnail
2024              *
2025              * @deprecated this column never existed internally, and could never
2026              *             have returned valid data.
2027              */
2028             @Deprecated
2029             @Column(Cursor.FIELD_TYPE_BLOB)
2030             public static final String THUMB_DATA = "thumb_data";
2031 
2032             /**
2033              * The width of the thumbnal
2034              */
2035             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2036             public static final String WIDTH = "width";
2037 
2038             /**
2039              * The height of the thumbnail
2040              */
2041             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2042             public static final String HEIGHT = "height";
2043         }
2044     }
2045 
2046     /**
2047      * Collection of all media with MIME type of {@code audio/*}.
2048      */
2049     public static final class Audio {
2050         /**
2051          * Audio metadata columns.
2052          */
2053         public interface AudioColumns extends MediaColumns {
2054 
2055             /**
2056              * A non human readable key calculated from the TITLE, used for
2057              * searching, sorting and grouping
2058              */
2059             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2060             public static final String TITLE_KEY = "title_key";
2061 
2062             /** @removed promoted to parent interface */
2063             public static final String DURATION = "duration";
2064 
2065             /**
2066              * The position within the audio item at which playback should be
2067              * resumed.
2068              */
2069             @DurationMillisLong
2070             @Column(Cursor.FIELD_TYPE_INTEGER)
2071             public static final String BOOKMARK = "bookmark";
2072 
2073             /**
2074              * The id of the artist who created the audio file, if any
2075              */
2076             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2077             public static final String ARTIST_ID = "artist_id";
2078 
2079             /**
2080              * The artist who created the audio file, if any
2081              */
2082             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2083             public static final String ARTIST = "artist";
2084 
2085             /**
2086              * The artist credited for the album that contains the audio file
2087              * @hide
2088              */
2089             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2090             public static final String ALBUM_ARTIST = "album_artist";
2091 
2092             /**
2093              * Whether the song is part of a compilation
2094              * @hide
2095              */
2096             @Deprecated
2097             // @Column(Cursor.FIELD_TYPE_STRING)
2098             public static final String COMPILATION = "compilation";
2099 
2100             /**
2101              * A non human readable key calculated from the ARTIST, used for
2102              * searching, sorting and grouping
2103              */
2104             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2105             public static final String ARTIST_KEY = "artist_key";
2106 
2107             /**
2108              * The composer of the audio file, if any
2109              */
2110             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2111             public static final String COMPOSER = "composer";
2112 
2113             /**
2114              * The id of the album the audio file is from, if any
2115              */
2116             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2117             public static final String ALBUM_ID = "album_id";
2118 
2119             /**
2120              * The album the audio file is from, if any
2121              */
2122             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2123             public static final String ALBUM = "album";
2124 
2125             /**
2126              * A non human readable key calculated from the ALBUM, used for
2127              * searching, sorting and grouping
2128              */
2129             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2130             public static final String ALBUM_KEY = "album_key";
2131 
2132             /**
2133              * The track number of this song on the album, if any.
2134              * This number encodes both the track number and the
2135              * disc number. For multi-disc sets, this number will
2136              * be 1xxx for tracks on the first disc, 2xxx for tracks
2137              * on the second disc, etc.
2138              */
2139             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2140             public static final String TRACK = "track";
2141 
2142             /**
2143              * The year the audio file was recorded, if any
2144              */
2145             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2146             public static final String YEAR = "year";
2147 
2148             /**
2149              * Non-zero if the audio file is music
2150              */
2151             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2152             public static final String IS_MUSIC = "is_music";
2153 
2154             /**
2155              * Non-zero if the audio file is a podcast
2156              */
2157             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2158             public static final String IS_PODCAST = "is_podcast";
2159 
2160             /**
2161              * Non-zero if the audio file may be a ringtone
2162              */
2163             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2164             public static final String IS_RINGTONE = "is_ringtone";
2165 
2166             /**
2167              * Non-zero if the audio file may be an alarm
2168              */
2169             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2170             public static final String IS_ALARM = "is_alarm";
2171 
2172             /**
2173              * Non-zero if the audio file may be a notification sound
2174              */
2175             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2176             public static final String IS_NOTIFICATION = "is_notification";
2177 
2178             /**
2179              * Non-zero if the audio file is an audiobook
2180              */
2181             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2182             public static final String IS_AUDIOBOOK = "is_audiobook";
2183 
2184             /**
2185              * The genre of the audio file, if any
2186              * Does not exist in the database - only used by the media scanner for inserts.
2187              * @hide
2188              */
2189             @Deprecated
2190             // @Column(Cursor.FIELD_TYPE_STRING)
2191             public static final String GENRE = "genre";
2192 
2193             /**
2194              * The resource URI of a localized title, if any
2195              * Conforms to this pattern:
2196              *   Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
2197              *   Authority: Package Name of ringtone title provider
2198              *   First Path Segment: Type of resource (must be "string")
2199              *   Second Path Segment: Resource ID of title
2200              * @hide
2201              */
2202             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2203             public static final String TITLE_RESOURCE_URI = "title_resource_uri";
2204         }
2205 
2206         /**
2207          * Converts a name to a "key" that can be used for grouping, sorting
2208          * and searching.
2209          * The rules that govern this conversion are:
2210          * - remove 'special' characters like ()[]'!?.,
2211          * - remove leading/trailing spaces
2212          * - convert everything to lowercase
2213          * - remove leading "the ", "an " and "a "
2214          * - remove trailing ", the|an|a"
2215          * - remove accents. This step leaves us with CollationKey data,
2216          *   which is not human readable
2217          *
2218          * @param name The artist or album name to convert
2219          * @return The "key" for the given name.
2220          */
keyFor(String name)2221         public static String keyFor(String name) {
2222             if (name != null)  {
2223                 boolean sortfirst = false;
2224                 if (name.equals(UNKNOWN_STRING)) {
2225                     return "\001";
2226                 }
2227                 // Check if the first character is \001. We use this to
2228                 // force sorting of certain special files, like the silent ringtone.
2229                 if (name.startsWith("\001")) {
2230                     sortfirst = true;
2231                 }
2232                 name = name.trim().toLowerCase();
2233                 if (name.startsWith("the ")) {
2234                     name = name.substring(4);
2235                 }
2236                 if (name.startsWith("an ")) {
2237                     name = name.substring(3);
2238                 }
2239                 if (name.startsWith("a ")) {
2240                     name = name.substring(2);
2241                 }
2242                 if (name.endsWith(", the") || name.endsWith(",the") ||
2243                     name.endsWith(", an") || name.endsWith(",an") ||
2244                     name.endsWith(", a") || name.endsWith(",a")) {
2245                     name = name.substring(0, name.lastIndexOf(','));
2246                 }
2247                 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
2248                 if (name.length() > 0) {
2249                     // Insert a separator between the characters to avoid
2250                     // matches on a partial character. If we ever change
2251                     // to start-of-word-only matches, this can be removed.
2252                     StringBuilder b = new StringBuilder();
2253                     b.append('.');
2254                     int nl = name.length();
2255                     for (int i = 0; i < nl; i++) {
2256                         b.append(name.charAt(i));
2257                         b.append('.');
2258                     }
2259                     name = b.toString();
2260                     String key = DatabaseUtils.getCollationKey(name);
2261                     if (sortfirst) {
2262                         key = "\001" + key;
2263                     }
2264                     return key;
2265                } else {
2266                     return "";
2267                 }
2268             }
2269             return null;
2270         }
2271 
2272         public static final class Media implements AudioColumns {
2273             /**
2274              * Get the content:// style URI for the audio media table on the
2275              * given volume.
2276              *
2277              * @param volumeName the name of the volume to get the URI for
2278              * @return the URI to the audio media table on the given volume
2279              */
getContentUri(String volumeName)2280             public static Uri getContentUri(String volumeName) {
2281                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
2282                         .appendPath("media").build();
2283             }
2284 
2285             /** @hide */
getContentUri(@onNull String volumeName, long id)2286             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
2287                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
2288             }
2289 
2290             /**
2291              * Get the content:// style URI for the given audio media file.
2292              *
2293              * @deprecated Apps may not have filesystem permissions to directly
2294              *             access this path.
2295              */
2296             @Deprecated
getContentUriForPath(@onNull String path)2297             public static @Nullable Uri getContentUriForPath(@NonNull String path) {
2298                 return getContentUri(getVolumeName(new File(path)));
2299             }
2300 
2301             /**
2302              * The content:// style URI for the internal storage.
2303              */
2304             public static final Uri INTERNAL_CONTENT_URI =
2305                     getContentUri("internal");
2306 
2307             /**
2308              * The content:// style URI for the "primary" external storage
2309              * volume.
2310              */
2311             public static final Uri EXTERNAL_CONTENT_URI =
2312                     getContentUri("external");
2313 
2314             /**
2315              * The MIME type for this table.
2316              */
2317             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
2318 
2319             /**
2320              * The MIME type for an audio track.
2321              */
2322             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
2323 
2324             /**
2325              * The default sort order for this table
2326              */
2327             public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
2328 
2329             /**
2330              * Activity Action: Start SoundRecorder application.
2331              * <p>Input: nothing.
2332              * <p>Output: An uri to the recorded sound stored in the Media Library
2333              * if the recording was successful.
2334              * May also contain the extra EXTRA_MAX_BYTES.
2335              * @see #EXTRA_MAX_BYTES
2336              */
2337             @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
2338             public static final String RECORD_SOUND_ACTION =
2339                     "android.provider.MediaStore.RECORD_SOUND";
2340 
2341             /**
2342              * The name of the Intent-extra used to define a maximum file size for
2343              * a recording made by the SoundRecorder application.
2344              *
2345              * @see #RECORD_SOUND_ACTION
2346              */
2347              public static final String EXTRA_MAX_BYTES =
2348                     "android.provider.MediaStore.extra.MAX_BYTES";
2349         }
2350 
2351         /**
2352          * Audio genre metadata columns.
2353          */
2354         public interface GenresColumns {
2355             /**
2356              * The name of the genre
2357              */
2358             @Column(Cursor.FIELD_TYPE_STRING)
2359             public static final String NAME = "name";
2360         }
2361 
2362         /**
2363          * Contains all genres for audio files
2364          */
2365         public static final class Genres implements BaseColumns, GenresColumns {
2366             /**
2367              * Get the content:// style URI for the audio genres table on the
2368              * given volume.
2369              *
2370              * @param volumeName the name of the volume to get the URI for
2371              * @return the URI to the audio genres table on the given volume
2372              */
getContentUri(String volumeName)2373             public static Uri getContentUri(String volumeName) {
2374                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
2375                         .appendPath("genres").build();
2376             }
2377 
2378             /**
2379              * Get the content:// style URI for querying the genres of an audio file.
2380              *
2381              * @param volumeName the name of the volume to get the URI for
2382              * @param audioId the ID of the audio file for which to retrieve the genres
2383              * @return the URI to for querying the genres for the audio file
2384              * with the given the volume and audioID
2385              */
getContentUriForAudioId(String volumeName, int audioId)2386             public static Uri getContentUriForAudioId(String volumeName, int audioId) {
2387                 return ContentUris.withAppendedId(Audio.Media.getContentUri(volumeName), audioId)
2388                         .buildUpon().appendPath("genres").build();
2389             }
2390 
2391             /**
2392              * The content:// style URI for the internal storage.
2393              */
2394             public static final Uri INTERNAL_CONTENT_URI =
2395                     getContentUri("internal");
2396 
2397             /**
2398              * The content:// style URI for the "primary" external storage
2399              * volume.
2400              */
2401             public static final Uri EXTERNAL_CONTENT_URI =
2402                     getContentUri("external");
2403 
2404             /**
2405              * The MIME type for this table.
2406              */
2407             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
2408 
2409             /**
2410              * The MIME type for entries in this table.
2411              */
2412             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
2413 
2414             /**
2415              * The default sort order for this table
2416              */
2417             public static final String DEFAULT_SORT_ORDER = NAME;
2418 
2419             /**
2420              * Sub-directory of each genre containing all members.
2421              */
2422             public static final class Members implements AudioColumns {
2423 
getContentUri(String volumeName, long genreId)2424                 public static final Uri getContentUri(String volumeName, long genreId) {
2425                     return ContentUris
2426                             .withAppendedId(Audio.Genres.getContentUri(volumeName), genreId)
2427                             .buildUpon().appendPath("members").build();
2428                 }
2429 
2430                 /**
2431                  * A subdirectory of each genre containing all member audio files.
2432                  */
2433                 public static final String CONTENT_DIRECTORY = "members";
2434 
2435                 /**
2436                  * The default sort order for this table
2437                  */
2438                 public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
2439 
2440                 /**
2441                  * The ID of the audio file
2442                  */
2443                 @Column(Cursor.FIELD_TYPE_INTEGER)
2444                 public static final String AUDIO_ID = "audio_id";
2445 
2446                 /**
2447                  * The ID of the genre
2448                  */
2449                 @Column(Cursor.FIELD_TYPE_INTEGER)
2450                 public static final String GENRE_ID = "genre_id";
2451             }
2452         }
2453 
2454         /**
2455          * Audio playlist metadata columns.
2456          */
2457         public interface PlaylistsColumns {
2458             /**
2459              * The name of the playlist
2460              */
2461             @Column(Cursor.FIELD_TYPE_STRING)
2462             public static final String NAME = "name";
2463 
2464             /**
2465              * Path to the playlist file on disk.
2466              * <p>
2467              * Note that apps may not have filesystem permissions to directly
2468              * access this path. Instead of trying to open this path directly,
2469              * apps should use
2470              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
2471              * access.
2472              *
2473              * @deprecated Apps may not have filesystem permissions to directly
2474              *             access this path. Instead of trying to open this path
2475              *             directly, apps should use
2476              *             {@link ContentResolver#openFileDescriptor(Uri, String)}
2477              *             to gain access.
2478              */
2479             @Deprecated
2480             @Column(Cursor.FIELD_TYPE_STRING)
2481             public static final String DATA = "_data";
2482 
2483             /**
2484              * The time the media item was first added.
2485              */
2486             @CurrentTimeSecondsLong
2487             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2488             public static final String DATE_ADDED = "date_added";
2489 
2490             /**
2491              * The time the media item was last modified.
2492              */
2493             @CurrentTimeSecondsLong
2494             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2495             public static final String DATE_MODIFIED = "date_modified";
2496         }
2497 
2498         /**
2499          * Contains playlists for audio files
2500          */
2501         public static final class Playlists implements BaseColumns,
2502                 PlaylistsColumns {
2503             /**
2504              * Get the content:// style URI for the audio playlists table on the
2505              * given volume.
2506              *
2507              * @param volumeName the name of the volume to get the URI for
2508              * @return the URI to the audio playlists table on the given volume
2509              */
getContentUri(String volumeName)2510             public static Uri getContentUri(String volumeName) {
2511                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
2512                         .appendPath("playlists").build();
2513             }
2514 
2515             /**
2516              * The content:// style URI for the internal storage.
2517              */
2518             public static final Uri INTERNAL_CONTENT_URI =
2519                     getContentUri("internal");
2520 
2521             /**
2522              * The content:// style URI for the "primary" external storage
2523              * volume.
2524              */
2525             public static final Uri EXTERNAL_CONTENT_URI =
2526                     getContentUri("external");
2527 
2528             /**
2529              * The MIME type for this table.
2530              */
2531             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
2532 
2533             /**
2534              * The MIME type for entries in this table.
2535              */
2536             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
2537 
2538             /**
2539              * The default sort order for this table
2540              */
2541             public static final String DEFAULT_SORT_ORDER = NAME;
2542 
2543             /**
2544              * Sub-directory of each playlist containing all members.
2545              */
2546             public static final class Members implements AudioColumns {
getContentUri(String volumeName, long playlistId)2547                 public static final Uri getContentUri(String volumeName, long playlistId) {
2548                     return ContentUris
2549                             .withAppendedId(Audio.Playlists.getContentUri(volumeName), playlistId)
2550                             .buildUpon().appendPath("members").build();
2551                 }
2552 
2553                 /**
2554                  * Convenience method to move a playlist item to a new location
2555                  * @param res The content resolver to use
2556                  * @param playlistId The numeric id of the playlist
2557                  * @param from The position of the item to move
2558                  * @param to The position to move the item to
2559                  * @return true on success
2560                  */
moveItem(ContentResolver res, long playlistId, int from, int to)2561                 public static final boolean moveItem(ContentResolver res,
2562                         long playlistId, int from, int to) {
2563                     Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
2564                             playlistId)
2565                             .buildUpon()
2566                             .appendEncodedPath(String.valueOf(from))
2567                             .appendQueryParameter("move", "true")
2568                             .build();
2569                     ContentValues values = new ContentValues();
2570                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
2571                     return res.update(uri, values, null, null) != 0;
2572                 }
2573 
2574                 /**
2575                  * The ID within the playlist.
2576                  */
2577                 @Column(Cursor.FIELD_TYPE_INTEGER)
2578                 public static final String _ID = "_id";
2579 
2580                 /**
2581                  * A subdirectory of each playlist containing all member audio
2582                  * files.
2583                  */
2584                 public static final String CONTENT_DIRECTORY = "members";
2585 
2586                 /**
2587                  * The ID of the audio file
2588                  */
2589                 @Column(Cursor.FIELD_TYPE_INTEGER)
2590                 public static final String AUDIO_ID = "audio_id";
2591 
2592                 /**
2593                  * The ID of the playlist
2594                  */
2595                 @Column(Cursor.FIELD_TYPE_INTEGER)
2596                 public static final String PLAYLIST_ID = "playlist_id";
2597 
2598                 /**
2599                  * The order of the songs in the playlist
2600                  */
2601                 @Column(Cursor.FIELD_TYPE_INTEGER)
2602                 public static final String PLAY_ORDER = "play_order";
2603 
2604                 /**
2605                  * The default sort order for this table
2606                  */
2607                 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
2608             }
2609         }
2610 
2611         /**
2612          * Audio artist metadata columns.
2613          */
2614         public interface ArtistColumns {
2615             /**
2616              * The artist who created the audio file, if any
2617              */
2618             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2619             public static final String ARTIST = "artist";
2620 
2621             /**
2622              * A non human readable key calculated from the ARTIST, used for
2623              * searching, sorting and grouping
2624              */
2625             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2626             public static final String ARTIST_KEY = "artist_key";
2627 
2628             /**
2629              * The number of albums in the database for this artist
2630              */
2631             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2632             public static final String NUMBER_OF_ALBUMS = "number_of_albums";
2633 
2634             /**
2635              * The number of albums in the database for this artist
2636              */
2637             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2638             public static final String NUMBER_OF_TRACKS = "number_of_tracks";
2639         }
2640 
2641         /**
2642          * Contains artists for audio files
2643          */
2644         public static final class Artists implements BaseColumns, ArtistColumns {
2645             /**
2646              * Get the content:// style URI for the artists table on the
2647              * given volume.
2648              *
2649              * @param volumeName the name of the volume to get the URI for
2650              * @return the URI to the audio artists table on the given volume
2651              */
getContentUri(String volumeName)2652             public static Uri getContentUri(String volumeName) {
2653                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
2654                         .appendPath("artists").build();
2655             }
2656 
2657             /**
2658              * The content:// style URI for the internal storage.
2659              */
2660             public static final Uri INTERNAL_CONTENT_URI =
2661                     getContentUri("internal");
2662 
2663             /**
2664              * The content:// style URI for the "primary" external storage
2665              * volume.
2666              */
2667             public static final Uri EXTERNAL_CONTENT_URI =
2668                     getContentUri("external");
2669 
2670             /**
2671              * The MIME type for this table.
2672              */
2673             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
2674 
2675             /**
2676              * The MIME type for entries in this table.
2677              */
2678             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
2679 
2680             /**
2681              * The default sort order for this table
2682              */
2683             public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
2684 
2685             /**
2686              * Sub-directory of each artist containing all albums on which
2687              * a song by the artist appears.
2688              */
2689             public static final class Albums implements AlbumColumns {
getContentUri(String volumeName,long artistId)2690                 public static final Uri getContentUri(String volumeName,long artistId) {
2691                     return ContentUris
2692                             .withAppendedId(Audio.Artists.getContentUri(volumeName), artistId)
2693                             .buildUpon().appendPath("albums").build();
2694                 }
2695             }
2696         }
2697 
2698         /**
2699          * Audio album metadata columns.
2700          */
2701         public interface AlbumColumns {
2702 
2703             /**
2704              * The id for the album
2705              */
2706             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2707             public static final String ALBUM_ID = "album_id";
2708 
2709             /**
2710              * The album on which the audio file appears, if any
2711              */
2712             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2713             public static final String ALBUM = "album";
2714 
2715             /**
2716              * The ID of the artist whose songs appear on this album.
2717              */
2718             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2719             public static final String ARTIST_ID = "artist_id";
2720 
2721             /**
2722              * The name of the artist whose songs appear on this album.
2723              */
2724             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2725             public static final String ARTIST = "artist";
2726 
2727             /**
2728              * The number of songs on this album
2729              */
2730             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2731             public static final String NUMBER_OF_SONGS = "numsongs";
2732 
2733             /**
2734              * This column is available when getting album info via artist,
2735              * and indicates the number of songs on the album by the given
2736              * artist.
2737              */
2738             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2739             public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
2740 
2741             /**
2742              * The year in which the earliest songs
2743              * on this album were released. This will often
2744              * be the same as {@link #LAST_YEAR}, but for compilation albums
2745              * they might differ.
2746              */
2747             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2748             public static final String FIRST_YEAR = "minyear";
2749 
2750             /**
2751              * The year in which the latest songs
2752              * on this album were released. This will often
2753              * be the same as {@link #FIRST_YEAR}, but for compilation albums
2754              * they might differ.
2755              */
2756             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
2757             public static final String LAST_YEAR = "maxyear";
2758 
2759             /**
2760              * A non human readable key calculated from the ALBUM, used for
2761              * searching, sorting and grouping
2762              */
2763             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2764             public static final String ALBUM_KEY = "album_key";
2765 
2766             /**
2767              * Cached album art.
2768              *
2769              * @deprecated Apps may not have filesystem permissions to directly
2770              *             access this path. Instead of trying to open this path
2771              *             directly, apps should use
2772              *             {@link ContentResolver#loadThumbnail}
2773              *             to gain access.
2774              */
2775             @Deprecated
2776             @Column(Cursor.FIELD_TYPE_STRING)
2777             public static final String ALBUM_ART = "album_art";
2778         }
2779 
2780         /**
2781          * Contains artists for audio files
2782          */
2783         public static final class Albums implements BaseColumns, AlbumColumns {
2784             /**
2785              * Get the content:// style URI for the albums table on the
2786              * given volume.
2787              *
2788              * @param volumeName the name of the volume to get the URI for
2789              * @return the URI to the audio albums table on the given volume
2790              */
getContentUri(String volumeName)2791             public static Uri getContentUri(String volumeName) {
2792                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
2793                         .appendPath("albums").build();
2794             }
2795 
2796             /**
2797              * The content:// style URI for the internal storage.
2798              */
2799             public static final Uri INTERNAL_CONTENT_URI =
2800                     getContentUri("internal");
2801 
2802             /**
2803              * The content:// style URI for the "primary" external storage
2804              * volume.
2805              */
2806             public static final Uri EXTERNAL_CONTENT_URI =
2807                     getContentUri("external");
2808 
2809             /**
2810              * The MIME type for this table.
2811              */
2812             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
2813 
2814             /**
2815              * The MIME type for entries in this table.
2816              */
2817             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
2818 
2819             /**
2820              * The default sort order for this table
2821              */
2822             public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
2823         }
2824 
2825         public static final class Radio {
2826             /**
2827              * The MIME type for entries in this table.
2828              */
2829             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
2830 
2831             // Not instantiable.
Radio()2832             private Radio() { }
2833         }
2834 
2835         /**
2836          * This class provides utility methods to obtain thumbnails for various
2837          * {@link Audio} items.
2838          *
2839          * @deprecated Callers should migrate to using
2840          *             {@link ContentResolver#loadThumbnail}, since it offers
2841          *             richer control over requested thumbnail sizes and
2842          *             cancellation behavior.
2843          * @hide
2844          */
2845         @Deprecated
2846         public static class Thumbnails implements BaseColumns {
2847             /**
2848              * Path to the thumbnail file on disk.
2849              * <p>
2850              * Note that apps may not have filesystem permissions to directly
2851              * access this path. Instead of trying to open this path directly,
2852              * apps should use
2853              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
2854              * access.
2855              *
2856              * @deprecated Apps may not have filesystem permissions to directly
2857              *             access this path. Instead of trying to open this path
2858              *             directly, apps should use
2859              *             {@link ContentResolver#loadThumbnail}
2860              *             to gain access.
2861              */
2862             @Deprecated
2863             @Column(Cursor.FIELD_TYPE_STRING)
2864             public static final String DATA = "_data";
2865 
2866             @Column(Cursor.FIELD_TYPE_INTEGER)
2867             public static final String ALBUM_ID = "album_id";
2868         }
2869     }
2870 
2871     /**
2872      * Collection of all media with MIME type of {@code video/*}.
2873      */
2874     public static final class Video {
2875 
2876         /**
2877          * The default sort order for this table.
2878          */
2879         public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
2880 
2881         /**
2882          * @deprecated all queries should be performed through
2883          *             {@link ContentResolver} directly, which offers modern
2884          *             features like {@link CancellationSignal}.
2885          */
2886         @Deprecated
query(ContentResolver cr, Uri uri, String[] projection)2887         public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
2888             return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
2889         }
2890 
2891         /**
2892          * Video metadata columns.
2893          */
2894         public interface VideoColumns extends MediaColumns {
2895             /** @removed promoted to parent interface */
2896             public static final String DURATION = "duration";
2897 
2898             /**
2899              * The artist who created the video file, if any
2900              */
2901             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2902             public static final String ARTIST = "artist";
2903 
2904             /**
2905              * The album the video file is from, if any
2906              */
2907             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2908             public static final String ALBUM = "album";
2909 
2910             /**
2911              * The resolution of the video file, formatted as "XxY"
2912              */
2913             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2914             public static final String RESOLUTION = "resolution";
2915 
2916             /**
2917              * The description of the video recording
2918              */
2919             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
2920             public static final String DESCRIPTION = "description";
2921 
2922             /**
2923              * Whether the video should be published as public or private
2924              */
2925             @Column(Cursor.FIELD_TYPE_INTEGER)
2926             public static final String IS_PRIVATE = "isprivate";
2927 
2928             /**
2929              * The user-added tags associated with a video
2930              */
2931             @Column(Cursor.FIELD_TYPE_STRING)
2932             public static final String TAGS = "tags";
2933 
2934             /**
2935              * The YouTube category of the video
2936              */
2937             @Column(Cursor.FIELD_TYPE_STRING)
2938             public static final String CATEGORY = "category";
2939 
2940             /**
2941              * The language of the video
2942              */
2943             @Column(Cursor.FIELD_TYPE_STRING)
2944             public static final String LANGUAGE = "language";
2945 
2946             /**
2947              * The latitude where the video was captured.
2948              *
2949              * @deprecated location details are no longer indexed for privacy
2950              *             reasons, and this value is now always {@code null}.
2951              *             You can still manually obtain location metadata using
2952              *             {@link ExifInterface#getLatLong(float[])}.
2953              */
2954             @Deprecated
2955             @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
2956             public static final String LATITUDE = "latitude";
2957 
2958             /**
2959              * The longitude where the video was captured.
2960              *
2961              * @deprecated location details are no longer indexed for privacy
2962              *             reasons, and this value is now always {@code null}.
2963              *             You can still manually obtain location metadata using
2964              *             {@link ExifInterface#getLatLong(float[])}.
2965              */
2966             @Deprecated
2967             @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
2968             public static final String LONGITUDE = "longitude";
2969 
2970             /** @removed promoted to parent interface */
2971             public static final String DATE_TAKEN = "datetaken";
2972 
2973             /**
2974              * The mini thumb id.
2975              *
2976              * @deprecated all thumbnails should be obtained via
2977              *             {@link MediaStore.Images.Thumbnails#getThumbnail}, as this
2978              *             value is no longer supported.
2979              */
2980             @Deprecated
2981             @Column(Cursor.FIELD_TYPE_INTEGER)
2982             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
2983 
2984             /** @removed promoted to parent interface */
2985             public static final String BUCKET_ID = "bucket_id";
2986             /** @removed promoted to parent interface */
2987             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
2988             /** @removed promoted to parent interface */
2989             public static final String GROUP_ID = "group_id";
2990 
2991             /**
2992              * The position within the video item at which playback should be
2993              * resumed.
2994              */
2995             @DurationMillisLong
2996             @Column(Cursor.FIELD_TYPE_INTEGER)
2997             public static final String BOOKMARK = "bookmark";
2998 
2999             /**
3000              * The standard of color aspects
3001              * @hide
3002              */
3003             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
3004             public static final String COLOR_STANDARD = "color_standard";
3005 
3006             /**
3007              * The transfer of color aspects
3008              * @hide
3009              */
3010             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
3011             public static final String COLOR_TRANSFER = "color_transfer";
3012 
3013             /**
3014              * The range of color aspects
3015              * @hide
3016              */
3017             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
3018             public static final String COLOR_RANGE = "color_range";
3019         }
3020 
3021         public static final class Media implements VideoColumns {
3022             /**
3023              * Get the content:// style URI for the video media table on the
3024              * given volume.
3025              *
3026              * @param volumeName the name of the volume to get the URI for
3027              * @return the URI to the video media table on the given volume
3028              */
getContentUri(String volumeName)3029             public static Uri getContentUri(String volumeName) {
3030                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video")
3031                         .appendPath("media").build();
3032             }
3033 
3034             /** @hide */
getContentUri(@onNull String volumeName, long id)3035             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
3036                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
3037             }
3038 
3039             /**
3040              * The content:// style URI for the internal storage.
3041              */
3042             public static final Uri INTERNAL_CONTENT_URI =
3043                     getContentUri("internal");
3044 
3045             /**
3046              * The content:// style URI for the "primary" external storage
3047              * volume.
3048              */
3049             public static final Uri EXTERNAL_CONTENT_URI =
3050                     getContentUri("external");
3051 
3052             /**
3053              * The MIME type for this table.
3054              */
3055             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
3056 
3057             /**
3058              * The default sort order for this table
3059              */
3060             public static final String DEFAULT_SORT_ORDER = TITLE;
3061         }
3062 
3063         /**
3064          * This class provides utility methods to obtain thumbnails for various
3065          * {@link Video} items.
3066          *
3067          * @deprecated Callers should migrate to using
3068          *             {@link ContentResolver#loadThumbnail}, since it offers
3069          *             richer control over requested thumbnail sizes and
3070          *             cancellation behavior.
3071          */
3072         @Deprecated
3073         public static class Thumbnails implements BaseColumns {
3074             /**
3075              * Cancel any outstanding {@link #getThumbnail} requests, causing
3076              * them to return by throwing a {@link OperationCanceledException}.
3077              * <p>
3078              * This method has no effect on
3079              * {@link ContentResolver#loadThumbnail} calls, since they provide
3080              * their own {@link CancellationSignal}.
3081              *
3082              * @deprecated Callers should migrate to using
3083              *             {@link ContentResolver#loadThumbnail}, since it
3084              *             offers richer control over requested thumbnail sizes
3085              *             and cancellation behavior.
3086              */
3087             @Deprecated
cancelThumbnailRequest(ContentResolver cr, long origId)3088             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
3089                 final Uri uri = ContentUris.withAppendedId(
3090                         Video.Media.EXTERNAL_CONTENT_URI, origId);
3091                 InternalThumbnails.cancelThumbnail(cr, uri);
3092             }
3093 
3094             /**
3095              * Return thumbnail representing a specific video item. If a
3096              * thumbnail doesn't exist, this method will block until it's
3097              * generated. Callers are responsible for their own in-memory
3098              * caching of returned values.
3099              *
3100              * @param videoId the video item to obtain a thumbnail for.
3101              * @param kind optimal thumbnail size desired.
3102              * @return decoded thumbnail, or {@code null} if problem was
3103              *         encountered.
3104              * @deprecated Callers should migrate to using
3105              *             {@link ContentResolver#loadThumbnail}, since it
3106              *             offers richer control over requested thumbnail sizes
3107              *             and cancellation behavior.
3108              */
3109             @Deprecated
getThumbnail(ContentResolver cr, long videoId, int kind, BitmapFactory.Options options)3110             public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind,
3111                     BitmapFactory.Options options) {
3112                 final Uri uri = ContentUris.withAppendedId(
3113                         Video.Media.EXTERNAL_CONTENT_URI, videoId);
3114                 return InternalThumbnails.getThumbnail(cr, uri, kind, options);
3115             }
3116 
3117             /**
3118              * Cancel any outstanding {@link #getThumbnail} requests, causing
3119              * them to return by throwing a {@link OperationCanceledException}.
3120              * <p>
3121              * This method has no effect on
3122              * {@link ContentResolver#loadThumbnail} calls, since they provide
3123              * their own {@link CancellationSignal}.
3124              *
3125              * @deprecated Callers should migrate to using
3126              *             {@link ContentResolver#loadThumbnail}, since it
3127              *             offers richer control over requested thumbnail sizes
3128              *             and cancellation behavior.
3129              */
3130             @Deprecated
cancelThumbnailRequest(ContentResolver cr, long videoId, long groupId)3131             public static void cancelThumbnailRequest(ContentResolver cr, long videoId,
3132                     long groupId) {
3133                 cancelThumbnailRequest(cr, videoId);
3134             }
3135 
3136             /**
3137              * Return thumbnail representing a specific video item. If a
3138              * thumbnail doesn't exist, this method will block until it's
3139              * generated. Callers are responsible for their own in-memory
3140              * caching of returned values.
3141              *
3142              * @param videoId the video item to obtain a thumbnail for.
3143              * @param kind optimal thumbnail size desired.
3144              * @return decoded thumbnail, or {@code null} if problem was
3145              *         encountered.
3146              * @deprecated Callers should migrate to using
3147              *             {@link ContentResolver#loadThumbnail}, since it
3148              *             offers richer control over requested thumbnail sizes
3149              *             and cancellation behavior.
3150              */
3151             @Deprecated
getThumbnail(ContentResolver cr, long videoId, long groupId, int kind, BitmapFactory.Options options)3152             public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId,
3153                     int kind, BitmapFactory.Options options) {
3154                 return getThumbnail(cr, videoId, kind, options);
3155             }
3156 
3157             /**
3158              * Get the content:// style URI for the image media table on the
3159              * given volume.
3160              *
3161              * @param volumeName the name of the volume to get the URI for
3162              * @return the URI to the image media table on the given volume
3163              */
getContentUri(String volumeName)3164             public static Uri getContentUri(String volumeName) {
3165                 return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video")
3166                         .appendPath("thumbnails").build();
3167             }
3168 
3169             /**
3170              * The content:// style URI for the internal storage.
3171              */
3172             public static final Uri INTERNAL_CONTENT_URI =
3173                     getContentUri("internal");
3174 
3175             /**
3176              * The content:// style URI for the "primary" external storage
3177              * volume.
3178              */
3179             public static final Uri EXTERNAL_CONTENT_URI =
3180                     getContentUri("external");
3181 
3182             /**
3183              * The default sort order for this table
3184              */
3185             public static final String DEFAULT_SORT_ORDER = "video_id ASC";
3186 
3187             /**
3188              * Path to the thumbnail file on disk.
3189              *
3190              * @deprecated Apps may not have filesystem permissions to directly
3191              *             access this path. Instead of trying to open this path
3192              *             directly, apps should use
3193              *             {@link ContentResolver#openFileDescriptor(Uri, String)}
3194              *             to gain access.
3195              */
3196             @Deprecated
3197             @Column(Cursor.FIELD_TYPE_STRING)
3198             public static final String DATA = "_data";
3199 
3200             /**
3201              * The original image for the thumbnal
3202              */
3203             @Column(Cursor.FIELD_TYPE_INTEGER)
3204             public static final String VIDEO_ID = "video_id";
3205 
3206             /**
3207              * The kind of the thumbnail
3208              */
3209             @Column(Cursor.FIELD_TYPE_INTEGER)
3210             public static final String KIND = "kind";
3211 
3212             public static final int MINI_KIND = ThumbnailConstants.MINI_KIND;
3213             public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND;
3214             public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
3215 
3216             /**
3217              * The width of the thumbnal
3218              */
3219             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
3220             public static final String WIDTH = "width";
3221 
3222             /**
3223              * The height of the thumbnail
3224              */
3225             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
3226             public static final String HEIGHT = "height";
3227         }
3228     }
3229 
3230     /** @removed */
3231     @Deprecated
getAllVolumeNames(@onNull Context context)3232     public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) {
3233         return getExternalVolumeNames(context);
3234     }
3235 
3236     /**
3237      * Return list of all specific volume names that make up
3238      * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each
3239      * shared storage device that is currently attached, which typically
3240      * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}.
3241      * <p>
3242      * Each specific volume name can be passed to APIs like
3243      * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
3244      * media on that storage device.
3245      */
getExternalVolumeNames(@onNull Context context)3246     public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
3247         final StorageManager sm = context.getSystemService(StorageManager.class);
3248         final Set<String> volumeNames = new ArraySet<>();
3249         for (VolumeInfo vi : sm.getVolumes()) {
3250             if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
3251                 if (vi.isPrimary()) {
3252                     volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
3253                 } else {
3254                     volumeNames.add(vi.getNormalizedFsUuid());
3255                 }
3256             }
3257         }
3258         return volumeNames;
3259     }
3260 
3261     /**
3262      * Return the volume name that the given {@link Uri} references.
3263      */
getVolumeName(@onNull Uri uri)3264     public static @NonNull String getVolumeName(@NonNull Uri uri) {
3265         final List<String> segments = uri.getPathSegments();
3266         if (uri.getAuthority().equals(AUTHORITY) && segments != null && segments.size() > 0) {
3267             return segments.get(0);
3268         } else {
3269             throw new IllegalArgumentException("Missing volume name: " + uri);
3270         }
3271     }
3272 
3273     /** {@hide} */
checkArgumentVolumeName(@onNull String volumeName)3274     public static @NonNull String checkArgumentVolumeName(@NonNull String volumeName) {
3275         if (TextUtils.isEmpty(volumeName)) {
3276             throw new IllegalArgumentException();
3277         }
3278 
3279         if (VOLUME_INTERNAL.equals(volumeName)) {
3280             return volumeName;
3281         } else if (VOLUME_EXTERNAL.equals(volumeName)) {
3282             return volumeName;
3283         } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) {
3284             return volumeName;
3285         }
3286 
3287         // When not one of the well-known values above, it must be a hex UUID
3288         for (int i = 0; i < volumeName.length(); i++) {
3289             final char c = volumeName.charAt(i);
3290             if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
3291                 continue;
3292             } else {
3293                 throw new IllegalArgumentException("Invalid volume name: " + volumeName);
3294             }
3295         }
3296         return volumeName;
3297     }
3298 
3299     /**
3300      * Return path where the given specific volume is mounted. Not valid for
3301      * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
3302      * broad collections that cover many paths.
3303      *
3304      * @hide
3305      */
3306     @TestApi
getVolumePath(@onNull String volumeName)3307     public static @NonNull File getVolumePath(@NonNull String volumeName)
3308             throws FileNotFoundException {
3309         final StorageManager sm = AppGlobals.getInitialApplication()
3310                 .getSystemService(StorageManager.class);
3311         return getVolumePath(sm.getVolumes(), volumeName);
3312     }
3313 
3314     /** {@hide} */
getVolumePath(@onNull List<VolumeInfo> volumes, @NonNull String volumeName)3315     public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
3316             @NonNull String volumeName) throws FileNotFoundException {
3317         if (TextUtils.isEmpty(volumeName)) {
3318             throw new IllegalArgumentException();
3319         }
3320 
3321         switch (volumeName) {
3322             case VOLUME_INTERNAL:
3323             case VOLUME_EXTERNAL:
3324                 throw new FileNotFoundException(volumeName + " has no associated path");
3325         }
3326 
3327         final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
3328         for (VolumeInfo volume : volumes) {
3329             final boolean matchPrimary = wantPrimary
3330                     && volume.isPrimary();
3331             final boolean matchSecondary = !wantPrimary
3332                     && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
3333             if (matchPrimary || matchSecondary) {
3334                 final File path = volume.getPathForUser(UserHandle.myUserId());
3335                 if (path != null) {
3336                     return path;
3337                 }
3338             }
3339         }
3340         throw new FileNotFoundException("Failed to find path for " + volumeName);
3341     }
3342 
3343     /**
3344      * Return paths that should be scanned for the given volume.
3345      *
3346      * @hide
3347      */
3348     @TestApi
getVolumeScanPaths(@onNull String volumeName)3349     public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
3350             throws FileNotFoundException {
3351         if (TextUtils.isEmpty(volumeName)) {
3352             throw new IllegalArgumentException();
3353         }
3354 
3355         final Context context = AppGlobals.getInitialApplication();
3356         final UserManager um = context.getSystemService(UserManager.class);
3357 
3358         final ArrayList<File> res = new ArrayList<>();
3359         if (VOLUME_INTERNAL.equals(volumeName)) {
3360             addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
3361             addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
3362             addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
3363         } else if (VOLUME_EXTERNAL.equals(volumeName)) {
3364             for (String exactVolume : getExternalVolumeNames(context)) {
3365                 addCanonicalFile(res, getVolumePath(exactVolume));
3366             }
3367             if (um.isDemoUser()) {
3368                 addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
3369             }
3370         } else {
3371             addCanonicalFile(res, getVolumePath(volumeName));
3372             if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
3373                 addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
3374             }
3375         }
3376         return res;
3377     }
3378 
addCanonicalFile(List<File> list, File file)3379     private static void addCanonicalFile(List<File> list, File file) {
3380         try {
3381             list.add(file.getCanonicalFile());
3382         } catch (IOException e) {
3383             Log.w(TAG, "Failed to resolve " + file + ": " + e);
3384             list.add(file);
3385         }
3386     }
3387 
3388     /**
3389      * Uri for querying the state of the media scanner.
3390      */
getMediaScannerUri()3391     public static Uri getMediaScannerUri() {
3392         return AUTHORITY_URI.buildUpon().appendPath("none").appendPath("media_scanner").build();
3393     }
3394 
3395     /**
3396      * Name of current volume being scanned by the media scanner.
3397      */
3398     public static final String MEDIA_SCANNER_VOLUME = "volume";
3399 
3400     /**
3401      * Name of the file signaling the media scanner to ignore media in the containing directory
3402      * and its subdirectories. Developers should use this to avoid application graphics showing
3403      * up in the Gallery and likewise prevent application sounds and music from showing up in
3404      * the Music app.
3405      */
3406     public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
3407 
3408     /**
3409      * Return an opaque version string describing the {@link MediaStore} state.
3410      * <p>
3411      * Applications that import data from {@link MediaStore} into their own
3412      * caches can use this to detect that {@link MediaStore} has undergone
3413      * substantial changes, and that data should be rescanned.
3414      * <p>
3415      * No other assumptions should be made about the meaning of the version.
3416      * <p>
3417      * This method returns the version for
3418      * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a
3419      * different volume, use {@link #getVersion(Context, String)}.
3420      */
getVersion(@onNull Context context)3421     public static @NonNull String getVersion(@NonNull Context context) {
3422         return getVersion(context, VOLUME_EXTERNAL_PRIMARY);
3423     }
3424 
3425     /**
3426      * Return an opaque version string describing the {@link MediaStore} state.
3427      * <p>
3428      * Applications that import data from {@link MediaStore} into their own
3429      * caches can use this to detect that {@link MediaStore} has undergone
3430      * substantial changes, and that data should be rescanned.
3431      * <p>
3432      * No other assumptions should be made about the meaning of the version.
3433      *
3434      * @param volumeName specific volume to obtain an opaque version string for.
3435      *            Must be one of the values returned from
3436      *            {@link #getExternalVolumeNames(Context)}.
3437      */
getVersion(@onNull Context context, @NonNull String volumeName)3438     public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) {
3439         final ContentResolver resolver = context.getContentResolver();
3440         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
3441             final Bundle in = new Bundle();
3442             in.putString(Intent.EXTRA_TEXT, volumeName);
3443             final Bundle out = client.call(GET_VERSION_CALL, null, in);
3444             return out.getString(Intent.EXTRA_TEXT);
3445         } catch (RemoteException e) {
3446             throw e.rethrowAsRuntimeException();
3447         }
3448     }
3449 
3450     /**
3451      * Return a {@link DocumentsProvider} Uri that is an equivalent to the given
3452      * {@link MediaStore} Uri.
3453      * <p>
3454      * This allows apps with Storage Access Framework permissions to convert
3455      * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
3456      * to the same underlying item. Note that this method doesn't grant any new
3457      * permissions; callers must already hold permissions obtained with
3458      * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
3459      *
3460      * @param mediaUri The {@link MediaStore} Uri to convert.
3461      * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null}
3462      *         if no equivalent was found.
3463      * @see #getMediaUri(Context, Uri)
3464      */
getDocumentUri(@onNull Context context, @NonNull Uri mediaUri)3465     public static @Nullable Uri getDocumentUri(@NonNull Context context, @NonNull Uri mediaUri) {
3466         final ContentResolver resolver = context.getContentResolver();
3467         final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
3468 
3469         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
3470             final Bundle in = new Bundle();
3471             in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
3472             in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
3473             final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
3474             return out.getParcelable(DocumentsContract.EXTRA_URI);
3475         } catch (RemoteException e) {
3476             throw e.rethrowAsRuntimeException();
3477         }
3478     }
3479 
3480     /**
3481      * Return a {@link MediaStore} Uri that is an equivalent to the given
3482      * {@link DocumentsProvider} Uri.
3483      * <p>
3484      * This allows apps with Storage Access Framework permissions to convert
3485      * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
3486      * to the same underlying item. Note that this method doesn't grant any new
3487      * permissions; callers must already hold permissions obtained with
3488      * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
3489      *
3490      * @param documentUri The {@link DocumentsProvider} Uri to convert.
3491      * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no
3492      *         equivalent was found.
3493      * @see #getDocumentUri(Context, Uri)
3494      */
getMediaUri(@onNull Context context, @NonNull Uri documentUri)3495     public static @Nullable Uri getMediaUri(@NonNull Context context, @NonNull Uri documentUri) {
3496         final ContentResolver resolver = context.getContentResolver();
3497         final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
3498 
3499         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
3500             final Bundle in = new Bundle();
3501             in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
3502             in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
3503             final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
3504             return out.getParcelable(DocumentsContract.EXTRA_URI);
3505         } catch (RemoteException e) {
3506             throw e.rethrowAsRuntimeException();
3507         }
3508     }
3509 
3510     /**
3511      * Calculate size of media contributed by given package under the calling
3512      * user. The meaning of "contributed" means it won't automatically be
3513      * deleted when the app is uninstalled.
3514      *
3515      * @hide
3516      */
3517     @TestApi
3518     @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
getContributedMediaSize(Context context, String packageName, UserHandle user)3519     public static @BytesLong long getContributedMediaSize(Context context, String packageName,
3520             UserHandle user) throws IOException {
3521         final UserManager um = context.getSystemService(UserManager.class);
3522         if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
3523             try {
3524                 final ContentResolver resolver = context
3525                         .createPackageContextAsUser(packageName, 0, user).getContentResolver();
3526                 final Bundle in = new Bundle();
3527                 in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
3528                 final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
3529                 return out.getLong(Intent.EXTRA_INDEX);
3530             } catch (Exception e) {
3531                 throw new IOException(e);
3532             }
3533         } else {
3534             throw new IOException("User " + user + " must be unlocked and running");
3535         }
3536     }
3537 
3538     /**
3539      * Delete all media contributed by given package under the calling user. The
3540      * meaning of "contributed" means it won't automatically be deleted when the
3541      * app is uninstalled.
3542      *
3543      * @hide
3544      */
3545     @TestApi
3546     @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
deleteContributedMedia(Context context, String packageName, UserHandle user)3547     public static void deleteContributedMedia(Context context, String packageName,
3548             UserHandle user) throws IOException {
3549         final UserManager um = context.getSystemService(UserManager.class);
3550         if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
3551             try {
3552                 final ContentResolver resolver = context
3553                         .createPackageContextAsUser(packageName, 0, user).getContentResolver();
3554                 final Bundle in = new Bundle();
3555                 in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
3556                 resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
3557             } catch (Exception e) {
3558                 throw new IOException(e);
3559             }
3560         } else {
3561             throw new IOException("User " + user + " must be unlocked and running");
3562         }
3563     }
3564 
3565     /** @hide */
3566     @TestApi
waitForIdle(Context context)3567     public static void waitForIdle(Context context) {
3568         final ContentResolver resolver = context.getContentResolver();
3569         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
3570             client.call(WAIT_FOR_IDLE_CALL, null, null);
3571         } catch (RemoteException e) {
3572             throw e.rethrowAsRuntimeException();
3573         }
3574     }
3575 
3576     /** @hide */
3577     @TestApi
scanFile(Context context, File file)3578     public static Uri scanFile(Context context, File file) {
3579         return scan(context, SCAN_FILE_CALL, file, false);
3580     }
3581 
3582     /** @hide */
3583     @TestApi
scanFileFromShell(Context context, File file)3584     public static Uri scanFileFromShell(Context context, File file) {
3585         return scan(context, SCAN_FILE_CALL, file, true);
3586     }
3587 
3588     /** @hide */
3589     @TestApi
scanVolume(Context context, File file)3590     public static void scanVolume(Context context, File file) {
3591         scan(context, SCAN_VOLUME_CALL, file, false);
3592     }
3593 
3594     /** @hide */
scan(Context context, String method, File file, boolean originatedFromShell)3595     private static Uri scan(Context context, String method, File file,
3596             boolean originatedFromShell) {
3597         final ContentResolver resolver = context.getContentResolver();
3598         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
3599             final Bundle in = new Bundle();
3600             in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
3601             in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
3602             final Bundle out = client.call(method, null, in);
3603             return out.getParcelable(Intent.EXTRA_STREAM);
3604         } catch (RemoteException e) {
3605             throw e.rethrowAsRuntimeException();
3606         }
3607     }
3608 }
3609