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