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