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