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