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.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.content.ContentProviderClient;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.UriPermission;
28 import android.database.Cursor;
29 import android.database.DatabaseUtils;
30 import android.database.sqlite.SQLiteException;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 import android.graphics.Matrix;
34 import android.media.MiniThumbFile;
35 import android.media.ThumbnailUtils;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.os.Environment;
39 import android.os.ParcelFileDescriptor;
40 import android.os.RemoteException;
41 import android.service.media.CameraPrewarmService;
42 import android.util.Log;
43 
44 import libcore.io.IoUtils;
45 
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.FileNotFoundException;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.util.Arrays;
53 import java.util.List;
54 
55 /**
56  * The Media provider contains meta data for all available media on both internal
57  * and external storage devices.
58  */
59 public final class MediaStore {
60     private final static String TAG = "MediaStore";
61 
62     public static final String AUTHORITY = "media";
63 
64     private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
65 
66    /**
67      * Broadcast Action:  A broadcast to indicate the end of an MTP session with the host.
68      * This broadcast is only sent if MTP activity has modified the media database during the
69      * most recent MTP session.
70      *
71      * @hide
72      */
73     public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
74 
75     /**
76      * The method name used by the media scanner and mtp to tell the media provider to
77      * rescan and reclassify that have become unhidden because of renaming folders or
78      * removing nomedia files
79      * @hide
80      */
81     public static final String UNHIDE_CALL = "unhide";
82 
83     /**
84      * This is for internal use by the media scanner only.
85      * Name of the (optional) Uri parameter that determines whether to skip deleting
86      * the file pointed to by the _data column, when deleting the database entry.
87      * The only appropriate value for this parameter is "false", in which case the
88      * delete will be skipped. Note especially that setting this to true, or omitting
89      * the parameter altogether, will perform the default action, which is different
90      * for different types of media.
91      * @hide
92      */
93     public static final String PARAM_DELETE_DATA = "deletedata";
94 
95     /**
96      * Activity Action: Launch a music player.
97      * The activity should be able to play, browse, or manipulate music files stored on the device.
98      *
99      * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead.
100      */
101     @Deprecated
102     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
103     public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
104 
105     /**
106      * Activity Action: Perform a search for media.
107      * Contains at least the {@link android.app.SearchManager#QUERY} extra.
108      * May also contain any combination of the following extras:
109      * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
110      *
111      * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
112      * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
113      * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
114      * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
115      */
116     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
117     public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
118 
119     /**
120      * An intent to perform a search for music media and automatically play content from the
121      * result when possible. This can be fired, for example, by the result of a voice recognition
122      * command to listen to music.
123      * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}
124      * and {@link android.app.SearchManager#QUERY} extras. The
125      * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and
126      * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode.
127      * For more information about the search modes for this intent, see
128      * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based
129      * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common
130      * Intents</a>.</p>
131      *
132      * <p>This intent makes the most sense for apps that can support large-scale search of music,
133      * such as services connected to an online database of music which can be streamed and played
134      * on the device.</p>
135      */
136     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
137     public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
138             "android.media.action.MEDIA_PLAY_FROM_SEARCH";
139 
140     /**
141      * An intent to perform a search for readable media and automatically play content from the
142      * result when possible. This can be fired, for example, by the result of a voice recognition
143      * command to read a book or magazine.
144      * <p>
145      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
146      * contain any type of unstructured text search, like the name of a book or magazine, an author
147      * a genre, a publisher, or any combination of these.
148      * <p>
149      * Because this intent includes an open-ended unstructured search string, it makes the most
150      * sense for apps that can support large-scale search of text media, such as services connected
151      * to an online database of books and/or magazines which can be read on the device.
152      */
153     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
154     public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH =
155             "android.media.action.TEXT_OPEN_FROM_SEARCH";
156 
157     /**
158      * An intent to perform a search for video media and automatically play content from the
159      * result when possible. This can be fired, for example, by the result of a voice recognition
160      * command to play movies.
161      * <p>
162      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
163      * contain any type of unstructured video search, like the name of a movie, one or more actors,
164      * a genre, or any combination of these.
165      * <p>
166      * Because this intent includes an open-ended unstructured search string, it makes the most
167      * sense for apps that can support large-scale search of video, such as services connected to an
168      * online database of videos which can be streamed and played on the device.
169      */
170     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
171     public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH =
172             "android.media.action.VIDEO_PLAY_FROM_SEARCH";
173 
174     /**
175      * The name of the Intent-extra used to define the artist
176      */
177     public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
178     /**
179      * The name of the Intent-extra used to define the album
180      */
181     public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
182     /**
183      * The name of the Intent-extra used to define the song title
184      */
185     public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
186     /**
187      * The name of the Intent-extra used to define the genre.
188      */
189     public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
190     /**
191      * The name of the Intent-extra used to define the playlist.
192      */
193     public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
194     /**
195      * The name of the Intent-extra used to define the radio channel.
196      */
197     public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
198     /**
199      * The name of the Intent-extra used to define the search focus. The search focus
200      * indicates whether the search should be for things related to the artist, album
201      * or song that is identified by the other extras.
202      */
203     public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
204 
205     /**
206      * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView.
207      * This is an int property that overrides the activity's requestedOrientation.
208      * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
209      */
210     public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
211 
212     /**
213      * The name of an Intent-extra used to control the UI of a ViewImage.
214      * This is a boolean property that overrides the activity's default fullscreen state.
215      */
216     public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
217 
218     /**
219      * The name of an Intent-extra used to control the UI of a ViewImage.
220      * This is a boolean property that specifies whether or not to show action icons.
221      */
222     public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
223 
224     /**
225      * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
226      * This is a boolean property that specifies whether or not to finish the MovieView activity
227      * when the movie completes playing. The default value is true, which means to automatically
228      * exit the movie player activity when the movie completes playing.
229      */
230     public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
231 
232     /**
233      * The name of the Intent action used to launch a camera in still image mode.
234      */
235     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
236     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
237 
238     /**
239      * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
240      * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm
241      * service.
242      * <p>
243      * This meta-data should reference the fully qualified class name of the prewarm service
244      * extending {@link CameraPrewarmService}.
245      * <p>
246      * The prewarm service will get bound and receive a prewarm signal
247      * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
248      * An application implementing a prewarm service should do the absolute minimum amount of work
249      * to initialize the camera in order to reduce startup time in likely case that shortly after a
250      * camera launch intent would be sent.
251      */
252     public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE =
253             "android.media.still_image_camera_preview_service";
254 
255     /**
256      * The name of the Intent action used to launch a camera in still image mode
257      * for use when the device is secured (e.g. with a pin, password, pattern,
258      * or face unlock). Applications responding to this intent must not expose
259      * any personal content like existing photos or videos on the device. The
260      * applications should be careful not to share any photo or video with other
261      * applications or internet. The activity should use {@link
262      * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display
263      * on top of the lock screen while secured. There is no activity stack when
264      * this flag is used, so launching more than one activity is strongly
265      * discouraged.
266      */
267     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
268     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
269             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
270 
271     /**
272      * The name of the Intent action used to launch a camera in video mode.
273      */
274     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
275     public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
276 
277     /**
278      * Standard Intent action that can be sent to have the camera application
279      * capture an image and return it.
280      * <p>
281      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
282      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
283      * object in the extra field. This is useful for applications that only need a small image.
284      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
285      * value of EXTRA_OUTPUT.
286      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
287      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
288      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
289      * If you don't set a ClipData, it will be copied there for you when calling
290      * {@link Context#startActivity(Intent)}.
291      *
292      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
293      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
294      * is not granted, then attempting to use this action will result in a {@link
295      * java.lang.SecurityException}.
296      *
297      *  @see #EXTRA_OUTPUT
298      */
299     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
300     public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
301 
302     /**
303      * Intent action that can be sent to have the camera application capture an image and return
304      * it when the device is secured (e.g. with a pin, password, pattern, or face unlock).
305      * Applications responding to this intent must not expose any personal content like existing
306      * photos or videos on the device. The applications should be careful not to share any photo
307      * or video with other applications or internet. The activity should use {@link
308      * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display on top of the
309      * lock screen while secured. There is no activity stack when this flag is used, so
310      * launching more than one activity is strongly discouraged.
311      * <p>
312      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
313      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
314      * object in the extra field. This is useful for applications that only need a small image.
315      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
316      * value of EXTRA_OUTPUT.
317      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
318      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
319      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
320      * If you don't set a ClipData, it will be copied there for you when calling
321      * {@link Context#startActivity(Intent)}.
322      *
323      * @see #ACTION_IMAGE_CAPTURE
324      * @see #EXTRA_OUTPUT
325      */
326     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
327     public static final String ACTION_IMAGE_CAPTURE_SECURE =
328             "android.media.action.IMAGE_CAPTURE_SECURE";
329 
330     /**
331      * Standard Intent action that can be sent to have the camera application
332      * capture a video and return it.
333      * <p>
334      * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
335      * <p>
336      * The caller may pass in an extra EXTRA_OUTPUT to control
337      * where the video is written. If EXTRA_OUTPUT is not present the video will be
338      * written to the standard location for videos, and the Uri of that location will be
339      * returned in the data field of the Uri.
340      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
341      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
342      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
343      * If you don't set a ClipData, it will be copied there for you when calling
344      * {@link Context#startActivity(Intent)}.
345      *
346      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
347      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
348      * is not granted, then atempting to use this action will result in a {@link
349      * java.lang.SecurityException}.
350      *
351      * @see #EXTRA_OUTPUT
352      * @see #EXTRA_VIDEO_QUALITY
353      * @see #EXTRA_SIZE_LIMIT
354      * @see #EXTRA_DURATION_LIMIT
355      */
356     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
357     public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
358 
359     /**
360      * The name of the Intent-extra used to control the quality of a recorded video. This is an
361      * integer property. Currently value 0 means low quality, suitable for MMS messages, and
362      * value 1 means high quality. In the future other quality levels may be added.
363      */
364     public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
365 
366     /**
367      * Specify the maximum allowed size.
368      */
369     public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
370 
371     /**
372      * Specify the maximum allowed recording duration in seconds.
373      */
374     public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
375 
376     /**
377      * The name of the Intent-extra used to indicate a content resolver Uri to be used to
378      * store the requested image or video.
379      */
380     public final static String EXTRA_OUTPUT = "output";
381 
382     /**
383       * The string that is used when a media attribute is not known. For example,
384       * if an audio file does not have any meta data, the artist and album columns
385       * will be set to this value.
386       */
387     public static final String UNKNOWN_STRING = "<unknown>";
388 
389     /**
390      * Common fields for most MediaProvider tables
391      */
392 
393     public interface MediaColumns extends BaseColumns {
394         /**
395          * Path to the file on disk.
396          * <p>
397          * Note that apps may not have filesystem permissions to directly access
398          * this path. Instead of trying to open this path directly, apps should
399          * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
400          * access.
401          * <p>
402          * Type: TEXT
403          */
404         public static final String DATA = "_data";
405 
406         /**
407          * The size of the file in bytes
408          * <P>Type: INTEGER (long)</P>
409          */
410         public static final String SIZE = "_size";
411 
412         /**
413          * The display name of the file
414          * <P>Type: TEXT</P>
415          */
416         public static final String DISPLAY_NAME = "_display_name";
417 
418         /**
419          * The title of the content
420          * <P>Type: TEXT</P>
421          */
422         public static final String TITLE = "title";
423 
424         /**
425          * The time the file was added to the media provider
426          * Units are seconds since 1970.
427          * <P>Type: INTEGER (long)</P>
428          */
429         public static final String DATE_ADDED = "date_added";
430 
431         /**
432          * The time the file was last modified
433          * Units are seconds since 1970.
434          * NOTE: This is for internal use by the media scanner.  Do not modify this field.
435          * <P>Type: INTEGER (long)</P>
436          */
437         public static final String DATE_MODIFIED = "date_modified";
438 
439         /**
440          * The MIME type of the file
441          * <P>Type: TEXT</P>
442          */
443         public static final String MIME_TYPE = "mime_type";
444 
445         /**
446          * The MTP object handle of a newly transfered file.
447          * Used to pass the new file's object handle through the media scanner
448          * from MTP to the media provider
449          * For internal use only by MTP, media scanner and media provider.
450          * <P>Type: INTEGER</P>
451          * @hide
452          */
453         public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id";
454 
455         /**
456          * Non-zero if the media file is drm-protected
457          * <P>Type: INTEGER (boolean)</P>
458          * @hide
459          */
460         public static final String IS_DRM = "is_drm";
461 
462         /**
463          * The width of the image/video in pixels.
464          */
465         public static final String WIDTH = "width";
466 
467         /**
468          * The height of the image/video in pixels.
469          */
470         public static final String HEIGHT = "height";
471      }
472 
473     /**
474      * Media provider table containing an index of all files in the media storage,
475      * including non-media files.  This should be used by applications that work with
476      * non-media file types (text, HTML, PDF, etc) as well as applications that need to
477      * work with multiple media file types in a single query.
478      */
479     public static final class Files {
480 
481         /**
482          * Get the content:// style URI for the files table on the
483          * given volume.
484          *
485          * @param volumeName the name of the volume to get the URI for
486          * @return the URI to the files table on the given volume
487          */
getContentUri(String volumeName)488         public static Uri getContentUri(String volumeName) {
489             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
490                     "/file");
491         }
492 
493         /**
494          * Get the content:// style URI for a single row in the files table on the
495          * given volume.
496          *
497          * @param volumeName the name of the volume to get the URI for
498          * @param rowId the file to get the URI for
499          * @return the URI to the files table on the given volume
500          */
getContentUri(String volumeName, long rowId)501         public static final Uri getContentUri(String volumeName,
502                 long rowId) {
503             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
504                     + "/file/" + rowId);
505         }
506 
507         /**
508          * For use only by the MTP implementation.
509          * @hide
510          */
getMtpObjectsUri(String volumeName)511         public static Uri getMtpObjectsUri(String volumeName) {
512             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
513                     "/object");
514         }
515 
516         /**
517          * For use only by the MTP implementation.
518          * @hide
519          */
getMtpObjectsUri(String volumeName, long fileId)520         public static final Uri getMtpObjectsUri(String volumeName,
521                 long fileId) {
522             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
523                     + "/object/" + fileId);
524         }
525 
526         /**
527          * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
528          * @hide
529          */
getMtpReferencesUri(String volumeName, long fileId)530         public static final Uri getMtpReferencesUri(String volumeName,
531                 long fileId) {
532             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
533                     + "/object/" + fileId + "/references");
534         }
535 
536         /**
537          * Used to trigger special logic for directories.
538          * @hide
539          */
getDirectoryUri(String volumeName)540         public static final Uri getDirectoryUri(String volumeName) {
541             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + "/dir");
542         }
543 
544         /**
545          * Fields for master table for all media files.
546          * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
547          */
548         public interface FileColumns extends MediaColumns {
549             /**
550              * The MTP storage ID of the file
551              * <P>Type: INTEGER</P>
552              * @hide
553              */
554             public static final String STORAGE_ID = "storage_id";
555 
556             /**
557              * The MTP format code of the file
558              * <P>Type: INTEGER</P>
559              * @hide
560              */
561             public static final String FORMAT = "format";
562 
563             /**
564              * The index of the parent directory of the file
565              * <P>Type: INTEGER</P>
566              */
567             public static final String PARENT = "parent";
568 
569             /**
570              * The MIME type of the file
571              * <P>Type: TEXT</P>
572              */
573             public static final String MIME_TYPE = "mime_type";
574 
575             /**
576              * The title of the content
577              * <P>Type: TEXT</P>
578              */
579             public static final String TITLE = "title";
580 
581             /**
582              * The media type (audio, video, image or playlist)
583              * of the file, or 0 for not a media file
584              * <P>Type: TEXT</P>
585              */
586             public static final String MEDIA_TYPE = "media_type";
587 
588             /**
589              * Constant for the {@link #MEDIA_TYPE} column indicating that file
590              * is not an audio, image, video or playlist file.
591              */
592             public static final int MEDIA_TYPE_NONE = 0;
593 
594             /**
595              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an image file.
596              */
597             public static final int MEDIA_TYPE_IMAGE = 1;
598 
599             /**
600              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an audio file.
601              */
602             public static final int MEDIA_TYPE_AUDIO = 2;
603 
604             /**
605              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a video file.
606              */
607             public static final int MEDIA_TYPE_VIDEO = 3;
608 
609             /**
610              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a playlist file.
611              */
612             public static final int MEDIA_TYPE_PLAYLIST = 4;
613         }
614     }
615 
616     /**
617      * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
618      * to be accessed elsewhere.
619      */
620     private static class InternalThumbnails implements BaseColumns {
621         private static final int MINI_KIND = 1;
622         private static final int FULL_SCREEN_KIND = 2;
623         private static final int MICRO_KIND = 3;
624         private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
625         static final int DEFAULT_GROUP_ID = 0;
626         private static final Object sThumbBufLock = new Object();
627         private static byte[] sThumbBuf;
628 
getMiniThumbFromFile( Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options)629         private static Bitmap getMiniThumbFromFile(
630                 Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
631             Bitmap bitmap = null;
632             Uri thumbUri = null;
633             try {
634                 long thumbId = c.getLong(0);
635                 String filePath = c.getString(1);
636                 thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
637                 ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
638                 bitmap = BitmapFactory.decodeFileDescriptor(
639                         pfdInput.getFileDescriptor(), null, options);
640                 pfdInput.close();
641             } catch (FileNotFoundException ex) {
642                 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
643             } catch (IOException ex) {
644                 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
645             } catch (OutOfMemoryError ex) {
646                 Log.e(TAG, "failed to allocate memory for thumbnail "
647                         + thumbUri + "; " + ex);
648             }
649             return bitmap;
650         }
651 
652         /**
653          * This method cancels the thumbnail request so clients waiting for getThumbnail will be
654          * interrupted and return immediately. Only the original process which made the getThumbnail
655          * requests can cancel their own requests.
656          *
657          * @param cr ContentResolver
658          * @param origId original image or video id. use -1 to cancel all requests.
659          * @param groupId the same groupId used in getThumbnail
660          * @param baseUri the base URI of requested thumbnails
661          */
cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri, long groupId)662         static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
663                 long groupId) {
664             Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
665                     .appendQueryParameter("orig_id", String.valueOf(origId))
666                     .appendQueryParameter("group_id", String.valueOf(groupId)).build();
667             Cursor c = null;
668             try {
669                 c = cr.query(cancelUri, PROJECTION, null, null, null);
670             }
671             finally {
672                 if (c != null) c.close();
673             }
674         }
675 
676         /**
677          * This method ensure thumbnails associated with origId are generated and decode the byte
678          * stream from database (MICRO_KIND) or file (MINI_KIND).
679          *
680          * Special optimization has been done to avoid further IPC communication for MICRO_KIND
681          * thumbnails.
682          *
683          * @param cr ContentResolver
684          * @param origId original image or video id
685          * @param kind could be MINI_KIND or MICRO_KIND
686          * @param options this is only used for MINI_KIND when decoding the Bitmap
687          * @param baseUri the base URI of requested thumbnails
688          * @param groupId the id of group to which this request belongs
689          * @return Bitmap bitmap of specified thumbnail kind
690          */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options, Uri baseUri, boolean isVideo)691         static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
692                 BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
693             Bitmap bitmap = null;
694             // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
695             // If the magic is non-zero, we simply return thumbnail if it does exist.
696             // querying MediaProvider and simply return thumbnail.
697             MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI
698                     : Images.Media.EXTERNAL_CONTENT_URI);
699             Cursor c = null;
700             try {
701                 long magic = thumbFile.getMagic(origId);
702                 if (magic != 0) {
703                     if (kind == MICRO_KIND) {
704                         synchronized (sThumbBufLock) {
705                             if (sThumbBuf == null) {
706                                 sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
707                             }
708                             if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
709                                 bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
710                                 if (bitmap == null) {
711                                     Log.w(TAG, "couldn't decode byte array.");
712                                 }
713                             }
714                         }
715                         return bitmap;
716                     } else if (kind == MINI_KIND) {
717                         String column = isVideo ? "video_id=" : "image_id=";
718                         c = cr.query(baseUri, PROJECTION, column + origId, null, null);
719                         if (c != null && c.moveToFirst()) {
720                             bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
721                             if (bitmap != null) {
722                                 return bitmap;
723                             }
724                         }
725                     }
726                 }
727 
728                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
729                         .appendQueryParameter("orig_id", String.valueOf(origId))
730                         .appendQueryParameter("group_id", String.valueOf(groupId)).build();
731                 if (c != null) c.close();
732                 c = cr.query(blockingUri, PROJECTION, null, null, null);
733                 // This happens when original image/video doesn't exist.
734                 if (c == null) return null;
735 
736                 // Assuming thumbnail has been generated, at least original image exists.
737                 if (kind == MICRO_KIND) {
738                     synchronized (sThumbBufLock) {
739                         if (sThumbBuf == null) {
740                             sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
741                         }
742                         Arrays.fill(sThumbBuf, (byte)0);
743                         if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
744                             bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
745                             if (bitmap == null) {
746                                 Log.w(TAG, "couldn't decode byte array.");
747                             }
748                         }
749                     }
750                 } else if (kind == MINI_KIND) {
751                     if (c.moveToFirst()) {
752                         bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
753                     }
754                 } else {
755                     throw new IllegalArgumentException("Unsupported kind: " + kind);
756                 }
757 
758                 // We probably run out of space, so create the thumbnail in memory.
759                 if (bitmap == null) {
760                     Log.v(TAG, "Create the thumbnail in memory: origId=" + origId
761                             + ", kind=" + kind + ", isVideo="+isVideo);
762                     Uri uri = Uri.parse(
763                             baseUri.buildUpon().appendPath(String.valueOf(origId))
764                                     .toString().replaceFirst("thumbnails", "media"));
765                     if (c != null) c.close();
766                     c = cr.query(uri, PROJECTION, null, null, null);
767                     if (c == null || !c.moveToFirst()) {
768                         return null;
769                     }
770                     String filePath = c.getString(1);
771                     if (filePath != null) {
772                         if (isVideo) {
773                             bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
774                         } else {
775                             bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
776                         }
777                     }
778                 }
779             } catch (SQLiteException ex) {
780                 Log.w(TAG, ex);
781             } finally {
782                 if (c != null) c.close();
783                 // To avoid file descriptor leak in application process.
784                 thumbFile.deactivate();
785                 thumbFile = null;
786             }
787             return bitmap;
788         }
789     }
790 
791     /**
792      * Contains meta data for all available images.
793      */
794     public static final class Images {
795         public interface ImageColumns extends MediaColumns {
796             /**
797              * The description of the image
798              * <P>Type: TEXT</P>
799              */
800             public static final String DESCRIPTION = "description";
801 
802             /**
803              * The picasa id of the image
804              * <P>Type: TEXT</P>
805              */
806             public static final String PICASA_ID = "picasa_id";
807 
808             /**
809              * Whether the video should be published as public or private
810              * <P>Type: INTEGER</P>
811              */
812             public static final String IS_PRIVATE = "isprivate";
813 
814             /**
815              * The latitude where the image was captured.
816              * <P>Type: DOUBLE</P>
817              */
818             public static final String LATITUDE = "latitude";
819 
820             /**
821              * The longitude where the image was captured.
822              * <P>Type: DOUBLE</P>
823              */
824             public static final String LONGITUDE = "longitude";
825 
826             /**
827              * The date & time that the image was taken in units
828              * of milliseconds since jan 1, 1970.
829              * <P>Type: INTEGER</P>
830              */
831             public static final String DATE_TAKEN = "datetaken";
832 
833             /**
834              * The orientation for the image expressed as degrees.
835              * Only degrees 0, 90, 180, 270 will work.
836              * <P>Type: INTEGER</P>
837              */
838             public static final String ORIENTATION = "orientation";
839 
840             /**
841              * The mini thumb id.
842              * <P>Type: INTEGER</P>
843              */
844             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
845 
846             /**
847              * The bucket id of the image. This is a read-only property that
848              * is automatically computed from the DATA column.
849              * <P>Type: TEXT</P>
850              */
851             public static final String BUCKET_ID = "bucket_id";
852 
853             /**
854              * The bucket display name of the image. This is a read-only property that
855              * is automatically computed from the DATA column.
856              * <P>Type: TEXT</P>
857              */
858             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
859         }
860 
861         public static final class Media implements ImageColumns {
query(ContentResolver cr, Uri uri, String[] projection)862             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
863                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
864             }
865 
query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)866             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
867                     String where, String orderBy) {
868                 return cr.query(uri, projection, where,
869                                              null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
870             }
871 
query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)872             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
873                     String selection, String [] selectionArgs, String orderBy) {
874                 return cr.query(uri, projection, selection,
875                         selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
876             }
877 
878             /**
879              * Retrieves an image for the given url as a {@link Bitmap}.
880              *
881              * @param cr The content resolver to use
882              * @param url The url of the image
883              * @throws FileNotFoundException
884              * @throws IOException
885              */
getBitmap(ContentResolver cr, Uri url)886             public static final Bitmap getBitmap(ContentResolver cr, Uri url)
887                     throws FileNotFoundException, IOException {
888                 InputStream input = cr.openInputStream(url);
889                 Bitmap bitmap = BitmapFactory.decodeStream(input);
890                 input.close();
891                 return bitmap;
892             }
893 
894             /**
895              * Insert an image and create a thumbnail for it.
896              *
897              * @param cr The content resolver to use
898              * @param imagePath The path to the image to insert
899              * @param name The name of the image
900              * @param description The description of the image
901              * @return The URL to the newly created image
902              * @throws FileNotFoundException
903              */
insertImage(ContentResolver cr, String imagePath, String name, String description)904             public static final String insertImage(ContentResolver cr, String imagePath,
905                     String name, String description) throws FileNotFoundException {
906                 // Check if file exists with a FileInputStream
907                 FileInputStream stream = new FileInputStream(imagePath);
908                 try {
909                     Bitmap bm = BitmapFactory.decodeFile(imagePath);
910                     String ret = insertImage(cr, bm, name, description);
911                     bm.recycle();
912                     return ret;
913                 } finally {
914                     try {
915                         stream.close();
916                     } catch (IOException e) {
917                     }
918                 }
919             }
920 
StoreThumbnail( ContentResolver cr, Bitmap source, long id, float width, float height, int kind)921             private static final Bitmap StoreThumbnail(
922                     ContentResolver cr,
923                     Bitmap source,
924                     long id,
925                     float width, float height,
926                     int kind) {
927                 // create the matrix to scale it
928                 Matrix matrix = new Matrix();
929 
930                 float scaleX = width / source.getWidth();
931                 float scaleY = height / source.getHeight();
932 
933                 matrix.setScale(scaleX, scaleY);
934 
935                 Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
936                                                    source.getWidth(),
937                                                    source.getHeight(), matrix,
938                                                    true);
939 
940                 ContentValues values = new ContentValues(4);
941                 values.put(Images.Thumbnails.KIND,     kind);
942                 values.put(Images.Thumbnails.IMAGE_ID, (int)id);
943                 values.put(Images.Thumbnails.HEIGHT,   thumb.getHeight());
944                 values.put(Images.Thumbnails.WIDTH,    thumb.getWidth());
945 
946                 Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
947 
948                 try {
949                     OutputStream thumbOut = cr.openOutputStream(url);
950 
951                     thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
952                     thumbOut.close();
953                     return thumb;
954                 }
955                 catch (FileNotFoundException ex) {
956                     return null;
957                 }
958                 catch (IOException ex) {
959                     return null;
960                 }
961             }
962 
963             /**
964              * Insert an image and create a thumbnail for it.
965              *
966              * @param cr The content resolver to use
967              * @param source The stream to use for the image
968              * @param title The name of the image
969              * @param description The description of the image
970              * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
971              *              for any reason.
972              */
insertImage(ContentResolver cr, Bitmap source, String title, String description)973             public static final String insertImage(ContentResolver cr, Bitmap source,
974                                                    String title, String description) {
975                 ContentValues values = new ContentValues();
976                 values.put(Images.Media.TITLE, title);
977                 values.put(Images.Media.DESCRIPTION, description);
978                 values.put(Images.Media.MIME_TYPE, "image/jpeg");
979 
980                 Uri url = null;
981                 String stringUrl = null;    /* value to be returned */
982 
983                 try {
984                     url = cr.insert(EXTERNAL_CONTENT_URI, values);
985 
986                     if (source != null) {
987                         OutputStream imageOut = cr.openOutputStream(url);
988                         try {
989                             source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
990                         } finally {
991                             imageOut.close();
992                         }
993 
994                         long id = ContentUris.parseId(url);
995                         // Wait until MINI_KIND thumbnail is generated.
996                         Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id,
997                                 Images.Thumbnails.MINI_KIND, null);
998                         // This is for backward compatibility.
999                         Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F,
1000                                 Images.Thumbnails.MICRO_KIND);
1001                     } else {
1002                         Log.e(TAG, "Failed to create thumbnail, removing original");
1003                         cr.delete(url, null, null);
1004                         url = null;
1005                     }
1006                 } catch (Exception e) {
1007                     Log.e(TAG, "Failed to insert image", e);
1008                     if (url != null) {
1009                         cr.delete(url, null, null);
1010                         url = null;
1011                     }
1012                 }
1013 
1014                 if (url != null) {
1015                     stringUrl = url.toString();
1016                 }
1017 
1018                 return stringUrl;
1019             }
1020 
1021             /**
1022              * Get the content:// style URI for the image media table on the
1023              * given volume.
1024              *
1025              * @param volumeName the name of the volume to get the URI for
1026              * @return the URI to the image media table on the given volume
1027              */
getContentUri(String volumeName)1028             public static Uri getContentUri(String volumeName) {
1029                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1030                         "/images/media");
1031             }
1032 
1033             /**
1034              * The content:// style URI for the internal storage.
1035              */
1036             public static final Uri INTERNAL_CONTENT_URI =
1037                     getContentUri("internal");
1038 
1039             /**
1040              * The content:// style URI for the "primary" external storage
1041              * volume.
1042              */
1043             public static final Uri EXTERNAL_CONTENT_URI =
1044                     getContentUri("external");
1045 
1046             /**
1047              * The MIME type of of this directory of
1048              * images.  Note that each entry in this directory will have a standard
1049              * image MIME type as appropriate -- for example, image/jpeg.
1050              */
1051             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
1052 
1053             /**
1054              * The default sort order for this table
1055              */
1056             public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
1057         }
1058 
1059         /**
1060          * This class allows developers to query and get two kinds of thumbnails:
1061          * MINI_KIND: 512 x 384 thumbnail
1062          * MICRO_KIND: 96 x 96 thumbnail
1063          */
1064         public static class Thumbnails implements BaseColumns {
query(ContentResolver cr, Uri uri, String[] projection)1065             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1066                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1067             }
1068 
queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)1069             public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
1070                     String[] projection) {
1071                 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
1072             }
1073 
queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)1074             public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
1075                     String[] projection) {
1076                 return cr.query(EXTERNAL_CONTENT_URI, projection,
1077                         IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
1078                         kind, null, null);
1079             }
1080 
1081             /**
1082              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1083              * interrupted and return immediately. Only the original process which made the getThumbnail
1084              * requests can cancel their own requests.
1085              *
1086              * @param cr ContentResolver
1087              * @param origId original image id
1088              */
cancelThumbnailRequest(ContentResolver cr, long origId)1089             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
1090                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
1091                         InternalThumbnails.DEFAULT_GROUP_ID);
1092             }
1093 
1094             /**
1095              * This method checks if the thumbnails of the specified image (origId) has been created.
1096              * It will be blocked until the thumbnails are generated.
1097              *
1098              * @param cr ContentResolver used to dispatch queries to MediaProvider.
1099              * @param origId Original image id associated with thumbnail of interest.
1100              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1101              * @param options this is only used for MINI_KIND when decoding the Bitmap
1102              * @return A Bitmap instance. It could be null if the original image
1103              *         associated with origId doesn't exist or memory is not enough.
1104              */
getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options)1105             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
1106                     BitmapFactory.Options options) {
1107                 return InternalThumbnails.getThumbnail(cr, origId,
1108                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
1109                         EXTERNAL_CONTENT_URI, false);
1110             }
1111 
1112             /**
1113              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1114              * interrupted and return immediately. Only the original process which made the getThumbnail
1115              * requests can cancel their own requests.
1116              *
1117              * @param cr ContentResolver
1118              * @param origId original image id
1119              * @param groupId the same groupId used in getThumbnail.
1120              */
cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)1121             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
1122                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
1123             }
1124 
1125             /**
1126              * This method checks if the thumbnails of the specified image (origId) has been created.
1127              * It will be blocked until the thumbnails are generated.
1128              *
1129              * @param cr ContentResolver used to dispatch queries to MediaProvider.
1130              * @param origId Original image id associated with thumbnail of interest.
1131              * @param groupId the id of group to which this request belongs
1132              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1133              * @param options this is only used for MINI_KIND when decoding the Bitmap
1134              * @return A Bitmap instance. It could be null if the original image
1135              *         associated with origId doesn't exist or memory is not enough.
1136              */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options)1137             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
1138                     int kind, BitmapFactory.Options options) {
1139                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
1140                         EXTERNAL_CONTENT_URI, false);
1141             }
1142 
1143             /**
1144              * Get the content:// style URI for the image media table on the
1145              * given volume.
1146              *
1147              * @param volumeName the name of the volume to get the URI for
1148              * @return the URI to the image media table on the given volume
1149              */
getContentUri(String volumeName)1150             public static Uri getContentUri(String volumeName) {
1151                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1152                         "/images/thumbnails");
1153             }
1154 
1155             /**
1156              * The content:// style URI for the internal storage.
1157              */
1158             public static final Uri INTERNAL_CONTENT_URI =
1159                     getContentUri("internal");
1160 
1161             /**
1162              * The content:// style URI for the "primary" external storage
1163              * volume.
1164              */
1165             public static final Uri EXTERNAL_CONTENT_URI =
1166                     getContentUri("external");
1167 
1168             /**
1169              * The default sort order for this table
1170              */
1171             public static final String DEFAULT_SORT_ORDER = "image_id ASC";
1172 
1173             /**
1174              * Path to the thumbnail file on disk.
1175              * <p>
1176              * Note that apps may not have filesystem permissions to directly
1177              * access this path. Instead of trying to open this path directly,
1178              * apps should use
1179              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
1180              * access.
1181              * <p>
1182              * Type: TEXT
1183              */
1184             public static final String DATA = "_data";
1185 
1186             /**
1187              * The original image for the thumbnal
1188              * <P>Type: INTEGER (ID from Images table)</P>
1189              */
1190             public static final String IMAGE_ID = "image_id";
1191 
1192             /**
1193              * The kind of the thumbnail
1194              * <P>Type: INTEGER (One of the values below)</P>
1195              */
1196             public static final String KIND = "kind";
1197 
1198             public static final int MINI_KIND = 1;
1199             public static final int FULL_SCREEN_KIND = 2;
1200             public static final int MICRO_KIND = 3;
1201             /**
1202              * The blob raw data of thumbnail
1203              * <P>Type: DATA STREAM</P>
1204              */
1205             public static final String THUMB_DATA = "thumb_data";
1206 
1207             /**
1208              * The width of the thumbnal
1209              * <P>Type: INTEGER (long)</P>
1210              */
1211             public static final String WIDTH = "width";
1212 
1213             /**
1214              * The height of the thumbnail
1215              * <P>Type: INTEGER (long)</P>
1216              */
1217             public static final String HEIGHT = "height";
1218         }
1219     }
1220 
1221     /**
1222      * Container for all audio content.
1223      */
1224     public static final class Audio {
1225         /**
1226          * Columns for audio file that show up in multiple tables.
1227          */
1228         public interface AudioColumns extends MediaColumns {
1229 
1230             /**
1231              * A non human readable key calculated from the TITLE, used for
1232              * searching, sorting and grouping
1233              * <P>Type: TEXT</P>
1234              */
1235             public static final String TITLE_KEY = "title_key";
1236 
1237             /**
1238              * The duration of the audio file, in ms
1239              * <P>Type: INTEGER (long)</P>
1240              */
1241             public static final String DURATION = "duration";
1242 
1243             /**
1244              * The position, in ms, playback was at when playback for this file
1245              * was last stopped.
1246              * <P>Type: INTEGER (long)</P>
1247              */
1248             public static final String BOOKMARK = "bookmark";
1249 
1250             /**
1251              * The id of the artist who created the audio file, if any
1252              * <P>Type: INTEGER (long)</P>
1253              */
1254             public static final String ARTIST_ID = "artist_id";
1255 
1256             /**
1257              * The artist who created the audio file, if any
1258              * <P>Type: TEXT</P>
1259              */
1260             public static final String ARTIST = "artist";
1261 
1262             /**
1263              * The artist credited for the album that contains the audio file
1264              * <P>Type: TEXT</P>
1265              * @hide
1266              */
1267             public static final String ALBUM_ARTIST = "album_artist";
1268 
1269             /**
1270              * Whether the song is part of a compilation
1271              * <P>Type: TEXT</P>
1272              * @hide
1273              */
1274             public static final String COMPILATION = "compilation";
1275 
1276             /**
1277              * A non human readable key calculated from the ARTIST, used for
1278              * searching, sorting and grouping
1279              * <P>Type: TEXT</P>
1280              */
1281             public static final String ARTIST_KEY = "artist_key";
1282 
1283             /**
1284              * The composer of the audio file, if any
1285              * <P>Type: TEXT</P>
1286              */
1287             public static final String COMPOSER = "composer";
1288 
1289             /**
1290              * The id of the album the audio file is from, if any
1291              * <P>Type: INTEGER (long)</P>
1292              */
1293             public static final String ALBUM_ID = "album_id";
1294 
1295             /**
1296              * The album the audio file is from, if any
1297              * <P>Type: TEXT</P>
1298              */
1299             public static final String ALBUM = "album";
1300 
1301             /**
1302              * A non human readable key calculated from the ALBUM, used for
1303              * searching, sorting and grouping
1304              * <P>Type: TEXT</P>
1305              */
1306             public static final String ALBUM_KEY = "album_key";
1307 
1308             /**
1309              * The track number of this song on the album, if any.
1310              * This number encodes both the track number and the
1311              * disc number. For multi-disc sets, this number will
1312              * be 1xxx for tracks on the first disc, 2xxx for tracks
1313              * on the second disc, etc.
1314              * <P>Type: INTEGER</P>
1315              */
1316             public static final String TRACK = "track";
1317 
1318             /**
1319              * The year the audio file was recorded, if any
1320              * <P>Type: INTEGER</P>
1321              */
1322             public static final String YEAR = "year";
1323 
1324             /**
1325              * Non-zero if the audio file is music
1326              * <P>Type: INTEGER (boolean)</P>
1327              */
1328             public static final String IS_MUSIC = "is_music";
1329 
1330             /**
1331              * Non-zero if the audio file is a podcast
1332              * <P>Type: INTEGER (boolean)</P>
1333              */
1334             public static final String IS_PODCAST = "is_podcast";
1335 
1336             /**
1337              * Non-zero if the audio file may be a ringtone
1338              * <P>Type: INTEGER (boolean)</P>
1339              */
1340             public static final String IS_RINGTONE = "is_ringtone";
1341 
1342             /**
1343              * Non-zero if the audio file may be an alarm
1344              * <P>Type: INTEGER (boolean)</P>
1345              */
1346             public static final String IS_ALARM = "is_alarm";
1347 
1348             /**
1349              * Non-zero if the audio file may be a notification sound
1350              * <P>Type: INTEGER (boolean)</P>
1351              */
1352             public static final String IS_NOTIFICATION = "is_notification";
1353 
1354             /**
1355              * The genre of the audio file, if any
1356              * <P>Type: TEXT</P>
1357              * Does not exist in the database - only used by the media scanner for inserts.
1358              * @hide
1359              */
1360             public static final String GENRE = "genre";
1361         }
1362 
1363         /**
1364          * Converts a name to a "key" that can be used for grouping, sorting
1365          * and searching.
1366          * The rules that govern this conversion are:
1367          * - remove 'special' characters like ()[]'!?.,
1368          * - remove leading/trailing spaces
1369          * - convert everything to lowercase
1370          * - remove leading "the ", "an " and "a "
1371          * - remove trailing ", the|an|a"
1372          * - remove accents. This step leaves us with CollationKey data,
1373          *   which is not human readable
1374          *
1375          * @param name The artist or album name to convert
1376          * @return The "key" for the given name.
1377          */
keyFor(String name)1378         public static String keyFor(String name) {
1379             if (name != null)  {
1380                 boolean sortfirst = false;
1381                 if (name.equals(UNKNOWN_STRING)) {
1382                     return "\001";
1383                 }
1384                 // Check if the first character is \001. We use this to
1385                 // force sorting of certain special files, like the silent ringtone.
1386                 if (name.startsWith("\001")) {
1387                     sortfirst = true;
1388                 }
1389                 name = name.trim().toLowerCase();
1390                 if (name.startsWith("the ")) {
1391                     name = name.substring(4);
1392                 }
1393                 if (name.startsWith("an ")) {
1394                     name = name.substring(3);
1395                 }
1396                 if (name.startsWith("a ")) {
1397                     name = name.substring(2);
1398                 }
1399                 if (name.endsWith(", the") || name.endsWith(",the") ||
1400                     name.endsWith(", an") || name.endsWith(",an") ||
1401                     name.endsWith(", a") || name.endsWith(",a")) {
1402                     name = name.substring(0, name.lastIndexOf(','));
1403                 }
1404                 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
1405                 if (name.length() > 0) {
1406                     // Insert a separator between the characters to avoid
1407                     // matches on a partial character. If we ever change
1408                     // to start-of-word-only matches, this can be removed.
1409                     StringBuilder b = new StringBuilder();
1410                     b.append('.');
1411                     int nl = name.length();
1412                     for (int i = 0; i < nl; i++) {
1413                         b.append(name.charAt(i));
1414                         b.append('.');
1415                     }
1416                     name = b.toString();
1417                     String key = DatabaseUtils.getCollationKey(name);
1418                     if (sortfirst) {
1419                         key = "\001" + key;
1420                     }
1421                     return key;
1422                } else {
1423                     return "";
1424                 }
1425             }
1426             return null;
1427         }
1428 
1429         public static final class Media implements AudioColumns {
1430 
1431             private static final String[] EXTERNAL_PATHS;
1432 
1433             static {
1434                 String secondary_storage = System.getenv("SECONDARY_STORAGE");
1435                 if (secondary_storage != null) {
1436                     EXTERNAL_PATHS = secondary_storage.split(":");
1437                 } else {
1438                     EXTERNAL_PATHS = new String[0];
1439                 }
1440             }
1441 
1442             /**
1443              * Get the content:// style URI for the audio media table on the
1444              * given volume.
1445              *
1446              * @param volumeName the name of the volume to get the URI for
1447              * @return the URI to the audio media table on the given volume
1448              */
getContentUri(String volumeName)1449             public static Uri getContentUri(String volumeName) {
1450                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1451                         "/audio/media");
1452             }
1453 
getContentUriForPath(String path)1454             public static Uri getContentUriForPath(String path) {
1455                 for (String ep : EXTERNAL_PATHS) {
1456                     if (path.startsWith(ep)) {
1457                         return EXTERNAL_CONTENT_URI;
1458                     }
1459                 }
1460 
1461                 return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
1462                         EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
1463             }
1464 
1465             /**
1466              * The content:// style URI for the internal storage.
1467              */
1468             public static final Uri INTERNAL_CONTENT_URI =
1469                     getContentUri("internal");
1470 
1471             /**
1472              * The content:// style URI for the "primary" external storage
1473              * volume.
1474              */
1475             public static final Uri EXTERNAL_CONTENT_URI =
1476                     getContentUri("external");
1477 
1478             /**
1479              * The MIME type for this table.
1480              */
1481             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
1482 
1483             /**
1484              * The MIME type for an audio track.
1485              */
1486             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
1487 
1488             /**
1489              * The default sort order for this table
1490              */
1491             public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1492 
1493             /**
1494              * Activity Action: Start SoundRecorder application.
1495              * <p>Input: nothing.
1496              * <p>Output: An uri to the recorded sound stored in the Media Library
1497              * if the recording was successful.
1498              * May also contain the extra EXTRA_MAX_BYTES.
1499              * @see #EXTRA_MAX_BYTES
1500              */
1501             @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
1502             public static final String RECORD_SOUND_ACTION =
1503                     "android.provider.MediaStore.RECORD_SOUND";
1504 
1505             /**
1506              * The name of the Intent-extra used to define a maximum file size for
1507              * a recording made by the SoundRecorder application.
1508              *
1509              * @see #RECORD_SOUND_ACTION
1510              */
1511              public static final String EXTRA_MAX_BYTES =
1512                     "android.provider.MediaStore.extra.MAX_BYTES";
1513         }
1514 
1515         /**
1516          * Columns representing an audio genre
1517          */
1518         public interface GenresColumns {
1519             /**
1520              * The name of the genre
1521              * <P>Type: TEXT</P>
1522              */
1523             public static final String NAME = "name";
1524         }
1525 
1526         /**
1527          * Contains all genres for audio files
1528          */
1529         public static final class Genres implements BaseColumns, GenresColumns {
1530             /**
1531              * Get the content:// style URI for the audio genres table on the
1532              * given volume.
1533              *
1534              * @param volumeName the name of the volume to get the URI for
1535              * @return the URI to the audio genres table on the given volume
1536              */
getContentUri(String volumeName)1537             public static Uri getContentUri(String volumeName) {
1538                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1539                         "/audio/genres");
1540             }
1541 
1542             /**
1543              * Get the content:// style URI for querying the genres of an audio file.
1544              *
1545              * @param volumeName the name of the volume to get the URI for
1546              * @param audioId the ID of the audio file for which to retrieve the genres
1547              * @return the URI to for querying the genres for the audio file
1548              * with the given the volume and audioID
1549              */
getContentUriForAudioId(String volumeName, int audioId)1550             public static Uri getContentUriForAudioId(String volumeName, int audioId) {
1551                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1552                         "/audio/media/" + audioId + "/genres");
1553             }
1554 
1555             /**
1556              * The content:// style URI for the internal storage.
1557              */
1558             public static final Uri INTERNAL_CONTENT_URI =
1559                     getContentUri("internal");
1560 
1561             /**
1562              * The content:// style URI for the "primary" external storage
1563              * volume.
1564              */
1565             public static final Uri EXTERNAL_CONTENT_URI =
1566                     getContentUri("external");
1567 
1568             /**
1569              * The MIME type for this table.
1570              */
1571             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
1572 
1573             /**
1574              * The MIME type for entries in this table.
1575              */
1576             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
1577 
1578             /**
1579              * The default sort order for this table
1580              */
1581             public static final String DEFAULT_SORT_ORDER = NAME;
1582 
1583             /**
1584              * Sub-directory of each genre containing all members.
1585              */
1586             public static final class Members implements AudioColumns {
1587 
getContentUri(String volumeName, long genreId)1588                 public static final Uri getContentUri(String volumeName,
1589                         long genreId) {
1590                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1591                             + "/audio/genres/" + genreId + "/members");
1592                 }
1593 
1594                 /**
1595                  * A subdirectory of each genre containing all member audio files.
1596                  */
1597                 public static final String CONTENT_DIRECTORY = "members";
1598 
1599                 /**
1600                  * The default sort order for this table
1601                  */
1602                 public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1603 
1604                 /**
1605                  * The ID of the audio file
1606                  * <P>Type: INTEGER (long)</P>
1607                  */
1608                 public static final String AUDIO_ID = "audio_id";
1609 
1610                 /**
1611                  * The ID of the genre
1612                  * <P>Type: INTEGER (long)</P>
1613                  */
1614                 public static final String GENRE_ID = "genre_id";
1615             }
1616         }
1617 
1618         /**
1619          * Columns representing a playlist
1620          */
1621         public interface PlaylistsColumns {
1622             /**
1623              * The name of the playlist
1624              * <P>Type: TEXT</P>
1625              */
1626             public static final String NAME = "name";
1627 
1628             /**
1629              * Path to the playlist file on disk.
1630              * <p>
1631              * Note that apps may not have filesystem permissions to directly
1632              * access this path. Instead of trying to open this path directly,
1633              * apps should use
1634              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
1635              * access.
1636              * <p>
1637              * Type: TEXT
1638              */
1639             public static final String DATA = "_data";
1640 
1641             /**
1642              * The time the file was added to the media provider
1643              * Units are seconds since 1970.
1644              * <P>Type: INTEGER (long)</P>
1645              */
1646             public static final String DATE_ADDED = "date_added";
1647 
1648             /**
1649              * The time the file was last modified
1650              * Units are seconds since 1970.
1651              * NOTE: This is for internal use by the media scanner.  Do not modify this field.
1652              * <P>Type: INTEGER (long)</P>
1653              */
1654             public static final String DATE_MODIFIED = "date_modified";
1655         }
1656 
1657         /**
1658          * Contains playlists for audio files
1659          */
1660         public static final class Playlists implements BaseColumns,
1661                 PlaylistsColumns {
1662             /**
1663              * Get the content:// style URI for the audio playlists table on the
1664              * given volume.
1665              *
1666              * @param volumeName the name of the volume to get the URI for
1667              * @return the URI to the audio playlists table on the given volume
1668              */
getContentUri(String volumeName)1669             public static Uri getContentUri(String volumeName) {
1670                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1671                         "/audio/playlists");
1672             }
1673 
1674             /**
1675              * The content:// style URI for the internal storage.
1676              */
1677             public static final Uri INTERNAL_CONTENT_URI =
1678                     getContentUri("internal");
1679 
1680             /**
1681              * The content:// style URI for the "primary" external storage
1682              * volume.
1683              */
1684             public static final Uri EXTERNAL_CONTENT_URI =
1685                     getContentUri("external");
1686 
1687             /**
1688              * The MIME type for this table.
1689              */
1690             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
1691 
1692             /**
1693              * The MIME type for entries in this table.
1694              */
1695             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
1696 
1697             /**
1698              * The default sort order for this table
1699              */
1700             public static final String DEFAULT_SORT_ORDER = NAME;
1701 
1702             /**
1703              * Sub-directory of each playlist containing all members.
1704              */
1705             public static final class Members implements AudioColumns {
getContentUri(String volumeName, long playlistId)1706                 public static final Uri getContentUri(String volumeName,
1707                         long playlistId) {
1708                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1709                             + "/audio/playlists/" + playlistId + "/members");
1710                 }
1711 
1712                 /**
1713                  * Convenience method to move a playlist item to a new location
1714                  * @param res The content resolver to use
1715                  * @param playlistId The numeric id of the playlist
1716                  * @param from The position of the item to move
1717                  * @param to The position to move the item to
1718                  * @return true on success
1719                  */
moveItem(ContentResolver res, long playlistId, int from, int to)1720                 public static final boolean moveItem(ContentResolver res,
1721                         long playlistId, int from, int to) {
1722                     Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
1723                             playlistId)
1724                             .buildUpon()
1725                             .appendEncodedPath(String.valueOf(from))
1726                             .appendQueryParameter("move", "true")
1727                             .build();
1728                     ContentValues values = new ContentValues();
1729                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
1730                     return res.update(uri, values, null, null) != 0;
1731                 }
1732 
1733                 /**
1734                  * The ID within the playlist.
1735                  */
1736                 public static final String _ID = "_id";
1737 
1738                 /**
1739                  * A subdirectory of each playlist containing all member audio
1740                  * files.
1741                  */
1742                 public static final String CONTENT_DIRECTORY = "members";
1743 
1744                 /**
1745                  * The ID of the audio file
1746                  * <P>Type: INTEGER (long)</P>
1747                  */
1748                 public static final String AUDIO_ID = "audio_id";
1749 
1750                 /**
1751                  * The ID of the playlist
1752                  * <P>Type: INTEGER (long)</P>
1753                  */
1754                 public static final String PLAYLIST_ID = "playlist_id";
1755 
1756                 /**
1757                  * The order of the songs in the playlist
1758                  * <P>Type: INTEGER (long)></P>
1759                  */
1760                 public static final String PLAY_ORDER = "play_order";
1761 
1762                 /**
1763                  * The default sort order for this table
1764                  */
1765                 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
1766             }
1767         }
1768 
1769         /**
1770          * Columns representing an artist
1771          */
1772         public interface ArtistColumns {
1773             /**
1774              * The artist who created the audio file, if any
1775              * <P>Type: TEXT</P>
1776              */
1777             public static final String ARTIST = "artist";
1778 
1779             /**
1780              * A non human readable key calculated from the ARTIST, used for
1781              * searching, sorting and grouping
1782              * <P>Type: TEXT</P>
1783              */
1784             public static final String ARTIST_KEY = "artist_key";
1785 
1786             /**
1787              * The number of albums in the database for this artist
1788              */
1789             public static final String NUMBER_OF_ALBUMS = "number_of_albums";
1790 
1791             /**
1792              * The number of albums in the database for this artist
1793              */
1794             public static final String NUMBER_OF_TRACKS = "number_of_tracks";
1795         }
1796 
1797         /**
1798          * Contains artists for audio files
1799          */
1800         public static final class Artists implements BaseColumns, ArtistColumns {
1801             /**
1802              * Get the content:// style URI for the artists table on the
1803              * given volume.
1804              *
1805              * @param volumeName the name of the volume to get the URI for
1806              * @return the URI to the audio artists table on the given volume
1807              */
getContentUri(String volumeName)1808             public static Uri getContentUri(String volumeName) {
1809                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1810                         "/audio/artists");
1811             }
1812 
1813             /**
1814              * The content:// style URI for the internal storage.
1815              */
1816             public static final Uri INTERNAL_CONTENT_URI =
1817                     getContentUri("internal");
1818 
1819             /**
1820              * The content:// style URI for the "primary" external storage
1821              * volume.
1822              */
1823             public static final Uri EXTERNAL_CONTENT_URI =
1824                     getContentUri("external");
1825 
1826             /**
1827              * The MIME type for this table.
1828              */
1829             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
1830 
1831             /**
1832              * The MIME type for entries in this table.
1833              */
1834             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
1835 
1836             /**
1837              * The default sort order for this table
1838              */
1839             public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
1840 
1841             /**
1842              * Sub-directory of each artist containing all albums on which
1843              * a song by the artist appears.
1844              */
1845             public static final class Albums implements AlbumColumns {
getContentUri(String volumeName, long artistId)1846                 public static final Uri getContentUri(String volumeName,
1847                         long artistId) {
1848                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1849                             + "/audio/artists/" + artistId + "/albums");
1850                 }
1851             }
1852         }
1853 
1854         /**
1855          * Columns representing an album
1856          */
1857         public interface AlbumColumns {
1858 
1859             /**
1860              * The id for the album
1861              * <P>Type: INTEGER</P>
1862              */
1863             public static final String ALBUM_ID = "album_id";
1864 
1865             /**
1866              * The album on which the audio file appears, if any
1867              * <P>Type: TEXT</P>
1868              */
1869             public static final String ALBUM = "album";
1870 
1871             /**
1872              * The artist whose songs appear on this album
1873              * <P>Type: TEXT</P>
1874              */
1875             public static final String ARTIST = "artist";
1876 
1877             /**
1878              * The number of songs on this album
1879              * <P>Type: INTEGER</P>
1880              */
1881             public static final String NUMBER_OF_SONGS = "numsongs";
1882 
1883             /**
1884              * This column is available when getting album info via artist,
1885              * and indicates the number of songs on the album by the given
1886              * artist.
1887              * <P>Type: INTEGER</P>
1888              */
1889             public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
1890 
1891             /**
1892              * The year in which the earliest songs
1893              * on this album were released. This will often
1894              * be the same as {@link #LAST_YEAR}, but for compilation albums
1895              * they might differ.
1896              * <P>Type: INTEGER</P>
1897              */
1898             public static final String FIRST_YEAR = "minyear";
1899 
1900             /**
1901              * The year in which the latest songs
1902              * on this album were released. This will often
1903              * be the same as {@link #FIRST_YEAR}, but for compilation albums
1904              * they might differ.
1905              * <P>Type: INTEGER</P>
1906              */
1907             public static final String LAST_YEAR = "maxyear";
1908 
1909             /**
1910              * A non human readable key calculated from the ALBUM, used for
1911              * searching, sorting and grouping
1912              * <P>Type: TEXT</P>
1913              */
1914             public static final String ALBUM_KEY = "album_key";
1915 
1916             /**
1917              * Cached album art.
1918              * <P>Type: TEXT</P>
1919              */
1920             public static final String ALBUM_ART = "album_art";
1921         }
1922 
1923         /**
1924          * Contains artists for audio files
1925          */
1926         public static final class Albums implements BaseColumns, AlbumColumns {
1927             /**
1928              * Get the content:// style URI for the albums table on the
1929              * given volume.
1930              *
1931              * @param volumeName the name of the volume to get the URI for
1932              * @return the URI to the audio albums table on the given volume
1933              */
getContentUri(String volumeName)1934             public static Uri getContentUri(String volumeName) {
1935                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1936                         "/audio/albums");
1937             }
1938 
1939             /**
1940              * The content:// style URI for the internal storage.
1941              */
1942             public static final Uri INTERNAL_CONTENT_URI =
1943                     getContentUri("internal");
1944 
1945             /**
1946              * The content:// style URI for the "primary" external storage
1947              * volume.
1948              */
1949             public static final Uri EXTERNAL_CONTENT_URI =
1950                     getContentUri("external");
1951 
1952             /**
1953              * The MIME type for this table.
1954              */
1955             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
1956 
1957             /**
1958              * The MIME type for entries in this table.
1959              */
1960             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
1961 
1962             /**
1963              * The default sort order for this table
1964              */
1965             public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
1966         }
1967 
1968         public static final class Radio {
1969             /**
1970              * The MIME type for entries in this table.
1971              */
1972             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
1973 
1974             // Not instantiable.
Radio()1975             private Radio() { }
1976         }
1977     }
1978 
1979     public static final class Video {
1980 
1981         /**
1982          * The default sort order for this table.
1983          */
1984         public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
1985 
query(ContentResolver cr, Uri uri, String[] projection)1986         public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1987             return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1988         }
1989 
1990         public interface VideoColumns extends MediaColumns {
1991 
1992             /**
1993              * The duration of the video file, in ms
1994              * <P>Type: INTEGER (long)</P>
1995              */
1996             public static final String DURATION = "duration";
1997 
1998             /**
1999              * The artist who created the video file, if any
2000              * <P>Type: TEXT</P>
2001              */
2002             public static final String ARTIST = "artist";
2003 
2004             /**
2005              * The album the video file is from, if any
2006              * <P>Type: TEXT</P>
2007              */
2008             public static final String ALBUM = "album";
2009 
2010             /**
2011              * The resolution of the video file, formatted as "XxY"
2012              * <P>Type: TEXT</P>
2013              */
2014             public static final String RESOLUTION = "resolution";
2015 
2016             /**
2017              * The description of the video recording
2018              * <P>Type: TEXT</P>
2019              */
2020             public static final String DESCRIPTION = "description";
2021 
2022             /**
2023              * Whether the video should be published as public or private
2024              * <P>Type: INTEGER</P>
2025              */
2026             public static final String IS_PRIVATE = "isprivate";
2027 
2028             /**
2029              * The user-added tags associated with a video
2030              * <P>Type: TEXT</P>
2031              */
2032             public static final String TAGS = "tags";
2033 
2034             /**
2035              * The YouTube category of the video
2036              * <P>Type: TEXT</P>
2037              */
2038             public static final String CATEGORY = "category";
2039 
2040             /**
2041              * The language of the video
2042              * <P>Type: TEXT</P>
2043              */
2044             public static final String LANGUAGE = "language";
2045 
2046             /**
2047              * The latitude where the video was captured.
2048              * <P>Type: DOUBLE</P>
2049              */
2050             public static final String LATITUDE = "latitude";
2051 
2052             /**
2053              * The longitude where the video was captured.
2054              * <P>Type: DOUBLE</P>
2055              */
2056             public static final String LONGITUDE = "longitude";
2057 
2058             /**
2059              * The date & time that the video was taken in units
2060              * of milliseconds since jan 1, 1970.
2061              * <P>Type: INTEGER</P>
2062              */
2063             public static final String DATE_TAKEN = "datetaken";
2064 
2065             /**
2066              * The mini thumb id.
2067              * <P>Type: INTEGER</P>
2068              */
2069             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
2070 
2071             /**
2072              * The bucket id of the video. This is a read-only property that
2073              * is automatically computed from the DATA column.
2074              * <P>Type: TEXT</P>
2075              */
2076             public static final String BUCKET_ID = "bucket_id";
2077 
2078             /**
2079              * The bucket display name of the video. This is a read-only property that
2080              * is automatically computed from the DATA column.
2081              * <P>Type: TEXT</P>
2082              */
2083             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
2084 
2085             /**
2086              * The bookmark for the video. Time in ms. Represents the location in the video that the
2087              * video should start playing at the next time it is opened. If the value is null or
2088              * out of the range 0..DURATION-1 then the video should start playing from the
2089              * beginning.
2090              * <P>Type: INTEGER</P>
2091              */
2092             public static final String BOOKMARK = "bookmark";
2093         }
2094 
2095         public static final class Media implements VideoColumns {
2096             /**
2097              * Get the content:// style URI for the video media table on the
2098              * given volume.
2099              *
2100              * @param volumeName the name of the volume to get the URI for
2101              * @return the URI to the video media table on the given volume
2102              */
getContentUri(String volumeName)2103             public static Uri getContentUri(String volumeName) {
2104                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2105                         "/video/media");
2106             }
2107 
2108             /**
2109              * The content:// style URI for the internal storage.
2110              */
2111             public static final Uri INTERNAL_CONTENT_URI =
2112                     getContentUri("internal");
2113 
2114             /**
2115              * The content:// style URI for the "primary" external storage
2116              * volume.
2117              */
2118             public static final Uri EXTERNAL_CONTENT_URI =
2119                     getContentUri("external");
2120 
2121             /**
2122              * The MIME type for this table.
2123              */
2124             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
2125 
2126             /**
2127              * The default sort order for this table
2128              */
2129             public static final String DEFAULT_SORT_ORDER = TITLE;
2130         }
2131 
2132         /**
2133          * This class allows developers to query and get two kinds of thumbnails:
2134          * MINI_KIND: 512 x 384 thumbnail
2135          * MICRO_KIND: 96 x 96 thumbnail
2136          *
2137          */
2138         public static class Thumbnails implements BaseColumns {
2139             /**
2140              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2141              * interrupted and return immediately. Only the original process which made the getThumbnail
2142              * requests can cancel their own requests.
2143              *
2144              * @param cr ContentResolver
2145              * @param origId original video id
2146              */
cancelThumbnailRequest(ContentResolver cr, long origId)2147             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
2148                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
2149                         InternalThumbnails.DEFAULT_GROUP_ID);
2150             }
2151 
2152             /**
2153              * This method checks if the thumbnails of the specified image (origId) has been created.
2154              * It will be blocked until the thumbnails are generated.
2155              *
2156              * @param cr ContentResolver used to dispatch queries to MediaProvider.
2157              * @param origId Original image id associated with thumbnail of interest.
2158              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
2159              * @param options this is only used for MINI_KIND when decoding the Bitmap
2160              * @return A Bitmap instance. It could be null if the original image
2161              *         associated with origId doesn't exist or memory is not enough.
2162              */
getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options)2163             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
2164                     BitmapFactory.Options options) {
2165                 return InternalThumbnails.getThumbnail(cr, origId,
2166                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
2167                         EXTERNAL_CONTENT_URI, true);
2168             }
2169 
2170             /**
2171              * This method checks if the thumbnails of the specified image (origId) has been created.
2172              * It will be blocked until the thumbnails are generated.
2173              *
2174              * @param cr ContentResolver used to dispatch queries to MediaProvider.
2175              * @param origId Original image id associated with thumbnail of interest.
2176              * @param groupId the id of group to which this request belongs
2177              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
2178              * @param options this is only used for MINI_KIND when decoding the Bitmap
2179              * @return A Bitmap instance. It could be null if the original image associated with
2180              *         origId doesn't exist or memory is not enough.
2181              */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options)2182             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
2183                     int kind, BitmapFactory.Options options) {
2184                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
2185                         EXTERNAL_CONTENT_URI, true);
2186             }
2187 
2188             /**
2189              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2190              * interrupted and return immediately. Only the original process which made the getThumbnail
2191              * requests can cancel their own requests.
2192              *
2193              * @param cr ContentResolver
2194              * @param origId original video id
2195              * @param groupId the same groupId used in getThumbnail.
2196              */
cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)2197             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
2198                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
2199             }
2200 
2201             /**
2202              * Get the content:// style URI for the image media table on the
2203              * given volume.
2204              *
2205              * @param volumeName the name of the volume to get the URI for
2206              * @return the URI to the image media table on the given volume
2207              */
getContentUri(String volumeName)2208             public static Uri getContentUri(String volumeName) {
2209                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2210                         "/video/thumbnails");
2211             }
2212 
2213             /**
2214              * The content:// style URI for the internal storage.
2215              */
2216             public static final Uri INTERNAL_CONTENT_URI =
2217                     getContentUri("internal");
2218 
2219             /**
2220              * The content:// style URI for the "primary" external storage
2221              * volume.
2222              */
2223             public static final Uri EXTERNAL_CONTENT_URI =
2224                     getContentUri("external");
2225 
2226             /**
2227              * The default sort order for this table
2228              */
2229             public static final String DEFAULT_SORT_ORDER = "video_id ASC";
2230 
2231             /**
2232              * Path to the thumbnail file on disk.
2233              * <p>
2234              * Note that apps may not have filesystem permissions to directly
2235              * access this path. Instead of trying to open this path directly,
2236              * apps should use
2237              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
2238              * access.
2239              * <p>
2240              * Type: TEXT
2241              */
2242             public static final String DATA = "_data";
2243 
2244             /**
2245              * The original image for the thumbnal
2246              * <P>Type: INTEGER (ID from Video table)</P>
2247              */
2248             public static final String VIDEO_ID = "video_id";
2249 
2250             /**
2251              * The kind of the thumbnail
2252              * <P>Type: INTEGER (One of the values below)</P>
2253              */
2254             public static final String KIND = "kind";
2255 
2256             public static final int MINI_KIND = 1;
2257             public static final int FULL_SCREEN_KIND = 2;
2258             public static final int MICRO_KIND = 3;
2259 
2260             /**
2261              * The width of the thumbnal
2262              * <P>Type: INTEGER (long)</P>
2263              */
2264             public static final String WIDTH = "width";
2265 
2266             /**
2267              * The height of the thumbnail
2268              * <P>Type: INTEGER (long)</P>
2269              */
2270             public static final String HEIGHT = "height";
2271         }
2272     }
2273 
2274     /**
2275      * Uri for querying the state of the media scanner.
2276      */
getMediaScannerUri()2277     public static Uri getMediaScannerUri() {
2278         return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner");
2279     }
2280 
2281     /**
2282      * Name of current volume being scanned by the media scanner.
2283      */
2284     public static final String MEDIA_SCANNER_VOLUME = "volume";
2285 
2286     /**
2287      * Name of the file signaling the media scanner to ignore media in the containing directory
2288      * and its subdirectories. Developers should use this to avoid application graphics showing
2289      * up in the Gallery and likewise prevent application sounds and music from showing up in
2290      * the Music app.
2291      */
2292     public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
2293 
2294     /**
2295      * Get the media provider's version.
2296      * Applications that import data from the media provider into their own caches
2297      * can use this to detect that the media provider changed, and reimport data
2298      * as needed. No other assumptions should be made about the meaning of the version.
2299      * @param context Context to use for performing the query.
2300      * @return A version string, or null if the version could not be determined.
2301      */
getVersion(Context context)2302     public static String getVersion(Context context) {
2303         Cursor c = context.getContentResolver().query(
2304                 Uri.parse(CONTENT_AUTHORITY_SLASH + "none/version"),
2305                 null, null, null, null);
2306         if (c != null) {
2307             try {
2308                 if (c.moveToFirst()) {
2309                     return c.getString(0);
2310                 }
2311             } finally {
2312                 c.close();
2313             }
2314         }
2315         return null;
2316     }
2317 
2318     /**
2319      * Gets a URI backed by a {@link DocumentsProvider} that points to the same media
2320      * file as the specified mediaUri. This allows apps who have permissions to access
2321      * media files in Storage Access Framework to perform file operations through that
2322      * on media files.
2323      * <p>
2324      * Note: this method doesn't grant any URI permission. Callers need to obtain
2325      * permission before calling this method. One way to obtain permission is through
2326      * a 3-step process:
2327      * <ol>
2328      *     <li>Call {@link android.os.storage.StorageManager#getStorageVolume(File)} to
2329      *     obtain the {@link android.os.storage.StorageVolume} of a media file;</li>
2330      *
2331      *     <li>Invoke the intent returned by
2332      *     {@link android.os.storage.StorageVolume#createAccessIntent(String)} to
2333      *     obtain the access of the volume or one of its specific subdirectories;</li>
2334      *
2335      *     <li>Check whether permission is granted and take persistent permission.</li>
2336      * </ol>
2337      * @param mediaUri the media URI which document URI is requested
2338      * @return the document URI
2339      */
getDocumentUri(Context context, Uri mediaUri)2340     public static Uri getDocumentUri(Context context, Uri mediaUri) {
2341 
2342         try {
2343             final ContentResolver resolver = context.getContentResolver();
2344 
2345             final String path = getFilePath(resolver, mediaUri);
2346             final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
2347 
2348             return getDocumentUri(resolver, path, uriPermissions);
2349         } catch (RemoteException e) {
2350             throw e.rethrowAsRuntimeException();
2351         }
2352     }
2353 
getFilePath(ContentResolver resolver, Uri mediaUri)2354     private static String getFilePath(ContentResolver resolver, Uri mediaUri)
2355             throws RemoteException {
2356 
2357         try (ContentProviderClient client =
2358                      resolver.acquireUnstableContentProviderClient(AUTHORITY)) {
2359             final Cursor c = client.query(
2360                     mediaUri,
2361                     new String[]{ MediaColumns.DATA },
2362                     null, /* selection */
2363                     null, /* selectionArg */
2364                     null /* sortOrder */);
2365 
2366             final String path;
2367             try {
2368                 if (c.getCount() == 0) {
2369                     throw new IllegalStateException("Not found media file under URI: " + mediaUri);
2370                 }
2371 
2372                 if (!c.moveToFirst()) {
2373                     throw new IllegalStateException("Failed to move cursor to the first item.");
2374                 }
2375 
2376                 path = c.getString(0);
2377             } finally {
2378                 IoUtils.closeQuietly(c);
2379             }
2380 
2381             return path;
2382         }
2383     }
2384 
getDocumentUri( ContentResolver resolver, String path, List<UriPermission> uriPermissions)2385     private static Uri getDocumentUri(
2386             ContentResolver resolver, String path, List<UriPermission> uriPermissions)
2387             throws RemoteException {
2388 
2389         try (ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
2390                 DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY)) {
2391             final Bundle in = new Bundle();
2392             in.putParcelableList(
2393                     DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY + ".extra.uriPermissions",
2394                     uriPermissions);
2395             final Bundle out = client.call("getDocumentId", path, in);
2396             return out.getParcelable(DocumentsContract.EXTRA_URI);
2397         }
2398     }
2399 }
2400