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