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