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
17package android.provider;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.database.Cursor;
27import android.database.DatabaseUtils;
28import android.database.sqlite.SQLiteException;
29import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.Matrix;
32import android.media.MiniThumbFile;
33import android.media.ThumbnailUtils;
34import android.net.Uri;
35import android.os.Environment;
36import android.os.ParcelFileDescriptor;
37import android.service.media.CameraPrewarmService;
38import android.util.Log;
39
40import java.io.FileInputStream;
41import java.io.FileNotFoundException;
42import java.io.IOException;
43import java.io.InputStream;
44import java.io.OutputStream;
45import 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 */
51public 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         */
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         */
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         */
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         */
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         */
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
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         */
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         */
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 {
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
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
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             */
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             */
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
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             */
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             */
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 {
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
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
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             */
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             */
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             */
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             */
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             */
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         */
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             */
1420            public static Uri getContentUri(String volumeName) {
1421                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1422                        "/audio/media");
1423            }
1424
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             */
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             */
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
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             */
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 {
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                 */
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             */
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 {
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             */
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.
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
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             */
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             */
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             */
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             */
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             */
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             */
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     */
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     */
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