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