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