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